Ejemplo n.º 1
0
def main(command, argument, argument2, paths_to_mutate, backup, runner,
         tests_dir, test_time_multiplier, test_time_base, swallow_output,
         use_coverage, dict_synonyms, cache_only, version, suspicious_policy,
         untested_policy, pre_mutation, post_mutation, use_patch_file):
    """return exit code, after performing an mutation test run.

    :return: the exit code from executing the mutation tests
    :rtype: int
    """
    if version:
        print("mutmut version %s" % __version__)
        return 0

    if use_coverage and use_patch_file:
        raise click.BadArgumentUsage(
            "You can't combine --use-coverage and --use-patch")

    valid_commands = ['run', 'results', 'apply', 'show', 'junitxml']
    if command not in valid_commands:
        raise click.BadArgumentUsage(
            '%s is not a valid command, must be one of %s' %
            (command, ', '.join(valid_commands)))

    if command == 'results' and argument:
        raise click.BadArgumentUsage('The %s command takes no arguments' %
                                     command)

    dict_synonyms = [x.strip() for x in dict_synonyms.split(',')]

    if command in ('show', 'diff'):
        if not argument:
            print_result_cache()
            return 0

        if argument == 'all':
            print_result_cache(show_diffs=True,
                               dict_synonyms=dict_synonyms,
                               print_only_filename=argument2)
            return 0

        print(get_unified_diff(argument, dict_synonyms))
        return 0

    if use_coverage and not exists('.coverage'):
        raise FileNotFoundError(
            'No .coverage file found. You must generate a coverage file to use this feature.'
        )

    if command == 'results':
        print_result_cache()
        return 0

    if command == 'junitxml':
        print_result_cache_junitxml(dict_synonyms, suspicious_policy,
                                    untested_policy)
        return 0

    if command == 'apply':
        do_apply(argument, dict_synonyms, backup)
        return 0

    paths_to_mutate = get_or_guess_paths_to_mutate(paths_to_mutate)

    if not isinstance(paths_to_mutate, (list, tuple)):
        paths_to_mutate = [x.strip() for x in paths_to_mutate.split(',')]

    if not paths_to_mutate:
        raise click.BadOptionUsage(
            '--paths-to-mutate',
            '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'
        )

    tests_dirs = []
    for p in tests_dir.split(':'):
        tests_dirs.extend(glob(p, recursive=True))

    for p in paths_to_mutate:
        for pt in tests_dir.split(':'):
            tests_dirs.extend(glob(p + '/**/' + pt, recursive=True))
    del tests_dir

    os.environ[
        'PYTHONDONTWRITEBYTECODE'] = '1'  # stop python from creating .pyc files

    using_testmon = '--testmon' in runner

    print("""
- Mutation testing starting -

These are the steps:
1. A full test suite run will be made to make sure we
   can run the tests successfully and we know how long
   it takes (to detect infinite loops for example)
2. Mutants will be generated and checked

Results are stored in .mutmut-cache.
Print found mutants with `mutmut results`.

Legend for output:
🎉 Killed mutants.   The goal is for everything to end up in this bucket.
⏰ Timeout.          Test suite took 10 times as long as the baseline so were killed.
🤔 Suspicious.       Tests took a long time, but not long enough to be fatal.
🙁 Survived.         This means your tests needs to be expanded.
""")
    baseline_time_elapsed = time_test_suite(swallow_output=not swallow_output,
                                            test_command=runner,
                                            using_testmon=using_testmon)

    if using_testmon:
        copy('.testmondata', '.testmondata-initial')

    # if we're running in a mode with externally whitelisted lines
    if use_coverage or use_patch_file:
        covered_lines_by_filename = {}
        if use_coverage:
            coverage_data = read_coverage_data()
        else:
            assert use_patch_file
            covered_lines_by_filename = read_patch_data(use_patch_file)
            coverage_data = None

        def _exclude(context):
            try:
                covered_lines = covered_lines_by_filename[context.filename]
            except KeyError:
                if coverage_data is not None:
                    covered_lines = coverage_data.lines(
                        os.path.abspath(context.filename))
                    covered_lines_by_filename[context.filename] = covered_lines
                else:
                    covered_lines = None

            if covered_lines is None:
                return True
            current_line = context.current_line_index + 1
            if current_line not in covered_lines:
                return True
            return False
    else:

        def _exclude(context):
            del context
            return False

    if command != 'run':
        raise click.BadArgumentUsage("Invalid command %s" % command)

    mutations_by_file = {}

    if argument is None:
        for path in paths_to_mutate:
            for filename in python_source_files(path, tests_dirs):
                update_line_numbers(filename)
                add_mutations_by_file(mutations_by_file, filename, _exclude,
                                      dict_synonyms)
    else:
        filename, mutation_id = filename_and_mutation_id_from_pk(int(argument))
        mutations_by_file[filename] = [mutation_id]

    total = sum(len(mutations) for mutations in mutations_by_file.values())

    print()
    print('2. Checking mutants')
    config = Config(
        swallow_output=not swallow_output,
        test_command=runner,
        exclude_callback=_exclude,
        baseline_time_elapsed=baseline_time_elapsed,
        backup=backup,
        dict_synonyms=dict_synonyms,
        total=total,
        using_testmon=using_testmon,
        cache_only=cache_only,
        tests_dirs=tests_dirs,
        hash_of_tests=hash_of_tests(tests_dirs),
        test_time_multiplier=test_time_multiplier,
        test_time_base=test_time_base,
        pre_mutation=pre_mutation,
        post_mutation=post_mutation,
    )

    try:
        run_mutation_tests(config=config, mutations_by_file=mutations_by_file)
    except Exception as e:
        traceback.print_exc()
        return compute_exit_code(config, e)
    else:
        return compute_exit_code(config)
    finally:
        print()  # make sure we end the output with a newline
Ejemplo n.º 2
0
def main(command, argument, argument2, paths_to_mutate, backup, runner,
         tests_dir, test_time_multiplier, test_time_base, swallow_output,
         use_coverage, dict_synonyms, cache_only, version, suspicious_policy,
         untested_policy, pre_mutation, post_mutation, use_patch_file,
         paths_to_exclude):
    """return exit code, after performing an mutation test run.

    :return: the exit code from executing the mutation tests
    :rtype: int
    """
    if version:
        print("mutmut version {}".format(__version__))
        return 0

    if use_coverage and use_patch_file:
        raise click.BadArgumentUsage(
            "You can't combine --use-coverage and --use-patch")

    valid_commands = [
        'run', 'results', 'apply', 'show', 'junitxml', 'html', 'update-cache'
    ]
    if command not in valid_commands:
        raise click.BadArgumentUsage(
            '{} is not a valid command, must be one of {}'.format(
                command, ', '.join(valid_commands)))

    if command == 'results' and argument:
        raise click.BadArgumentUsage(
            'The {} command takes no arguments'.format(command))

    dict_synonyms = [x.strip() for x in dict_synonyms.split(',')]

    if command in ('show', 'diff'):
        if not argument:
            print_result_cache()
            return 0

        if argument == 'all':
            print_result_cache(show_diffs=True,
                               dict_synonyms=dict_synonyms,
                               print_only_filename=argument2)
            return 0

        if os.path.isfile(argument):
            print_result_cache(show_diffs=True, only_this_file=argument)
            return 0

        print(get_unified_diff(argument, dict_synonyms))
        return 0

    if use_coverage and not exists('.coverage'):
        raise FileNotFoundError(
            'No .coverage file found. You must generate a coverage file to use this feature.'
        )

    if command == 'results':
        print_result_cache()
        return 0

    if command == 'junitxml':
        print_result_cache_junitxml(dict_synonyms, suspicious_policy,
                                    untested_policy)
        return 0

    if command == 'html':
        create_html_report(dict_synonyms)
        return 0

    if command == 'apply':
        do_apply(argument, dict_synonyms, backup)
        return 0

    if paths_to_mutate is None:
        paths_to_mutate = guess_paths_to_mutate()

    if not isinstance(paths_to_mutate, (list, tuple)):
        paths_to_mutate = [x.strip() for x in paths_to_mutate.split(',')]

    if not paths_to_mutate:
        raise click.BadOptionUsage(
            '--paths-to-mutate',
            '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'
        )

    tests_dirs = []
    for p in tests_dir.split(':'):
        tests_dirs.extend(glob(p, recursive=True))

    for p in paths_to_mutate:
        for pt in tests_dir.split(':'):
            tests_dirs.extend(glob(p + '/**/' + pt, recursive=True))
    del tests_dir
    current_hash_of_tests = hash_of_tests(tests_dirs)

    os.environ[
        'PYTHONDONTWRITEBYTECODE'] = '1'  # stop python from creating .pyc files

    using_testmon = '--testmon' in runner

    print("""
- Mutation testing starting -

These are the steps:
1. A full test suite run will be made to make sure we
   can run the tests successfully and we know how long
   it takes (to detect infinite loops for example)
2. Mutants will be generated and checked

Results are stored in .mutmut-cache.
Print found mutants with `mutmut results`.

Legend for output:
🎉 Killed mutants.   The goal is for everything to end up in this bucket.
⏰ Timeout.          Test suite took 10 times as long as the baseline so were killed.
🤔 Suspicious.       Tests took a long time, but not long enough to be fatal.
🙁 Survived.         This means your tests needs to be expanded.
🔇 Skipped.          Skipped.
""")
    baseline_time_elapsed = time_test_suite(
        swallow_output=not swallow_output,
        test_command=runner,
        using_testmon=using_testmon,
        current_hash_of_tests=current_hash_of_tests,
    )

    if hasattr(mutmut_config, 'init'):
        mutmut_config.init()

    if using_testmon:
        copy('.testmondata', '.testmondata-initial')

    # if we're running in a mode with externally whitelisted lines
    covered_lines_by_filename = None
    coverage_data = None
    if use_coverage or use_patch_file:
        covered_lines_by_filename = {}
        if use_coverage:
            coverage_data = read_coverage_data()
            check_coverage_data_filepaths(coverage_data)
        else:
            assert use_patch_file
            covered_lines_by_filename = read_patch_data(use_patch_file)

    if command != 'run' and command != 'update-cache':
        raise click.BadArgumentUsage("Invalid command {}".format(command))

    mutations_by_file = {}

    paths_to_exclude = paths_to_exclude or ''
    if paths_to_exclude:
        paths_to_exclude = [
            path.strip() for path in paths_to_exclude.split(',')
        ]

    config = Config(
        total=0,  # we'll fill this in later!
        swallow_output=not swallow_output,
        test_command=runner,
        covered_lines_by_filename=covered_lines_by_filename,
        coverage_data=coverage_data,
        baseline_time_elapsed=baseline_time_elapsed,
        backup=backup,
        dict_synonyms=dict_synonyms,
        using_testmon=using_testmon,
        cache_only=cache_only,
        tests_dirs=tests_dirs,
        hash_of_tests=current_hash_of_tests,
        test_time_multiplier=test_time_multiplier,
        test_time_base=test_time_base,
        pre_mutation=pre_mutation,
        post_mutation=post_mutation,
        paths_to_mutate=paths_to_mutate,
    )

    parse_run_argument(argument, config, dict_synonyms, mutations_by_file,
                       paths_to_exclude, paths_to_mutate, tests_dirs)
    if command == 'update-cache':
        print('Finished updating cash')
        return

    config.total = sum(
        len(mutations) for mutations in mutations_by_file.values())

    print()
    print('2. Checking mutants')
    progress = Progress(total=config.total)

    try:
        run_mutation_tests(config=config,
                           progress=progress,
                           mutations_by_file=mutations_by_file)
    except Exception as e:
        traceback.print_exc()
        return compute_exit_code(progress, e)
    else:
        return compute_exit_code(progress)
    finally:
        print()  # make sure we end the output with a newline
        # Close all active multiprocessing queues to avoid hanging up the main process
        close_active_queues()
def killed_mutants_raw(module_under_test_path,
                       project_root,
                       not_failing_node_ids,
                       timeout=None,
                       cache_key=None):
    logger.debug(
        "Killed mutants of project {proj} will be saved in folder {f}",
        proj=project_root,
        f=cache_location)
    test_time_multiplier = 2.0
    test_time_base = 0.0
    swallow_output = True
    cache_only = False,
    pre_mutation = None
    post_mutation = None
    backup = False

    save_working_dir = os.getcwd()
    os.chdir(project_root)

    os.environ[
        'PYTHONDONTWRITEBYTECODE'] = '1'  # stop python from creating .pyc files

    pytest_args = []
    pytest_args += not_failing_node_ids

    baseline_time_elapsed = time_test_suite(swallow_output=not swallow_output,
                                            test_command=pytest_args,
                                            working_dir=project_root,
                                            using_testmon=False)

    mutations: List[MutationID] = get_mutants_of(module_under_test_path)

    total = len(mutations)

    timed_out_mutants = set()
    killed = defaultdict(set)
    st = time.time()
    prog = 0
    for mutation_id in tqdm(mutations):
        prog += 1
        mut = mutation_id.line, mutation_id.index

        config = Config(
            swallow_output=not swallow_output,
            test_command=pytest_args,
            working_dir=project_root,
            baseline_time_elapsed=baseline_time_elapsed,
            backup=backup,
            dict_synonyms=[],
            total=total,
            using_testmon=False,
            cache_only=cache_only,
            tests_dirs=[],
            hash_of_tests=hash_of_tests([]),
            test_time_multiplier=test_time_multiplier,
            test_time_base=test_time_base,
            pre_mutation=pre_mutation,
            post_mutation=post_mutation,
            coverage_data=None,
            covered_lines_by_filename=None,
        )

        context = Context(
            mutation_id=mutation_id,
            filename=module_under_test_path,
            dict_synonyms=config.dict_synonyms,
            config=config,
        )

        try:
            mutate_file(backup=True, context=context)

            try:
                # those test cases that killed this mutant
                # they failed - which is good
                failed_test_cases_ids = tests_pass_expanded(
                    config=config, working_dir=project_root, callback=print)
            except TimeoutError:
                failed_test_cases_ids = None
            if failed_test_cases_ids is None:
                # This means test cases timed out
                # we can't figure out which ones exactly
                # so we don't use this mutant in statistics
                timed_out_mutants.add(mut)
            else:
                for test_case_id in failed_test_cases_ids:
                    killed[test_case_id].add(mut)

            if timeout and prog > 2 and prog < total / 2:
                time_per_item = (time.time() - st) / prog
                estimate = time_per_item * total
                if estimate > timeout * 1.1:
                    raise TimeoutError("It will take too much time")

        except Exception:
            raise
        finally:
            move(module_under_test_path + '.bak', module_under_test_path)
            os.chdir(save_working_dir)

    return killed, total