def do_apply(mutation_pk, dict_synonyms, backup): """Apply a specified mutant to the source code :param mutation_pk: mutmut cache primary key of the mutant to apply :type mutation_pk: str :param dict_synonyms: list of synonym keywords for a python dictionary :type dict_synonyms: list[str] :param backup: if :obj:`True` create a backup of the source file before applying the mutation :type backup: bool """ filename, mutation_id = filename_and_mutation_id_from_pk(int(mutation_pk)) update_line_numbers(filename) context = Context( mutation_id=mutation_id, filename=filename, dict_synonyms=dict_synonyms, ) mutate_file( backup=backup, context=context, )
def do_apply(mutation_pk, dict_synonyms, backup): """Apply a specified mutant to the source code :param mutation_pk: mutmut cache primary key of the mutant to apply :type mutation_pk: str :param dict_synonyms: list of synonym keywords for a python dictionary :type dict_synonyms: list[str] :param backup: if :obj:`True` create a backup of the source file before applying the mutation :type backup: bool """ filename, mutation_id = filename_and_mutation_id_from_pk(int(mutation_pk)) context = Context( mutation_id=mutation_id, filename=filename, dict_synonyms=dict_synonyms, ) mutate_file( backup=backup, context=context, ) if context.number_of_performed_mutations == 0: raise RuntimeError( 'No mutations performed. Are you sure the index is not too big?')
def run_mutation(config: Config, filename: str, mutation_id: MutationID, callback) -> str: """ :return: (computed or cached) status of the tested mutant, one of mutant_statuses """ context = Context( mutation_id=mutation_id, filename=filename, dict_synonyms=config.dict_synonyms, config=config, ) cached_status = cached_mutation_status(filename, mutation_id, config.hash_of_tests) if cached_status != UNTESTED: return cached_status if config.pre_mutation: result = subprocess.check_output(config.pre_mutation, shell=True).decode().strip() if result and not config.swallow_output: print(result) try: mutate_file(backup=True, context=context) start = time() try: survived = tests_pass(config=config, callback=callback) except TimeoutError: return BAD_TIMEOUT time_elapsed = time() - start if not survived and time_elapsed > config.test_time_base + ( config.baseline_time_elapsed * config.test_time_multipler): return OK_SUSPICIOUS if survived: return BAD_SURVIVED else: return OK_KILLED finally: move(filename + '.bak', filename) if config.post_mutation: result = subprocess.check_output(config.post_mutation, shell=True).decode().strip() if result and not config.swallow_output: print(result)
def run_mutation(config, filename, mutation_id): """ :type config: Config :type filename: str :type mutation_id: MutationID :return: (computed or cached) status of the tested mutant :rtype: str """ context = Context( mutation_id=mutation_id, filename=filename, exclude=config.exclude_callback, dict_synonyms=config.dict_synonyms, config=config, ) cached_status = cached_mutation_status(filename, mutation_id, config.hash_of_tests) if cached_status == BAD_SURVIVED: config.surviving_mutants += 1 elif cached_status == BAD_TIMEOUT: config.surviving_mutants_timeout += 1 elif cached_status == OK_KILLED: config.killed_mutants += 1 elif cached_status == OK_SUSPICIOUS: config.suspicious_mutants += 1 else: assert cached_status == UNTESTED, cached_status config.print_progress() if cached_status != UNTESTED: return cached_status try: number_of_mutations_performed = mutate_file(backup=True, context=context) assert number_of_mutations_performed start = time() try: survived = tests_pass(config) except TimeoutError: context.config.surviving_mutants_timeout += 1 return BAD_TIMEOUT time_elapsed = time() - start if time_elapsed > config.test_time_base + ( config.baseline_time_elapsed * config.test_time_multipler): config.suspicious_mutants += 1 return OK_SUSPICIOUS if survived: context.config.surviving_mutants += 1 return BAD_SURVIVED else: context.config.killed_mutants += 1 return OK_KILLED finally: move(filename + '.bak', filename)
def main(paths_to_mutate, apply, mutation, backup, runner, tests_dir, s, use_coverage, dict_synonyms, show_times): if paths_to_mutate is None: # Guess path with code this_dir = os.getcwd().split(os.sep)[-1] if isdir('lib'): paths_to_mutate = 'lib' elif isdir('src'): paths_to_mutate = 'src' elif isdir(this_dir): paths_to_mutate = this_dir else: print( 'Could not figure out where the code to mutate is. Please specify it on the command line like "mutmut code_dir" or by adding "paths_to_mutate=code_dir" in setup.cfg under the section [mutmut]' ) return if not isinstance(paths_to_mutate, (list, tuple)): paths_to_mutate = [x.strip() for x in paths_to_mutate.split(',')] dict_synonyms = [x.strip() for x in dict_synonyms.split(',')] if not paths_to_mutate: print( 'You must specify a list of paths to mutate. Either as a command line argument, or by setting paths_to_mutate under the section [mutmut] in setup.cfg' ) return os.environ['PYTHONDONTWRITEBYTECODE'] = '1' if apply: assert mutation is not None assert len(paths_to_mutate) == 1 mutations_performed = mutate_file(backup, mutation, paths_to_mutate[0]) if mutations_performed == 0: print( 'ERROR: no mutations performed. Are you sure the index is not too big?' ) return null_stdout = open(os.devnull, 'w') if not s else None null_stderr = open(os.devnull, 'w') if not s else None test_command = '%s %s' % (runner, tests_dir) using_testmon = '--testmon' in test_command def run_tests(): if using_testmon: copy('.testmondata-initial', '.testmondata') check_call(test_command, shell=True, stdout=null_stdout, stderr=null_stderr) start_time = datetime.now() try: check_output(test_command, shell=True) baseline_time_elapsed = datetime.now() - start_time except CalledProcessError as e: if using_testmon and e.returncode == 5: baseline_time_elapsed = datetime.now() - start_time else: print( "Tests don't run cleanly without mutations. Test command was: %s" % test_command) print(e.output.decode()) return if using_testmon: copy('.testmondata', '.testmondata-initial') coverage_data = None if use_coverage: print('Using coverage data from .coverage file') # noinspection PyPackageRequirements import coverage coverage_data = coverage.CoverageData() coverage_data.read_file('.coverage') def exclude(context): if use_coverage: measured_lines = coverage_data.lines( os.path.abspath(context.filename)) if measured_lines is None: return True if context.current_line not in measured_lines: return True return False mutations_by_file = {} for path in paths_to_mutate: for filename in python_source_files(path): mutations_by_file[filename] = count_mutations( open(filename).read(), context__filename=filename, context__exclude=exclude) total = sum(mutations_by_file.values()) print('--- starting mutation ---') progress = 0 for filename, mutations in mutations_by_file.items(): for mutation_index in range(mutations): if mutation is not None and mutation != mutation_index: continue start_time = datetime.now() progress += 1 print_status('%s out of %s (file: %s, mutation: %s)' % (progress, total, filename, mutation_index)) try: apply_line = 'mutmut %s --mutation %s --apply' % ( filename, mutation_index) assert mutate_file( backup=True, mutation=mutation_index, filename=filename, context__exclude=exclude, context__dict_synonyms=dict_synonyms, ) try: run_tests() print_status('') time_elapsed = (datetime.now() - start_time) print('\rFAILED: %s' % apply_line) except CalledProcessError as e: if using_testmon and e.returncode == 5: print( '\rFAILED (all tests skipped, uncovered line?): %s' % apply_line) time_elapsed = (datetime.now() - start_time) if time_elapsed > baseline_time_elapsed * 2: print('\nSUSPICIOUS LONG TIME: %s > expected %s (%s)' % (time_elapsed, baseline_time_elapsed, apply_line)) os.remove(filename) try: os.remove(filename + 'c') # remove .pyc file except OSError: pass finally: try: move(filename + '.bak', filename) if show_times: print('time: %s' % time_elapsed) except IOError: pass