commit - c3bf1e83ca58b6e1c539af029942c9b49a3df6ef
commit + 493b34178ab94af91370be67931f4961a736f19d
blob - 71f14b87f91feebf8d675ca16719a501977b82b1
blob + d76abfb73d37abc848e9d08d869aae0da1bfd9a4
--- generate.py
+++ generate.py
STATUS_RUNTIME_ERROR = "RuntimeError"
STATUS_TIMEOUT = "Timeout"
STATUS_IGNORED = "Ignored"
+
+# Source: https://mull.readthedocs.io/en/latest/SupportedMutations.html
+
+MUTATION_TYPES = {"cxx_add_assign_to_sub_assign": {"old": "+=", "new": "-="},
+ "cxx_add_to_sub": {"old": "+", "new": "-"},
+ "cxx_and_assign_to_or_assign": {"old": "&=", "new": "|="},
+ "cxx_and_to_or": {"old": "&", "new": "|"},
+ "cxx_assign_const": "Replaces ‘a = b’ with ‘a = 42’",
+ "cxx_bitwise_not_to_noop": "Replaces ~x with x",
+ "cxx_div_assign_to_mul_assign": {"old": "/=", "new": "*="},
+ "cxx_div_to_mul": {"old": "/", "new": "*"},
+ "cxx_eq_to_ne": {"old": "==", "new": "!="},
+ "cxx_ge_to_gt": {"old": ">=", "new": ">"},
+ "cxx_ge_to_lt": {"old": ">=", "new": "<"},
+ "cxx_gt_to_ge": {"old": ">", "new": ">="},
+ "cxx_gt_to_le": {"old": ">", "new": "<="},
+ "cxx_init_const": "Replaces ‘T a = b’ with ‘T a = 42’",
+ "cxx_le_to_gt": {"old": "<=", "new": ">"},
+ "cxx_le_to_lt": {"old": "<=", "new": "<"},
+ "cxx_logical_and_to_or": {"old": "&&", "new": "||"},
+ "cxx_logical_or_to_and": {"old": "||", "new": "&&"},
+ "cxx_lshift_assign_to_rshift_assign": {"old": "<<=", "new": ">>="},
+ "cxx_lshift_to_rshift": {"old": "<<", "new": ">>"},
+ "cxx_lt_to_ge": {"old": "<", "new": ">="},
+ "cxx_lt_to_le": {"old": "<", "new": "<="},
+ "cxx_minus_to_noop": "Replaces -x with x",
+ "cxx_mul_assign_to_div_assign": {"old": "*=", "new": "/="},
+ "cxx_mul_to_div": {"old": "*", "new": "/"},
+ "cxx_ne_to_eq": {"old": "!=", "new": "=="},
+ "cxx_or_assign_to_and_assign": {"old": "|=", "new": "&="},
+ "cxx_or_to_and": {"old": "|", "new": "&"},
+ "cxx_post_dec_to_post_inc": "Replaces x– with x++",
+ "cxx_post_inc_to_post_dec": "Replaces x++ with x–",
+ "cxx_pre_dec_to_pre_inc": "Replaces –x with ++x",
+ "cxx_pre_inc_to_pre_dec": "Replaces ++x with –x",
+ "cxx_rem_assign_to_div_assign": {"old": "%=", "new": "/="},
+ "cxx_rem_to_div": {"old": "%", "new": "/"},
+ "cxx_remove_negation": "Replaces !a with a",
+ "cxx_rshift_assign_to_lshift_assign": {"old": ">>=", "new": "<<="},
+ "cxx_rshift_to_lshift": {"old": "<<", "new": ">>"},
+ "cxx_sub_assign_to_add_assign": {"old": "-=", "new": "+="},
+ "cxx_sub_to_add": {"old": "-", "new": "+"},
+ "cxx_xor_assign_to_or_assign": {"old": "^=", "new": "|="},
+ "cxx_xor_to_or": {"old": "^", "new": "|"},
+ "negate_mutator": "Negates conditionals !x to x and x to !x",
+ "remove_void_function_mutator": "Removes calls to a function returning void",
+ "replace_call_mutator": "Replaces call to a function with 42",
+ "scalar_value_mutator": "Replaces zeros with 42, and non-zeros with 0",
+ }
LIST_STATUSES = [STATUS_KILLED,
STATUS_SURVIVED,
SUPPORTED_SCHEMA_VERSIONS = ["1.0"]
+INVALID_SYMBOLS = "<>:\"/\|?*. "
+
DEFAULT_TEXT_TEMPLATE = """
{% set files = json_data['files'] %}
</body>
</html>
"""
+
+def escape_invalid_symbols(string, invalid_symbols):
+ escaped_string = string
+ for char in invalid_symbols:
+ escaped_string = escaped_string.replace(char, '-')
+ return escaped_string
+
def print_stdout(json_data):
schema_version = json_data.get("schemaVersion", None)
print("Version:", schema_version)
print("\t{}: {}".format(mutatorName, status))
-def render_template(json_data, template):
- files_mutant_statuses = []
- for file_name, properties in json_data.get("files", None).items():
- files_mutant_statuses.append({file_name: file_mutant_statuses(properties)})
- report_mutant_statuses = sum_statuses(files_mutant_statuses)
-
+def render_template(json_data, report_mutant_statuses, template):
t = Template(template)
time = datetime.datetime.now()
return statuses
-def file_mutant_statuses(json_data):
+def file_mutant_statuses(file_json_data):
"""
json_data: a dict that includes dicts "file" described in
'mutation-elements' schema.
a number mutants with that status.
"""
- mutants = json_data.get("mutants", None)
+ mutants = file_json_data.get("mutants", None)
statuses = dict_statuses()
for mutant in mutants:
status = mutant.get("status", None)
return total_num_statuses
+def file_dict_generator(json_report):
+ files_dict = json_report.get("files", None)
+ for file_path, properties_dict in files_dict.items():
+ yield (file_path, properties_dict)
+def mutant_dict_generator(file_dict):
+ """
+ Generate dicts with structure like below.
+ {
+ "id": "cxx_post_inc_to_post_dec",
+ "location": {
+ "end": {
+ "column": 14,
+ "line": 97
+ },
+ "start": {
+ "column": 2,
+ "line": 97
+ }
+ },
+ "mutatorName": "Replaced x++ with x--",
+ "replacement": "--",
+ "status": "Killed"
+ },
+ """
+ mutants = file_dict.get("mutants", None)
+ for mutant in mutants:
+ yield mutant
+
+def generate_patch_with_mutant(source_file_path, mutant_dict):
+ """
+ Process a dict with sctructure like below and generate a patch with mutant.
+
+ {
+ "id": "cxx_post_inc_to_post_dec",
+ "location": {
+ "end": {
+ "column": 14,
+ "line": 97
+ },
+ "start": {
+ "column": 2,
+ "line": 97
+ }
+ },
+ "mutatorName": "Replaced x++ with x--",
+ "replacement": "--",
+ "status": "Killed"
+ },
+ """
+
+ replacement = mutant_dict.get("replacement", None)
+ if replacement == "":
+ print("replacement is not found")
+ return ""
+
+ location = mutant_dict.get("location", None)
+
+ loc_start = location.get("start", None)
+ loc_end = location.get("end", None)
+
+ start_column = loc_start.get("column", None)
+ start_line = loc_start.get("line", None)
+ end_column = loc_end.get("column", None)
+ end_line = loc_end.get("line", None)
+
+ if end_line != start_line:
+ print("Start line and end line is not equal, it is unsupported")
+ raise(Exception)
+
+ print(source_file_path)
+ source_code_lines = []
+ with open(source_file_path, "r") as source_file:
+ source_code_lines = [line.rstrip() for line in source_file]
+
+ line_no = start_line - 1
+ orig_line = source_code_lines[line_no]
+ changed_line = mutate_string(orig_line, replacement, start_column, end_column)
+ print("'{}' --> '{}'".format(orig_line, changed_line))
+ patch_lines = []
+
+ patch_lines.append("--- {}".format(source_file_path))
+ patch_line_start = 0
+ patch_line_end = len(source_code_lines)
+ if start_line > 3:
+ patch_line_start = start_line - 3
+ if end_line < len(source_code_lines) - 3:
+ patch_line_end = end_line + 3
+
+ patch_lines.append("+++ {}".format(source_file_path))
+ patch_lines.append("@@ -{},{} +{},{} @@".format(patch_line_start, start_column, patch_line_end, end_column))
+ for l in range(patch_line_start, start_line - 1):
+ patch_lines.append(source_code_lines[l])
+ patch_lines.append("- {}".format(orig_line))
+ patch_lines.append("+ {}".format(changed_line))
+ for l in range(end_line, patch_line_end):
+ patch_lines.append(source_code_lines[l])
+
+ return "\n".join(patch_lines)
+
+
+def mutate_string(string, replacement, start_column, end_column):
+ print("{} {}, {}".format(replacement, start_column, end_column))
+ print(string)
+ length = end_column - start_column
+ carets = "^" * length
+ placeholder = " " * start_column
+ print(placeholder, carets)
+
+ return string
+
+
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--data", dest="data_path", default="",
help="Path to a generated report")
parser.add_argument("--html", dest="html", action="store_true",
help="Use HTML in a generated report")
+ parser.add_argument("--with-patches", dest="with_patches", action="store_true",
+ help="Generate files with patches for each mutant")
args = parser.parse_args()
if not os.path.exists(args.data_path):
if args.html:
template = DEFAULT_HTML_TEMPLATE
- report_data = render_template(json_data, template)
+ files_mutant_statuses = []
+ mutant_idx = 0
+ for file_path, properties_dict in file_dict_generator(json_data):
+ files_mutant_statuses.append({file_path: file_mutant_statuses(properties_dict)})
+ if not os.path.exists(file_path):
+ print("{} is not found".format(file_path))
+ continue
+ escaped_source_file_basename = ""
+ if args.with_patches:
+ source_file_basename = os.path.basename(file_path)
+ escaped_source_file_basename = escape_invalid_symbols(source_file_basename, INVALID_SYMBOLS)
+ for mutant_dict in mutant_dict_generator(properties_dict):
+ mutant_idx += 1
+ mutant_status = mutant_dict.get("status", "unknown").lower()
+ patch_buf = generate_patch_with_mutant(file_path, mutant_dict)
+ if args.with_patches and patch_buf != "":
+ patch_file_name = "{:05d}-{}-{}.patch".format(mutant_idx, mutant_status, escaped_source_file_basename)
+ with open(patch_file_name, "w") as patch_file:
+ patch_file.write(patch_buf)
+
+ report_mutant_statuses = sum_statuses(files_mutant_statuses)
+
+ t = Template(template)
+ time = datetime.datetime.now()
+
+ report_data = render_template(json_data, report_mutant_statuses, template)
if args.report_path:
with open(args.report_path, "w") as report:
report.write(report_data)