def test_activation_record_created(operator, code): node = ast.parse(code) core = MutatingCore(0) op = operator(core) assert core.activation_record is None op.visit(node) assert core.activation_record is not None
def test_mutation_changes_ast(operator, code): node = ast.parse(code) core = MutatingCore(0) mutant = operator(core).visit(copy.deepcopy(node)) orig_nodes = linearize_tree(node) # noqa mutant_nodes = linearize_tree(mutant) # noqa # todo: disabled b/c adding/removing the not keyword # changes the number of nodes in the tree. # assert len(orig_nodes) == len(mutant_nodes) assert ast.dump(node) != ast.dump(mutant)
# This is just a simple example of how to inspect ASTs visually. # # This can be useful for developing new operators, etc. import ast from cosmic_ray.mutating import MutatingCore from cosmic_ray.operators.comparison_operator_replacement import MutateComparisonOperator code = "((x is not y) ^ (x is y))" node = ast.parse(code) print() print(ast.dump(node)) core = MutatingCore(0) operator = MutateComparisonOperator(core) new_node = operator.visit(node) print() print(ast.dump(new_node))
def test_no_mutation_leaves_ast_unchanged(operator, code): node = ast.parse(code) core = MutatingCore(-1) replacer = operator(core) assert ast.dump(node) == ast.dump(replacer.visit(copy.deepcopy(node)))
def worker(module_name, operator, occurrence, test_runner): """Mutate the OCCURRENCE-th site for OPERATOR_CLASS in MODULE_NAME, run the tests, and report the results. This is fundamentally the single-mutation-and-test-run process implementation. There are three high-level ways that a worker can finish. First, it could fail exceptionally, meaning that some uncaught exception made its way from some part of the operation to terminate the function. This function will intercept all exceptions and return it in a non-exceptional structure. Second, the mutation testing machinery may determine that there is no OCCURENCE-th instance for OPERATOR_NAME in the module under test. In this case there is no way to report a test result (i.e. killed, survived, or incompetent) so a special value is returned indicating that no mutation is possible. Finally, and hopefully normally, the worker will find that it can run a test. It will do so and report back the result - killed, survived, or incompetent - in a structured way. Args: module_name: The name of the module to be mutated operator: The operator be applied occurrence: The occurrence of the operator to apply test_runner: The test runner plugin to use Returns: A WorkItem Raises: This will generally not raise any exceptions. Rather, exceptions will be reported using the 'exception' result-type in the return value. """ try: # TODO: What should we be doing here? This feels too hacky. sys.path.insert(0, '') with preserve_modules(): module = importlib.import_module(module_name) module_source_file = inspect.getsourcefile(module) module_ast = get_ast(module) module_source = astunparse.unparse(module_ast) core = MutatingCore(occurrence) operator = operator(core) # note: after this step module_ast and modified_ast # appear to be the same modified_ast = operator.visit(module_ast) modified_source = astunparse.unparse(modified_ast) if not core.activation_record: return WorkItem(worker_outcome=WorkerOutcome.NO_TEST) # generate a source diff to visualize how the mutation # operator has changed the code module_diff = ["--- mutation diff ---"] for line in difflib.unified_diff(module_source.split('\n'), modified_source.split('\n'), fromfile="a" + module_source_file, tofile="b" + module_source_file, lineterm=""): module_diff.append(line) with using_ast(module_name, module_ast): item = test_runner() item.update({ 'diff': module_diff, 'worker_outcome': WorkerOutcome.NORMAL, 'occurrence': core.activation_record['occurrence'], 'line_number': core.activation_record['line_number'], }) return item except Exception: # noqa # pylint: disable=broad-except return WorkItem(data=traceback.format_exception(*sys.exc_info()), test_outcome=TestOutcome.INCOMPETENT, worker_outcome=WorkerOutcome.EXCEPTION)