Skip to content

Commit

Permalink
Merge pull request #19 from WiredNerd/dev
Browse files Browse the repository at this point in the history
πŸŽ‰ 🐩 New HTML Reporter
  • Loading branch information
WiredNerd authored Dec 30, 2023
2 parents cf230fa + 27e4cfa commit 15dc56a
Show file tree
Hide file tree
Showing 34 changed files with 1,839 additions and 76 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/poodle-mutation-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@ jobs:
- name: poodle
run: |
pytest --cov=src --cov-context=test
poodle -w 8
poodle
- name: Upload Report HTML
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: Mutation testing report HTML
path: mutation_reports
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ cover/
.ruff*
.poodle*
cov-html/
mutation_reports/

# Translations
*.mo
Expand Down
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@
"wcmatch",
"wirednerd"
],
"scss.format.preserveNewLines": false,
"scss.format.newlineBetweenSelectors": false,
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![PyPI - License](https://img.shields.io/pypi/l/poodle)](https://github.com/WiredNerd/poodle/blob/main/LICENSE)

[![Code Coverage](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FWiredNerd%2Fpoodle%2Fmain%2Fcode-coverage.json&query=%24.totals.percent_covered_display&suffix=%25&label=Code%20Coverage&color=teal&logo=pytest&logoColor=green)](https://pytest-cov.readthedocs.io)
[![Mutation Coverage](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FWiredNerd%2Fpoodle%2Fmain%2Fmutation-testing-report.json&query=%24.summary.coverage_display&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTM1Ljc1bW0iIGhlaWdodD0iMTE4Ljc1bW0iIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDEzNS43NSAxMTguNzUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI%2BCiA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDAuOTg4IC01Ni43NjUpIiBzdHJva2U9IiMwMDAiPgogIDxwYXRoIGQ9Im02Ny42NDMgMTU3LjE4Yy04LjUwMi00LjU1NjMtMTUuNzM3LTEyLjA5Ny0xNy43NjgtMjEuNzY4LTMuMDE0OC0xMi40MzEtMi44MDAxLTI1LjQ3LTEuMTU0Mi0zOC4wNzcgMS43Mzc5LTExLjA0NCAxMC44NzktMTguNzAzIDE5LjIzNy0yNS4xNCA3Ljk1NjItNS4zNTg3IDE2Ljc3Mi0xMC4wNzUgMjYuMjE3LTExLjk1NyAxMS4zNzctMS4zNjgxIDI0Ljk5NC0xLjMzODYgMzMuMTY0IDguMDQyOSAzLjk4MjQgNC4yMTQ0IDUuNTI1OCAxMC4wMzEgNC45NjY3IDE1LjcwMS0wLjAzODkgNy4xNTQgMi40MjM2IDE1LjIxMyA5LjQwOTMgMTguNDU1IDkuMTIxNSA1LjIyODUgMjAuMTgxIDQuNDEwNiAyOS42NzUgOC42MTg3IDUuNzA5IDUuODg3NS0yLjAzNzEgMTMuMDI4LTQuMDcxNyAxOC44NTktMi43MDMzIDcuNDQxNS0xMC4zMTcgOS44Njg2LTE3LjAxIDEyLjM1Ny0xMy4wMzYgNS4xNy0yNC44NDcgNy43MDExLTQxLjUxMSAxMS4yNDYtMjIuMDk5IDUuNTk4NS0zMC43MzMgNS45NjU2LTQxLjE1NCAzLjY2MjN6IiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjkiLz4KICA8Zz4KICAgPHBhdGggZD0ibTE2Mi42NyAxMzAuNzJjLTUuNDczNCAwLjU3NjYtMTEuMTc2LTkuODU5MS0xMC43NDgtMTUuNzc3IDEuNjYwMy03Ljk1NCA2Ljc3NDUtMi4wMDg5IDguOTc0NS0zLjE4NDkgMi4wMjAyLTEuMDcxIDQuMDg0MS0xLjMyNjIgNi4yMTE2LTAuMzkzMzIgNC44MzAyIDIuNzA4NyAwLjkxNDE1IDEwLjI2LTEuMDk3NyAxMy45ODV6IiBzdHJva2Utd2lkdGg9IjkiIGZpbGw9IiMwMDAiLz4KICAgPGVsbGlwc2UgY3g9IjExNy4xMSIgY3k9IjEwNi4wNyIgcng9IjEwLjc1IiByeT0iNi40MTEzIiBzdHJva2Utd2lkdGg9IjkiIGZpbGw9IiMwMDAiLz4KICAgPHBhdGggZD0ibTgxLjAyMiA4Ny41M2M1LjU0OTEgMi40MjY3IDQuODU2MiAyLjcxMzMgNC43NDY3IDcuMjgwMS0wLjA5MTkgMTMuNDMgNS4xNzE0IDI2Ljc2IDQuODE1NSA0MC42NzggMC4xMjQ5IDYuMDMxMS0xLjA0MTkgMTAuMjA1LTMuNjk1NiAxNy41MTYtNS45MjY5IDIuNzI3My0xMi41NjkgNS40ODE0LTE5LjI0NSA0LjE3MzgtNi45NjUzLTQuOTI2OC0xMy4yMTUtNy40NDMzLTE2LjQwMS0xNy4zODctMy40MDgzLTE0LjU1OC01Ljc0MjYtMzIuMTI2LTEuMzgwNS00NS4zNTkgMS41MTItNy42OTEyIDI4LjMyMi04LjEzMTkgMzEuMTU5LTYuOTAyNCIgc3Ryb2tlLXdpZHRoPSI5IiBmaWxsPSIjMDAwIi8%2BCiAgPC9nPgogPC9nPgo8L3N2Zz4%3D&label=Mutation%20Coverage&color=3A438C)](https://poodle.readthedocs.io/)
[![Mutation Coverage](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2FWiredNerd%2Fpoodle%2Fmain%2Fmutation-testing-report.json&query=%24.summary.coverage_display&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMTYuOTMzbW0iIGhlaWdodD0iMTMuNTNtbSIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTYuOTMzIDEzLjUzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLXdpZHRoPSIxLjEyODYiPjxwYXRoIGQ9Im0zLjEwMDIgMTIuODA0Yy0xLjA2NjItMC41NzEzOC0xLjk3MzUtMS41MTctMi4yMjgyLTIuNzI5OC0wLjM3ODA3LTEuNTU4OS0wLjM1MTE1LTMuMTk0MS0wLjE0NDc0LTQuNzc1IDAuMjE3OTQtMS4zODUgMS4zNjQzLTIuMzQ1NCAyLjQxMjQtMy4xNTI3IDAuOTk3NzQtMC42NzIwMSAyLjEwMzMtMS4yNjM1IDMuMjg3Ny0xLjQ5OTUgMS40MjY3LTAuMTcxNTcgMy4xMzQ0LTAuMTY3ODcgNC4xNTg5IDEuMDA4NiAwLjQ5OTQxIDAuNTI4NTEgMC42OTI5NiAxLjI1NzkgMC42MjI4NSAxLjk2OS0wLjAwNDkgMC44OTcxNCAwLjMwMzkzIDEuOTA3OCAxLjE4IDIuMzE0MyAxLjE0MzkgMC42NTU2OCAyLjUzMDggMC41NTMxMSAzLjcyMTQgMS4wODA4IDAuNzE1OTQgMC43MzgzMi0wLjI1NTQ2IDEuNjMzOC0wLjUxMDYxIDIuMzY1LTAuMzM5MDEgMC45MzMyLTEuMjkzOCAxLjIzNzYtMi4xMzMxIDEuNTQ5Ni0xLjYzNDggMC42NDgzNC0zLjExNTkgMC45NjU3NS01LjIwNTcgMS40MTAzLTIuNzcxMyAwLjcwMjA4LTMuODU0MSAwLjc0ODExLTUuMTYwOSAwLjQ1OTI3eiIgZmlsbD0iIzM3NzZiNSIvPjxwYXRoIGQ9Im0xNS4wMTcgOS40ODYyYy0wLjY4NjM5IDAuMDcyMzA4LTEuNDAxNS0xLjIzNjQtMS4zNDc4LTEuOTc4NSAwLjIwODIxLTAuOTk3NDcgMC44NDk1NS0wLjI1MTkzIDEuMTI1NC0wLjM5OTQgMC4yNTMzNC0wLjEzNDMxIDAuNTEyMTYtMC4xNjYzMSAwLjc3ODk2LTAuMDQ5MzI0IDAuNjA1NzMgMC4zMzk2OCAwLjExNDY0IDEuMjg2Ny0wLjEzNzY2IDEuNzUzOHoiLz48ZWxsaXBzZSBjeD0iOS4zMDM2IiBjeT0iNi4zOTUiIHJ4PSIxLjM0ODEiIHJ5PSIuODA0MDEiLz48L2c+PHBhdGggZD0ibTQuNzc3OSA0LjA3YzAuNjk1ODggMC4zMDQzMiAwLjYwODk5IDAuMzQwMjYgMC41OTUyNiAwLjkxMjk2LTAuMDExNTI1IDEuNjg0MiAwLjY0ODUyIDMuMzU1OCAwLjYwMzg5IDUuMTAxMiAwLjAxNTY2MyAwLjc1NjMzLTAuMDY2MTc3IDAuODQ3NzMtMC4zOTg5NiAxLjc2NDYtMC43NDMyNiAwLjM0MjAyLTEuNDQwOCAwLjE1ODY0LTIuMjc4LTAuMDA1MzM4LTAuODczNDgtMC42MTc4NC0xLjAxODktMC43NTkzMi0xLjQxODQtMi4wMDYzLTAuMzI0MjUtMS44NjQzLTAuMzIwMzYtMy4yOTM3LTAuMDgyODQ2LTQuOTU5NiAwLjE4OTYxLTAuOTY0NTEgMi42MjMyLTAuOTYxNzQgMi45Nzg5LTAuODA3NTYiIGZpbGw9IiMzNzNjNDMiIHN0cm9rZT0iIzM3M2M0MyIgc3Ryb2tlLXdpZHRoPSIxLjEyODYiLz48L3N2Zz4=&label=Mutation%20Coverage&color=3776b5)](https://poodle.readthedocs.io/)
[![Documentation](https://img.shields.io/badge/Read%20the%20Docs-Poodle-blue?logo=readthedocs&logoColor=FFF)](https://poodle.readthedocs.io/)

[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
Expand Down
11 changes: 8 additions & 3 deletions 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.1.5"
version = "1.2.0"
license = { file = "LICENSE" }
keywords = [
"test",
Expand All @@ -28,7 +28,12 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = ["click>=8", "wcmatch>=8.5", "tomli>=2; python_version<'3.11'"]
dependencies = [
"click>=8",
"wcmatch>=8.5",
"tomli>=2; python_version<'3.11'",
"jinja2>=3.0.3",
]

[project.urls]
"Documentation" = "https://poodle.readthedocs.io/"
Expand All @@ -53,7 +58,7 @@ filterwarnings = [

[tool.poodle]
file_filters = ["test_*.py", "*_test.py", 'cli.py', 'run.py', '__init__.py']
# reporters = ["summary", "not_found", "json"]
reporters = ["summary", "html"]

[tool.poodle.runner_opts]
command_line = "pytest -x --assert=plain -o pythonpath='{PYTHONPATH}' --sort-mode=mutcov"
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
click>=8
tomli>=2
wcmatch>=8.5
jinja2>=3.0.3
pysass

pytest
pytest-sort>=1.3.0
Expand Down
2 changes: 1 addition & 1 deletion src/poodle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from pathlib import Path
from typing import Any

__version__ = "1.1.5"
__version__ = "1.2.0"


class PoodleInputError(ValueError):
Expand Down
27 changes: 26 additions & 1 deletion src/poodle/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def build_config( # noqa: PLR0913
"""Build PoodleConfig object."""
config_file_path = get_config_file_path(cmd_config_file)
config_file_data = get_config_file_data(config_file_path)
project_name, project_version = get_project_info(config_file_path)

log_format = get_str_from_config("log_format", config_file_data, default=default_log_format)
log_level: int | str = get_any_from_config(
Expand All @@ -77,6 +78,8 @@ def build_config( # noqa: PLR0913
reporters += [reporter for reporter in cmd_report if reporter not in reporters]

return PoodleConfig(
project_name=get_str_from_config("project_name", config_file_data, default=project_name),
project_version=get_str_from_config("project_version", config_file_data, default=project_version),
config_file=config_file_path,
source_folders=get_source_folders(cmd_sources, config_file_data),
only_files=get_str_list_from_config("only_files", config_file_data, default=[], command_line=cmd_only_files),
Expand Down Expand Up @@ -190,13 +193,35 @@ def get_config_file_data(config_file: Path | None) -> dict:
raise PoodleInputError(msg)


def get_project_info(config_file: Path | None) -> tuple[str, str]:
"""Retrieve Poodle configuration from specified Config File."""
if not config_file:
return ("", "")

if config_file.suffix == ".toml":
return get_project_info_toml(config_file)

# TODO: tox.ini and setup.cfg
# https://tox.wiki/en/3.24.5/config.html

msg = f"Config file type not supported: --config_file='{config_file}'"
raise PoodleInputError(msg)


def get_config_file_data_toml(config_file: Path) -> dict:
"""Retrieve Poodle configuration from a 'toml' Config File."""
config_data = tomllib.load(config_file.open(mode="rb"))
config_data = config_data.get("tool", config_data)
config_data: dict = config_data.get("tool", config_data) # type: ignore[no-redef]
return config_data.get("poodle", {})


def get_project_info_toml(config_file: Path) -> tuple[str, str]:
"""Retrieve Project Name and Version from a 'toml' Config File."""
config_data = tomllib.load(config_file.open(mode="rb"))
config_data: dict = config_data.get("project", config_data) # type: ignore[no-redef]
return config_data.get("name", ""), config_data.get("version", "")


def get_source_folders(command_line_sources: tuple[Path], config_data: dict) -> list[Path]:
"""Retrieve list of source folders that contain files to mutate.
Expand Down
28 changes: 19 additions & 9 deletions src/poodle/data_types/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ def to_dict(self) -> dict[str, Any]:
class PoodleConfig:
"""Configuration options resolved from command line and config files."""

project_name: str | None
project_version: str | None

config_file: Path | None
source_folders: list[Path]

Expand Down Expand Up @@ -105,11 +108,11 @@ class MutantTrialResult(PoodleSerialize):
reason_code: str
reason_desc: str | None = None

RC_FOUND = "mutant_found"
RC_NOT_FOUND = "mutant_not_found"
RC_TIMEOUT = "timeout"
RC_INCOMPLETE = "incomplete"
RC_OTHER = "other"
RC_FOUND = "Mutant Found"
RC_NOT_FOUND = "Mutant Not Found"
RC_TIMEOUT = "Trial Exceeded Timeout"
RC_INCOMPLETE = "Testing Incomplete"
RC_OTHER = "Other, See Description"


@dataclass
Expand Down Expand Up @@ -147,7 +150,15 @@ class TestingSummary(PoodleSerialize):
not_found: int = 0
timeout: int = 0
errors: int = 0
success_rate: float = 0.0

@property
def success_rate(self) -> float:
"""Return the success rate of the test run."""
if self.trials > 0:
return self.found / self.trials
if self.tested > 0:
return self.found / self.tested
return 0.0

@property
def coverage_display(self) -> str:
Expand All @@ -167,20 +178,19 @@ def __iadd__(self, result: MutantTrialResult) -> Self:
else:
self.errors += 1

if self.trials > 0:
self.success_rate = self.found / self.trials

return self

@staticmethod
def from_dict(d: dict[str, Any]) -> dict[str, Any]:
"""Correct fields in Dictionary for JSON deserialization."""
d.pop("success_rate", None)
d.pop("coverage_display", None)
return d

def to_dict(self) -> dict[str, Any]:
"""Convert to Dictionary for JSON serialization."""
d = asdict(self)
d["success_rate"] = self.success_rate
d["coverage_display"] = self.coverage_display
return d

Expand Down
1 change: 1 addition & 0 deletions src/poodle/mutators/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ComparisonMutator(ast.NodeVisitor, Mutator):
# https://docs.python.org/3/library/ast.html#ast.Compare
# https://www.w3schools.com/python/python_operators.asp
# ast.Eq ==
# ast.NotEq !=
# ast.Lt <
# ast.LtE <=
# ast.Gt >
Expand Down
3 changes: 2 additions & 1 deletion src/poodle/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import logging
from typing import TYPE_CHECKING, Any, Callable

from .reporters import report_json, report_not_found, report_summary
from .reporters import report_html, report_json, report_not_found, report_summary
from .util import dynamic_import

if TYPE_CHECKING:
Expand All @@ -19,6 +19,7 @@
"summary": report_summary,
"not_found": report_not_found,
"json": report_json,
"html": report_html,
}


Expand Down
1 change: 1 addition & 0 deletions src/poodle/reporters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Report Mutation Testing Results."""

from .basic import report_json, report_not_found, report_summary
from .html import report_html
11 changes: 2 additions & 9 deletions src/poodle/reporters/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,9 @@
from pathlib import Path
from typing import Callable

from poodle.data_types import MutantTrialResult, PoodleConfig, TestingResults
from poodle.data_types import PoodleConfig, TestingResults
from poodle.util import to_json

display_reason_code = {
MutantTrialResult.RC_FOUND: "FOUND",
MutantTrialResult.RC_INCOMPLETE: "Testing Incomplete",
MutantTrialResult.RC_NOT_FOUND: "Mutant Not Found",
MutantTrialResult.RC_TIMEOUT: "Trial Exceeded Timeout",
}


def get_include_statuses(config: PoodleConfig, prefix: str) -> set[bool]:
"""Get set of statuses to include in report."""
Expand Down Expand Up @@ -69,7 +62,7 @@ def report_not_found(config: PoodleConfig, echo: Callable, testing_results: Test

echo("", file=not_found_file)
echo(
f"Mutant Trial Result: {display_reason_code.get(result.reason_code, result.reason_code)}",
f"Mutant Trial Result: {result.reason_code}",
file=not_found_file,
)
echo(f"Mutator: {mutant.mutator_name}", file=not_found_file)
Expand Down
Loading

0 comments on commit 15dc56a

Please sign in to comment.