Skip to content

Commit

Permalink
Merge pull request #23 from WiredNerd/dev
Browse files Browse the repository at this point in the history
🐩 new option fail_under
  • Loading branch information
WiredNerd committed Jan 4, 2024
2 parents 87da581 + bd6690c commit 76808b9
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 91 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "poodle"
description = "Mutation Testing Tool"
version = "1.2.2"
version = "1.3.0"
license = { file = "LICENSE" }
keywords = [
"test",
Expand Down
10 changes: 9 additions & 1 deletion src/poodle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@
from pathlib import Path
from typing import Any

__version__ = "1.2.2"
__version__ = "1.3.0"


class PoodleTestingFailedError(Exception):
"""Poodle testing failed."""


class PoodleNoMutantsFoundError(Exception):
"""Poodle could not find any mutants to test."""


class PoodleInputError(ValueError):
Expand Down
46 changes: 33 additions & 13 deletions src/poodle/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@

import click

from . import PoodleInputError, core
from . import (
PoodleInputError,
PoodleNoMutantsFoundError,
PoodleTestingFailedError,
PoodleTrialRunError,
__version__,
core,
)
from .config import build_config

CONTEXT_SETTINGS = {
Expand All @@ -27,7 +34,9 @@
@click.option("--report", help="Enable reporter by name. Multiple allowed.", multiple=True)
@click.option("--html", help="Folder name to store HTML report in.", type=click.Path(path_type=Path))
@click.option("--json", help="File to create with JSON report.", type=click.Path(path_type=Path))
def main(
@click.option("--fail_under", help="Fail if mutation score is under this value.", type=float)
@click.version_option(version=__version__)
def main( # noqa: C901, PLR0912
sources: tuple[Path],
config_file: Path | None,
quiet: int,
Expand All @@ -38,35 +47,46 @@ def main(
report: tuple[str],
html: Path | None,
json: Path | None,
fail_under: float | None,
) -> None:
"""Poodle Mutation Test Tool."""
try:
config = build_config(sources, config_file, quiet, verbose, workers, exclude, only, report, html, json)
config = build_config(
sources, config_file, quiet, verbose, workers, exclude, only, report, html, json, fail_under
)
except PoodleInputError as err:
click.echo(err.args)
for arg in err.args:
click.echo(arg)
sys.exit(4)

try:
core.main_process(config)
except PoodleTestingFailedError as err:
for arg in err.args:
click.echo(arg)
sys.exit(1)
except KeyboardInterrupt:
click.echo("Aborted due to Keyboard Interrupt!")
sys.exit(2)
except PoodleTrialRunError as err:
for arg in err.args:
click.echo(arg)
sys.exit(3)
except PoodleInputError as err:
for arg in err.args:
click.echo(arg)
sys.exit(4)
except PoodleNoMutantsFoundError as err:
for arg in err.args:
click.echo(arg)
sys.exit(5)
except: # noqa: E722
click.echo("Aborted due to Internal Error!")
click.echo(traceback.format_exc())
sys.exit(3)
sys.exit(0)


# pytest return codes
# Exit code 0: All tests were collected and passed successfully
# Exit code 1: Tests were collected and run but some of the tests failed
# Exit code 2: Test execution was interrupted by the user
# Exit code 3: Internal error happened while executing tests
# Exit code 4: pytest command line usage error
# Exit code 5: No tests were collected


# nomut: start
if __name__ == "__main__":
main()
24 changes: 24 additions & 0 deletions src/poodle/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def build_config( # noqa: PLR0913
cmd_report: tuple[str],
cmd_html: Path | None,
cmd_json: Path | None,
cmd_fail_under: float | None,
) -> PoodleConfig:
"""Build PoodleConfig object."""
config_file_path = get_config_file_path(cmd_config_file)
Expand Down Expand Up @@ -118,6 +119,7 @@ def build_config( # noqa: PLR0913
runner_opts=get_dict_from_config("runner_opts", config_file_data),
reporters=get_reporters(config_file_data, cmd_report, cmd_html, cmd_json),
reporter_opts=get_dict_from_config("reporter_opts", config_file_data, command_line=cmd_reporter_opts),
fail_under=get_float_from_config("fail_under", config_file_data, command_line=cmd_fail_under),
)


Expand Down Expand Up @@ -414,6 +416,28 @@ def get_int_from_config(
raise PoodleInputError(msg) from None


def get_float_from_config(
option_name: str,
config_data: dict,
default: float | None = None,
command_line: float | None = None,
) -> float | None:
"""Retrieve Config Option that should be an float or None.
Retrieve highest priority value from config sources.
"""
value, source = get_option_from_config(option_name=option_name, config_data=config_data, command_line=command_line)

if value is None:
return default

try:
return float(value)
except ValueError:
msg = f"{option_name} from {source} must be a valid float"
raise PoodleInputError(msg) from None


def get_str_from_config(
option_name: str,
config_data: dict,
Expand Down
59 changes: 28 additions & 31 deletions src/poodle/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
import logging
import shutil

import click

from . import PoodleInputError, PoodleTrialRunError, __version__
from . import PoodleNoMutantsFoundError, PoodleTestingFailedError, __version__
from .data_types import PoodleConfig, PoodleWork
from .mutate import create_mutants_for_all_mutators, initialize_mutators
from .report import generate_reporters
Expand All @@ -19,42 +17,41 @@

def main_process(config: PoodleConfig) -> None:
"""Poodle core run process."""
try:
work = PoodleWork(config) # sets logging defaults
print_header(work)
logger.info("\n%s", pprint_str(config))
work = PoodleWork(config) # sets logging defaults
print_header(work)
logger.info("\n%s", pprint_str(config))

if config.work_folder.exists():
logger.info("delete %s", config.work_folder)
shutil.rmtree(config.work_folder)

if config.work_folder.exists():
logger.info("delete %s", config.work_folder)
shutil.rmtree(config.work_folder)
create_temp_zips(work)

create_temp_zips(work)
work.mutators = initialize_mutators(work)
work.runner = get_runner(config)
work.reporters = list(generate_reporters(config))

work.mutators = initialize_mutators(work)
work.runner = get_runner(config)
work.reporters = list(generate_reporters(config))
mutants = create_mutants_for_all_mutators(work)
if not mutants:
raise PoodleNoMutantsFoundError("No mutants were found to test!")
work.echo(f"Identified {len(mutants)} mutants")

mutants = create_mutants_for_all_mutators(work)
work.echo(f"Identified {len(mutants)} mutants")
clean_run_results = clean_run_each_source_folder(work)
timeout = calc_timeout(config, clean_run_results)
results = run_mutant_trails(work, mutants, timeout)

clean_run_results = clean_run_each_source_folder(work)
timeout = calc_timeout(config, clean_run_results)
results = run_mutant_trails(work, mutants, timeout)
for trial in results.mutant_trials:
trial.mutant.unified_diff = create_unified_diff(trial.mutant)

for trial in results.mutant_trials:
trial.mutant.unified_diff = create_unified_diff(trial.mutant)
for reporter in work.reporters:
reporter(config=config, echo=work.echo, testing_results=results)

for reporter in work.reporters:
reporter(config=config, echo=work.echo, testing_results=results)
logger.info("delete %s", config.work_folder)
shutil.rmtree(config.work_folder)

logger.info("delete %s", config.work_folder)
shutil.rmtree(config.work_folder)
except PoodleInputError as err:
for arg in err.args:
click.echo(arg)
except PoodleTrialRunError as err:
for arg in err.args:
click.echo(arg)
if config.fail_under and results.summary.success_rate < config.fail_under / 100:
msg = f"Mutation score {results.summary.coverage_display} is below {config.fail_under:.2f}%"
raise PoodleTestingFailedError(msg)


poodle_header_str = r"""
Expand Down
2 changes: 2 additions & 0 deletions src/poodle/data_types/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class PoodleConfig:
reporters: list[str]
reporter_opts: dict

fail_under: float | None


@dataclass
class FileMutation:
Expand Down
5 changes: 5 additions & 0 deletions tests/data_types/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class PoodleConfigStub(PoodleConfig):
reporters: list[str] = None # type: ignore [assignment]
reporter_opts: dict = None # type: ignore [assignment]

fail_under: float | None = None


class TestPoodleConfig:
@staticmethod
Expand Down Expand Up @@ -81,6 +83,7 @@ def create_poodle_config():
runner_opts={"command_line": "pytest tests"},
reporters=["summary"],
reporter_opts={"summary": "value"},
fail_under=95.0,
)

def test_poodle_config(self):
Expand Down Expand Up @@ -119,6 +122,8 @@ def test_poodle_config(self):
assert config.reporters == ["summary"]
assert config.reporter_opts == {"summary": "value"}

assert config.fail_under == 95.0


class TestFileMutation:
@staticmethod
Expand Down
Loading

0 comments on commit 76808b9

Please sign in to comment.