-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from mam-dev/code
Initial code
- Loading branch information
Showing
18 changed files
with
955 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
name: CI | ||
|
||
on: | ||
push: | ||
branches: [ main ] | ||
pull_request: | ||
branches: [ main ] | ||
|
||
jobs: | ||
build: | ||
strategy: | ||
matrix: | ||
python_version: ["3.9", "3.10", "3.11"] | ||
runs-on: "ubuntu-latest" | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Set up Python ${{ matrix.python_version }} | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: ${{ matrix.python_version }} | ||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip wheel | ||
python -m pip install tox==4.* tox-gh-actions==3.* | ||
- name: Run tox | ||
run: tox | ||
- name: System test | ||
run: ./tests/system_test/system_test.sh |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Thanks to: Sean Hammond | ||
# https://www.seanh.cc/2022/05/21/publishing-python-packages-from-github-actions | ||
name: Publish to PyPI.org | ||
on: | ||
release: | ||
types: [published] | ||
jobs: | ||
pypi: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout | ||
uses: actions/checkout@v3 | ||
with: | ||
fetch-depth: 0 | ||
- run: python3 -m pip install --upgrade build && python3 -m build | ||
- name: Publish package | ||
uses: pypa/gh-action-pypi-publish@release/v1 | ||
with: | ||
password: ${{ secrets.PYPI_API_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# pytest-litter | ||
|
||
Pytest plugin that, when installed, will fail test cases which | ||
create or delete files. Tests should not modify the file tree, | ||
because it can be a cause of test pollution as well as accidental | ||
committing of files to the repo. | ||
|
||
To use it, simply run | ||
``` | ||
pip install pytest-litter | ||
``` | ||
The only dependency is `pytest` itself. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
[project] | ||
name = "pytest-litter" | ||
description = "Pytest plugin which verifies that tests do not modify file trees." | ||
readme = "README.md" | ||
license = {file = "LICENSE"} | ||
urls = {repo = "https://github.com/mam-dev/pytest-litter"} | ||
requires-python = ">=3.9" | ||
dependencies = ["pytest >= 6.1"] | ||
dynamic = ["version"] | ||
classifiers = [ | ||
"Development Status :: 4 - Beta", | ||
"Programming Language :: Python :: 3.9", | ||
"Programming Language :: Python :: 3.10", | ||
"Programming Language :: Python :: 3.11", | ||
"Topic :: Software Development :: Testing", | ||
"Topic :: Software Development :: Testing :: Unit", | ||
"Framework :: Pytest", | ||
"License :: OSI Approved :: Apache Software License", | ||
] | ||
|
||
[project.entry-points.pytest11] | ||
pytest-litter = "pytest_litter.plugin.plugin" | ||
|
||
[build-system] | ||
requires = ["setuptools>=51", "wheel", "setuptools_scm[toml]>=6.2"] | ||
build-backend = "setuptools.build_meta" | ||
|
||
[tool.setuptools_scm] | ||
|
||
[tool.setuptools.packages.find] | ||
where = ["src"] | ||
namespaces = false | ||
|
||
[tool.pytest.ini_options] | ||
addopts = "--random-order -p no:pytest-litter -p pytester -vv" | ||
testpaths = ["tests"] | ||
pytester_example_dir = "tests/pytester" | ||
|
||
[tool.coverage.run] | ||
branch = true | ||
source_pkgs = ["pytest_litter"] | ||
|
||
[tool.coverage.report] | ||
show_missing = true | ||
fail_under = 100 | ||
|
||
[tool.mypy] | ||
files = ["src", "tests"] | ||
warn_no_return = true | ||
warn_return_any = true | ||
warn_unused_configs = true | ||
warn_unused_ignores = true | ||
warn_redundant_casts = true | ||
warn_unreachable = true | ||
check_untyped_defs = true | ||
disallow_any_generics = true | ||
disallow_subclassing_any = true | ||
disallow_untyped_calls = true | ||
disallow_untyped_defs = true | ||
disallow_incomplete_defs = true | ||
disallow_untyped_decorators = true | ||
no_implicit_optional = true | ||
no_implicit_reexport = true | ||
strict_equality = true | ||
strict_concatenate = true | ||
|
||
[tool.ruff] | ||
src = ["src", "tests"] | ||
select = [ | ||
"E", # pycodestyle | ||
"F", # pyflakes | ||
"UP", # pyupgrade | ||
"S", # flake8-bandit | ||
"D", # pydocstyle | ||
"PT", # flake8-pytest-style | ||
"I", # isort | ||
"RUF", # Ruff-specific rules | ||
"PTH", # flake8-use-pathlib | ||
"ERA", # eradicate | ||
"PL", # pylint | ||
"FBT", # flake8-boolean-trap | ||
"B", # flake8-bugbear | ||
"A", # flake8-builtins | ||
"ISC", # flake8-implicit-str-concat | ||
"INP", # flake8-no-pep420 | ||
"SLF", # flake8-self | ||
"SIM", # flake8-simplify | ||
"TID", # flake8-tidy-imports | ||
"ARG", # flake8-unused-arguments | ||
"TRY", # tryceratops | ||
"FLY", # flynt | ||
"RSE", # flake8-raise | ||
"RET", # flake8-return | ||
"FIX", # flake8-fixme | ||
"Q", # flake8-quotes | ||
"C4", # flake8-comprehensions | ||
"DTZ", # flake8-datetimez | ||
"T10", # flake8-debugger | ||
"T20", # flake8-print | ||
"TCH", # flake8-type-checking | ||
] | ||
ignore = [ | ||
"D100", "D102", "D103", "D104", "D105", "D107", | ||
"PTH123", | ||
"TRY003", "TRY301", | ||
] | ||
|
||
[tool.ruff.per-file-ignores] | ||
"tests/**/*.py" = [ | ||
"S101", "S105", | ||
"D103", | ||
"FBT001", | ||
"SLF001", | ||
"PLR2004", "PLR0913", | ||
"ARG001", | ||
"INP001", | ||
] | ||
|
||
[tool.ruff.pydocstyle] | ||
convention = "google" | ||
|
||
[tool.ruff.flake8-pytest-style] | ||
fixture-parentheses = false | ||
mark-parentheses = false | ||
parametrize-names-type = "csv" | ||
parametrize-values-type = "list" | ||
|
||
[tool.ruff.flake8-tidy-imports] | ||
ban-relative-imports = "parents" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
ruff | ||
black | ||
mypy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
pytest | ||
coverage>=5.3 | ||
pytest-random-order | ||
pytest-integration |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Pytest plugin which verifies that tests do not modify file trees.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Pytest plugin code.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
"""Pytest plugin code.""" | ||
|
||
from typing import Optional | ||
|
||
import pytest | ||
|
||
from pytest_litter.plugin.utils import ( | ||
COMPARATOR_KEY, | ||
SNAPSHOT_KEY, | ||
raise_test_error_from_comparison, | ||
run_snapshot_comparison, | ||
) | ||
from pytest_litter.snapshots import ( | ||
DirectoryIgnoreSpec, | ||
IgnoreSpec, | ||
RegexIgnoreSpec, | ||
SnapshotComparator, | ||
TreeSnapshot, | ||
) | ||
|
||
|
||
def pytest_configure(config: pytest.Config) -> None: | ||
"""Configure pytest-litter plugin (pytest hook function).""" | ||
ignore_specs: list[IgnoreSpec] = [] | ||
basetemp: Optional[str] = config.getoption("basetemp", None) | ||
if basetemp is not None: | ||
ignore_specs.append( | ||
DirectoryIgnoreSpec( | ||
directory=config.rootpath / basetemp, | ||
) | ||
) | ||
ignore_specs.append(RegexIgnoreSpec(regex=r".*/__pycache__.*")) | ||
config.stash[SNAPSHOT_KEY] = TreeSnapshot(root=config.rootpath) | ||
config.stash[COMPARATOR_KEY] = SnapshotComparator(ignore_specs=ignore_specs) | ||
|
||
|
||
@pytest.hookimpl(hookwrapper=True) # type: ignore[misc] | ||
def pytest_runtest_call(item: pytest.Item): # type: ignore[no-untyped-def] | ||
yield | ||
run_snapshot_comparison( | ||
test_name=item.name, | ||
config=item.config, | ||
mismatch_cb=raise_test_error_from_comparison, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
from collections.abc import Iterable | ||
from pathlib import Path | ||
from typing import Callable | ||
|
||
import pytest | ||
|
||
from pytest_litter.snapshots import SnapshotComparator, SnapshotComparison, TreeSnapshot | ||
|
||
SNAPSHOT_KEY = pytest.StashKey[TreeSnapshot]() | ||
COMPARATOR_KEY = pytest.StashKey[SnapshotComparator]() | ||
|
||
|
||
class ProblematicTestLitterError(Exception): | ||
"""Raised when a test causes littering, i.e., modifies file tree.""" | ||
|
||
|
||
def format_test_snapshot_mismatch_message( | ||
test_name: str, paths_added: Iterable[Path], paths_deleted: Iterable[Path] | ||
) -> str: | ||
def _iterable_to_human_readable(iterable: Iterable[Path]) -> str: | ||
return ", ".join(f"'{x}'" for x in iterable) | ||
|
||
message = f"The test '{test_name}'" | ||
if paths_added: | ||
message += f" added {_iterable_to_human_readable(paths_added)}" | ||
if paths_deleted: | ||
message += " and" | ||
if paths_deleted: | ||
message += f" deleted {_iterable_to_human_readable(paths_deleted)}" | ||
return message | ||
|
||
|
||
def raise_test_error_from_comparison( | ||
test_name: str, comparison: SnapshotComparison | ||
) -> None: | ||
"""Raise ProblematicTestLitterError for test_name based on comparison.""" | ||
raise ProblematicTestLitterError( | ||
format_test_snapshot_mismatch_message( | ||
test_name=test_name, | ||
paths_added=tuple(p.path for p in comparison.only_b), | ||
paths_deleted=tuple(p.path for p in comparison.only_a), | ||
) | ||
) | ||
|
||
|
||
def run_snapshot_comparison( | ||
test_name: str, | ||
config: pytest.Config, | ||
mismatch_cb: Callable[[str, SnapshotComparison], None], | ||
) -> None: | ||
"""Compare current and old snapshots and call mismatch_cb if there is a mismatch.""" | ||
original_snapshot: TreeSnapshot = config.stash[SNAPSHOT_KEY] | ||
new_snapshot: TreeSnapshot = TreeSnapshot(root=original_snapshot.root) | ||
config.stash[SNAPSHOT_KEY] = new_snapshot | ||
|
||
comparator: SnapshotComparator = config.stash[COMPARATOR_KEY] | ||
comparison: SnapshotComparison = comparator.compare(original_snapshot, new_snapshot) | ||
|
||
if not comparison.matches: | ||
mismatch_cb(test_name, comparison) |
Oops, something went wrong.