commit 493b34178ab94af91370be67931f4961a736f19d from: Sergey Bronnikov date: Fri Dec 11 11:46:45 2020 UTC initial support of generating patches with mutants commit - c3bf1e83ca58b6e1c539af029942c9b49a3df6ef commit + 493b34178ab94af91370be67931f4961a736f19d blob - 71f14b87f91feebf8d675ca16719a501977b82b1 blob + d76abfb73d37abc848e9d08d869aae0da1bfd9a4 --- generate.py +++ generate.py @@ -14,6 +14,55 @@ STATUS_COMPILE_ERROR = "CompileError" 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, @@ -25,6 +74,8 @@ LIST_STATUSES = [STATUS_KILLED, SUPPORTED_SCHEMA_VERSIONS = ["1.0"] +INVALID_SYMBOLS = "<>:\"/\|?*. " + DEFAULT_TEXT_TEMPLATE = """ {% set files = json_data['files'] %} @@ -263,7 +314,14 @@ Generated by '{}'".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="", @@ -368,6 +532,8 @@ if __name__ == "__main__": 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): @@ -388,7 +554,32 @@ if __name__ == "__main__": 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)