def test_MutateAST_visit_index_neg( i_order, lineno, col_offset, mut, index_file, index_expected_locs ): """Test mutation for Index: i[0], i[1], i[-1].""" tree = get_ast_from_src(index_file) test_mutation = mut testing_tree = deepcopy(tree) mutated_tree = MutateAST(target_idx=index_expected_locs[i_order], mutation=test_mutation).visit( testing_tree ) mast = MutateAST(readonly=True) mast.visit(mutated_tree) assert len(mast.locs) == 4 for l in mast.locs: # spot check on mutation from Index_NumNeg to Index_NumPos if l.lineno == lineno and l.col_offset == col_offset: assert l.op_type == test_mutation # spot check on not-mutated location still being None if l.lineno == 4 and l.col_offset == 23: assert l.op_type == "Index_NumPos"
def test_MutateAST_visit_nameconst(nameconst_file, nameconst_expected_locs): """Test mutation for nameconst: True, False, None.""" tree = get_ast_from_src(nameconst_file) test_mutation = False testing_tree = deepcopy(tree) mutated_tree = MutateAST(target_idx=nameconst_expected_locs[0], mutation=test_mutation).visit( testing_tree ) mast = MutateAST(readonly=True) mast.visit(mutated_tree) # if statement is included with this file that will be picked up nc_locs = [l for l in mast.locs if l.ast_class == "NameConstant"] assert len(nc_locs) == 4 for l in nc_locs: # spot check on mutation from True to False if l.lineno == 1 and l.col_offset == 14: assert l.op_type == test_mutation # spot check on not-mutated location still being None if l.lineno == 7 and l.col_offset == 22: assert l.op_type is None
def test_MutateAST_visit_binop_37(binop_file): """Read only test to ensure locations are aggregated.""" tree = Genome(binop_file).ast # Py 3.7 vs. Py 3.8 end_lineno = None if sys.version_info < (3, 8) else 6 end_col_offset = None if sys.version_info < (3, 8) else 17 test_idx = LocIndex( ast_class="BinOp", lineno=6, col_offset=11, op_type=ast.Add, end_lineno=end_lineno, end_col_offset=end_col_offset, ) test_mutation = ast.Pow # apply the mutation to the original tree copy testing_tree = deepcopy(tree) mutated_tree = MutateAST(target_idx=test_idx, mutation=test_mutation).visit(testing_tree) # revisit in read-only mode to gather the locations of the new nodes mast = MutateAST(readonly=True) mast.visit(mutated_tree) # four locations from the binary operations in binop_file assert len(mast.locs) == 4 # locs is an unordered set, cycle through to thd target and check the mutation for l in mast.locs: if (l.lineno == 6 and l.col_offset == 11 and l.end_lineno == end_lineno and l.end_col_offset == end_col_offset): assert l.op_type == test_mutation
def test_MutateAST_visit_if(if_file, if_expected_locs): """Test mutation for nameconst: True, False, None.""" tree = get_ast_from_src(if_file) test_mutation = "If_True" testing_tree = deepcopy(tree) # change from If_Statement to If_True mutated_tree = MutateAST(target_idx=if_expected_locs[0], mutation=test_mutation).visit( testing_tree ) mast = MutateAST(readonly=True) mast.visit(mutated_tree) # named constants will also be picked up, filter just to if_ operations if_locs = [l for l in mast.locs if l.ast_class == "If"] assert len(if_locs) == 4 for l in if_locs: # spot check on mutation from True to False if l.lineno == 2 and l.col_offset == 4: print(l) assert l.op_type == test_mutation # spot check on not-mutated location still being None if l.lineno == 13 and l.col_offset == 4: assert l.op_type == "If_False"
def test_MutateAST_visit_read_only(binop_file): """Read only test to ensure locations are aggregated.""" tree = get_ast_from_src(binop_file) mast = MutateAST(readonly=True) testing_tree = deepcopy(tree) mast.visit(testing_tree) # four locations from the binary operations in binop_file assert len(mast.locs) == 4 # tree should be unmodified assert ast.dump(tree) == ast.dump(testing_tree)
def get_mutation_targets(tree: ast.Module, src_file: Path) -> Set[LocIndex]: """Run the mutatest AST search with no targets or mutations to bring back target indicies. Args: tree: the source file AST src_file: source file name, used in logging Returns: Set of potential mutatest targets within AST """ ro_mast = MutateAST(target_idx=None, mutation=None, readonly=True, src_file=src_file) ro_mast.visit(tree) return ro_mast.locs
def targets(self) -> Set[LocIndex]: """Viable mutation targets within the AST of the ``source_file``. This is cached locally and updated if the source_file is changed. Filtering is not cached and applies any time the ``filter_codes`` are changed. Returns: The set of the location index objects from the transformer that could be potential mutation targets. """ if self._targets is None: ro_mast = MutateAST( target_idx=None, mutation=None, readonly=True, src_file=self.source_file ) ro_mast.visit(self.ast) self._targets = ro_mast.locs return CategoryCodeFilter(codes=self.filter_codes).filter(self._targets)
def test_MutateAST_visit_binop(binop_file): """Read only test to ensure locations are aggregated.""" tree = get_ast_from_src(binop_file) test_idx = LocIndex(ast_class="BinOp", lineno=6, col_offset=11, op_type=ast.Add) test_mutation = ast.Pow # apply the mutation to the original tree copy testing_tree = deepcopy(tree) mutated_tree = MutateAST(target_idx=test_idx, mutation=test_mutation).visit(testing_tree) # revisit in read-only mode to gather the locations of the new nodes mast = MutateAST(readonly=True) mast.visit(mutated_tree) # four locations from the binary operations in binop_file assert len(mast.locs) == 4 # locs is an unordered set, cycle through to thd target and check the mutation for l in mast.locs: if l.lineno == 6 and l.col_offset == 11: assert l.op_type == test_mutation
def test_MutateAST_visit_augassign(augassign_file, augassign_expected_locs): """Test mutation for AugAssign: +=, -=, /=, *=.""" tree = Genome(augassign_file).ast test_mutation = "AugAssign_Div" testing_tree = deepcopy(tree) mutated_tree = MutateAST(target_idx=augassign_expected_locs[0], mutation=test_mutation).visit(testing_tree) mast = MutateAST(readonly=True) mast.visit(mutated_tree) assert len(mast.locs) == 4 for l in mast.locs: # spot check on mutation from Add tp Div if l.lineno == 1 and l.col_offset == 4: assert l.op_type == test_mutation # spot check on not-mutated location still being Mult if l.lineno == 5 and l.col_offset == 4: assert l.op_type == "AugAssign_Mult"
def test_MutateAST_visit_boolop(boolop_file, boolop_expected_loc): """Test mutation of AND to OR in the boolop.""" tree = Genome(boolop_file).ast test_mutation = ast.Or # apply the mutation to the original tree copy testing_tree = deepcopy(tree) mutated_tree = MutateAST(target_idx=boolop_expected_loc, mutation=test_mutation).visit(testing_tree) # revisit in read-only mode to gather the locations of the new nodes mast = MutateAST(readonly=True) mast.visit(mutated_tree) # four locations from the binary operations in binop_file assert len(mast.locs) == 1 # there will only be one loc, but this still works # basedon the col and line offset in the fixture for compare_expected_loc for l in mast.locs: if l.lineno == 2 and l.col_offset == 11: assert l.op_type == test_mutation
def test_MutateAST_visit_compare(idx, mut_op, lineno, compare_file, compare_expected_locs): """Test mutation of the == to != in the compare op.""" tree = Genome(compare_file).ast # apply the mutation to the original tree copy testing_tree = deepcopy(tree) mutated_tree = MutateAST(target_idx=compare_expected_locs[idx], mutation=mut_op).visit( testing_tree ) # revisit in read-only mode to gather the locations of the new nodes mast = MutateAST(readonly=True) mast.visit(mutated_tree) assert len(mast.locs) == 3 # check that the lineno marked for mutation is changed, otherwise original ops should # still be present without modification for loc in mast.locs: if loc.lineno == lineno and loc.col_offset == 11: assert loc.op_type == mut_op else: assert loc.op_type in {ast.Eq, ast.Is, ast.In} # based on compare_file fixture
def create_mutant(tree: ast.Module, src_file: str, target_idx: LocIndex, mutation_op: Any) -> Mutant: """Create a mutatest in the AST of src_file at sample_idx and update the cache. Args: tree: AST for the source file src_file: source file location on disk target_idx: the location to make the mutatest mutation_op: the mutatest to apply Returns: The mutant, and creates the cache file of the mutatest """ # mutate ast and create code binary mutant_ast = MutateAST(target_idx=target_idx, mutation=mutation_op, src_file=src_file, readonly=False).visit(tree) mutant_code = compile(mutant_ast, str(src_file), "exec") # get cache file locations and create directory if needed cfile = get_cache_file_loc(src_file) # generate cache file pyc machinery for writing the cache file loader = importlib.machinery.SourceFileLoader("<py_compile>", src_file) # type: ignore source_stats = loader.path_stats(src_file) mode = importlib._bootstrap_external._calc_mode(src_file) # type: ignore # create the cache files with the mutatest mutant = Mutant( mutant_code=mutant_code, src_file=Path(src_file), cfile=Path(cfile), loader=loader, source_stats=source_stats, mode=mode, src_idx=target_idx, mutation=mutation_op, ) return mutant
def test_MutateAST_visit_subscript(slice_file, slice_expected_locs): """Test Slice references within subscript.""" tree = Genome(slice_file).ast mast = MutateAST(readonly=True) mast.visit(tree) assert len(mast.locs) == len(slice_expected_locs) test_mutation = "Slice_UNegToZero" mutated_tree = MutateAST(target_idx=slice_expected_locs[2], mutation=test_mutation).visit(tree) mast.visit(mutated_tree) assert len(mast.locs) == len(slice_expected_locs) for loc in mast.locs: if loc.lineno == 5 and loc.col_offset == 15: assert loc.op_type == test_mutation # test one unmodified location if loc.lineno == 4 and loc.col_offset == 14: assert loc.op_type == "Slice_UnboundUpper"
def test_MutateAST_visit_subscript(slice_file, slice_expected_locs): """Test Slice references within subscript.""" tree = get_ast_from_src(slice_file) mast = MutateAST(readonly=True) mast.visit(tree) assert len(mast.locs) == len(slice_expected_locs) test_mutation = "Slice_UNegToZero" # loc index 3 is the Slice_NegShrink operation in the fixture mutated_tree = MutateAST(target_idx=slice_expected_locs[3], mutation=test_mutation).visit(tree) mast.visit(mutated_tree) assert len(mast.locs) == len(slice_expected_locs) for l in mast.locs: if l.lineno == 5 and l.col_offset == 15: assert l.op_type == test_mutation # test one unmodified location if l.lineno == 4 and l.col_offset == 14: assert l.op_type == "Slice_UnboundUpper"
def mutate(self, target_idx: LocIndex, mutation_op: Any, write_cache: bool = False) -> Mutant: """Create a mutant from a single LocIndex that is in the Genome. Mutation_op must be a valid mutation for the target_idx operation code type. Optionally, use write_cache to write the mutant to ``__pycache__`` based on the detected location at the time of creation. The Genome AST is unmodified by mutate. Args: target_idx: the target location index (member of .targets) mutation_op: the mutation operation to use write_cache: optional flag to write to ``__pycache__`` Returns: The mutant definition Raises: MutationException: if ``mutation_op`` is not a valid mutation for the location index. TypeError: if the source_file property is not set on the Genome. ValueError: if the target_idx is not a member of Genome targets. """ op_code = CATEGORIES[target_idx.ast_class] valid_mutations = CategoryCodeFilter(codes=(op_code, )).valid_mutations if mutation_op not in valid_mutations: raise MutationException( f"{mutation_op} is not a member of mutation category {op_code}.\n" f"Valid mutations for {op_code}: {valid_mutations}.") if not self.source_file: raise TypeError("Source_file is set to NoneType") if target_idx not in self.targets: raise ValueError(f"{target_idx} is not in the Genome targets.") mutant_ast = MutateAST( target_idx=target_idx, mutation=mutation_op, src_file=self.source_file, readonly=False).visit( deepcopy( self.ast) # deepcopy to avoid in-place modification of AST ) # generate cache file pyc machinery for writing the __pycache__ file loader = importlib.machinery.SourceFileLoader( # type: ignore "<py_compile>", self.source_file) # create the cache files with the mutated AST mutant = Mutant( mutant_code=compile(mutant_ast, str(self.source_file), "exec"), src_file=Path(self.source_file), cfile=Path(cache.get_cache_file_loc(self.source_file)), loader=loader, source_stats=loader.path_stats(self.source_file), mode=importlib._bootstrap_external._calc_mode( self.source_file), # type: ignore src_idx=target_idx, mutation=mutation_op, ) if write_cache: mutant.write_cache() return mutant