diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index 4f39080..0000000 --- a/.github/renovate.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["config:base"] -} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3f1c0bb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,16 @@ +name: Release + +on: + release: + types: [released] + +jobs: + publish-to-pypi: + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v7 + - run: uv build --wheel + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3a17b76..b380f8b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,32 +12,26 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: pre-commit/action@v3.0.0 + - uses: actions/checkout@v6 + - uses: j178/prek-action@v2 build: needs: [lint] strategy: matrix: - platform: [ubuntu-latest] - python-version: ["3.7", "3.11"] - include: - - platform: macos-latest - python-version: "3.11" - - platform: windows-latest - python-version: "3.11" + platform: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.10", "3.15"] runs-on: ${{ matrix.platform }} steps: - - uses: actions/setup-python@v4 + - uses: actions/checkout@v6 + + - uses: astral-sh/setup-uv@v7 with: python-version: ${{ matrix.python-version }} - - uses: actions/checkout@v4 - - name: Test with tox + + - name: Test run: | - pip install tox - tox -- --cov pytest_codeblocks --cov-report xml --cov-report term - - uses: codecov/codecov-action@v4-beta - if: ${{ matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest' }} + uv run pytest diff --git a/.gitignore b/.gitignore index f76b87b..bd66d07 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ dist/ *.png .pytest_cache/ .tox/ +uv.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d22aab6..b5ad1c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,16 +1,11 @@ repos: - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.290 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.17 hooks: - - id: ruff - - - repo: https://github.com/psf/black - rev: 23.9.1 - hooks: - - id: black - language_version: python3 + - id: ruff-check + - id: ruff-format - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.3 + rev: v3.1.0 hooks: - id: prettier diff --git a/justfile b/justfile index 698f532..c60e73b 100644 --- a/justfile +++ b/justfile @@ -1,21 +1,25 @@ -version := `python3 -c "from src.pytest_codeblocks.__about__ import __version__; print(__version__)"` +version := `python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])"` default: @echo "\"just publish\"?" -publish: +publish: release + +release: @if [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then exit 1; fi - gh release create "v{{version}}" - flit publish + gh release create {{version}} clean: @find . | grep -E "(__pycache__|\.pyc|\.pyo$)" | xargs rm -rf @rm -rf src/*.egg-info/ build/ dist/ .tox/ format: - ruff src/ tests/ --fix - black src/ tests/ + ruff format . + ruff check --fix . blacken-docs README.md lint: - pre-commit run --all + prek run --all-files + +test: + uv run pytest diff --git a/pyproject.toml b/pyproject.toml index a728d17..b5fdd19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,38 +4,27 @@ build-backend = "setuptools.build_meta" [project] name = "pytest_codeblocks" -authors = [{name = "Nico Schlömer", email = "nico.schloemer@gmail.com"}] +version = "0.18.0" +authors = [{ name = "Nico Schlömer", email = "nico.schloemer@gmail.com" }] description = "Test code blocks in your READMEs" readme = "README.md" -license = {file = "LICENSE.txt"} +license = { file = "LICENSE.txt" } classifiers = [ "Development Status :: 5 - Production/Stable", "Framework :: Pytest", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", -] -dynamic = ["version"] -requires-python = ">=3.7" -dependencies = [ - "pytest >= 7.0.0" ] +requires-python = ">=3.10" +dependencies = ["pytest >= 7.0.0"] -[tool.setuptools.dynamic] -version = {attr = "pytest_codeblocks.__about__.__version__"} +[dependency-groups] +dev = ["pytest"] [project.urls] Homepage = "https://github.com/nschloe/pytest-codeblocks" -Code = "https://github.com/nschloe/pytest-codeblocks" -Issues = "https://github.com/nschloe/pytest-codeblocks/issues" Funding = "https://github.com/sponsors/nschloe" [project.entry-points.pytest11] diff --git a/src/pytest_codeblocks/__about__.py b/src/pytest_codeblocks/__about__.py deleted file mode 100644 index fd86b3e..0000000 --- a/src/pytest_codeblocks/__about__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.17.0" diff --git a/src/pytest_codeblocks/__init__.py b/src/pytest_codeblocks/__init__.py index b0813f1..cfe1c24 100644 --- a/src/pytest_codeblocks/__init__.py +++ b/src/pytest_codeblocks/__init__.py @@ -1,5 +1,4 @@ from . import plugin -from .__about__ import __version__ from .main import CodeBlock, extract_from_buffer, extract_from_file __all__ = [ @@ -7,5 +6,4 @@ "extract_from_buffer", "extract_from_file", "plugin", - "__version__", ] diff --git a/src/pytest_codeblocks/main.py b/src/pytest_codeblocks/main.py index 36060c2..3612f89 100644 --- a/src/pytest_codeblocks/main.py +++ b/src/pytest_codeblocks/main.py @@ -1,12 +1,8 @@ -from __future__ import annotations - import contextlib import re import sys import warnings -# namedtuple with default arguments -# from dataclasses import dataclass, field from io import StringIO from pathlib import Path @@ -20,7 +16,7 @@ class CodeBlock: expected_output: str | None = None expected_output_ignore_whitespace: bool = False importorskip: str | None = None - marks: list[str] = field(default_factory=lambda: []) + marks: list[str] = field(default_factory=list) def extract_from_file( @@ -73,7 +69,6 @@ def extract_from_buffer(f, max_num_lines: int = 10000) -> list[CodeBlock]: ) expected_output_block = out[-1] if keyword == "expected-output-ignore-whitespace": - # \s: regex matches all whitespace characters expected_output_ignore_whitespace = True elif keyword == "cont": @@ -171,6 +166,7 @@ def extract_from_buffer(f, max_num_lines: int = 10000) -> list[CodeBlock]: expected_output_ignore_whitespace ) expected_output_block = None + expected_output_ignore_whitespace = False else: out.append( @@ -195,5 +191,7 @@ def stdout_io(stdout=None): if stdout is None: stdout = StringIO() sys.stdout = stdout - yield stdout - sys.stdout = old + try: + yield stdout + finally: + sys.stdout = old diff --git a/src/pytest_codeblocks/plugin.py b/src/pytest_codeblocks/plugin.py index d0e931e..ce6ce60 100644 --- a/src/pytest_codeblocks/plugin.py +++ b/src/pytest_codeblocks/plugin.py @@ -1,10 +1,6 @@ -# -# Take a look at the example -# https://docs.pytest.org/en/stable/example/nonpython.html -# import subprocess -from pathlib import Path import re +import sys import pytest @@ -20,17 +16,13 @@ def pytest_addoption(parser): ) -def pytest_collect_file(path, parent): +def pytest_collect_file(file_path, parent): config = parent.config - path = Path(path) - if config.option.codeblocks and path.suffix == ".md": - return MarkdownFile.from_parent(parent, path=path) + if config.option.codeblocks and file_path.suffix == ".md": + return MarkdownFile.from_parent(parent, path=file_path) class MarkdownFile(pytest.File): - def __init__(self, **kwargs): - super().__init__(**kwargs) - def collect(self): for block in extract_from_file(self.path): if block.syntax not in ["python", "sh", "bash"]: @@ -44,14 +36,7 @@ def collect(self): out.obj = block for mark in block.marks: - # A common thing is - # - # pytest.mark.skipif(sys.version_info < (3, 10), reason="...") - # - # which needs sys. Import it here. - import sys # noqa: F401 - - out.add_marker(eval(mark)) + out.add_marker(eval(mark, {"sys": sys, "pytest": pytest})) yield out @@ -62,13 +47,12 @@ def __init__(self, name, parent, obj=None): self.obj = obj def runtest(self): - assert self.obj is not None output = None if self.obj.importorskip is not None: try: __import__(self.obj.importorskip) - except (ImportError, ModuleNotFoundError): + except ImportError: pytest.skip() if self.obj.syntax == "python": @@ -85,20 +69,17 @@ def runtest(self): ) output = s.getvalue() else: - assert self.obj.syntax in ["sh", "bash"] executable = { "sh": None, "bash": "/bin/bash", "zsh": "/bin/zsh", }[self.obj.syntax] - # TODO for python 3.7+, stdout=subprocess.PIPE can be replaced - # by capture_output=True ret = subprocess.run( self.obj.code, shell=True, check=True, - stdout=subprocess.PIPE, + capture_output=True, executable=executable, ) output = ret.stdout.decode() @@ -119,12 +100,7 @@ def runtest(self): ) def repr_failure(self, excinfo): - """Called when self.runtest() raises an exception.""" - # if isinstance(excinfo.value, CodeblockException): return excinfo.value.args[0] - # if excinfo.errisinstance(RuntimeError): - # return excinfo.value.args[0].stdout - # return super().repr_failure(excinfo) def reportinfo(self): return (self.path, -1, "code block check") diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..c6481d5 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ["pytester"] diff --git a/tests/test_comment_styles.py b/tests/test_comment_styles.py index 907372d..25ecbb3 100644 --- a/tests/test_comment_styles.py +++ b/tests/test_comment_styles.py @@ -10,7 +10,7 @@ "", ], ) -def test_cont(testdir, comment): +def test_cont(pytester, comment): string = """ Lorem ipsum ```python @@ -22,6 +22,6 @@ def test_cont(testdir, comment): a + 1 ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) diff --git a/tests/test_cont.py b/tests/test_cont.py index 3744291..5a4ba71 100644 --- a/tests/test_cont.py +++ b/tests/test_cont.py @@ -1,4 +1,4 @@ -def test_cont(testdir): +def test_cont(pytester): string = """ Lorem ipsum ```python @@ -10,12 +10,12 @@ def test_cont(testdir): a + 1 ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) -def test_hidden_cont(testdir): +def test_hidden_cont(pytester): string = """ Lorem ipsum ```python 1 + 2 + 3 ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(errors=1) diff --git a/tests/test_errors.py b/tests/test_errors.py index a531668..28fed74 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -5,13 +5,13 @@ import pytest_codeblocks -def test_unclosed(testdir): +def test_unclosed(pytester): string = """ ```python 1 + 2 + 3 """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(errors=1) diff --git a/tests/test_expect_error.py b/tests/test_expect_error.py index c4dbf65..7f30d5c 100644 --- a/tests/test_expect_error.py +++ b/tests/test_expect_error.py @@ -1,40 +1,40 @@ -def test_expect_error(testdir): +def test_expect_error(pytester): string = """ ```python raise RuntimeError() ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(xfailed=1) -def test_expect_error_runtimeerror(testdir): +def test_expect_error_runtimeerror(pytester): string = """ ```python raise RuntimeError() ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(xfailed=1) -def test_expect_error_indexerror(testdir): +def test_expect_error_indexerror(pytester): string = """ ```python raise RuntimeError() ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(failed=1) -def test_expect_error_fail(testdir): +def test_expect_error_fail(pytester): string1 = """ Lorem ipsum @@ -42,6 +42,6 @@ def test_expect_error_fail(testdir): 1 + 1 ``` """ - testdir.makefile(".md", string1) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string1) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(xpassed=1) diff --git a/tests/test_expected_output.py b/tests/test_expected_output.py index 55c0f46..7ae3cd4 100644 --- a/tests/test_expected_output.py +++ b/tests/test_expected_output.py @@ -1,4 +1,4 @@ -def test_expected_output(testdir): +def test_expected_output(pytester): string = """ Lorem ipsum ```python @@ -14,12 +14,12 @@ def test_expected_output(testdir): 3 ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) -def test_expected_output_fail(testdir): +def test_expected_output_fail(pytester): string = """ Lorem ipsum ```python @@ -31,12 +31,12 @@ def test_expected_output_fail(testdir): 5 ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(failed=1) -def test_expected_output_ignore_whitespace(testdir): +def test_expected_output_ignore_whitespace(pytester): string = """ Lorem ipsum ```python @@ -52,12 +52,12 @@ def test_expected_output_ignore_whitespace(testdir): 3 ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) -def test_expected_output_ignore_whitespace_fail(testdir): +def test_expected_output_ignore_whitespace_fail(pytester): string = """ Lorem ipsum ```python @@ -73,6 +73,6 @@ def test_expected_output_ignore_whitespace_fail(testdir): 5 ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(failed=1) diff --git a/tests/test_general.py b/tests/test_general.py index 9227bb7..21b5326 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -3,7 +3,7 @@ import pytest_codeblocks -def test_basic(testdir): +def test_basic(pytester): string1 = """ Lorem ipsum ```python @@ -11,12 +11,12 @@ def test_basic(testdir): ``` dolor sit amet """ - testdir.makefile(".md", string1) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string1) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) -def test_skip(testdir): +def test_skip(pytester): string1 = """ Lorem ipsum @@ -33,8 +33,8 @@ def test_skip(testdir): 1 + 2 + 3 ``` """ - testdir.makefile(".md", string1) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string1) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(skipped=2) diff --git a/tests/test_shell.py b/tests/test_shell.py index f53607d..3073595 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -6,7 +6,7 @@ pytest.skip("skipping shell tests", allow_module_level=True) -def test_shell(testdir): +def test_shell(pytester): string = """ Lorem ipsum ```sh @@ -17,47 +17,47 @@ def test_shell(testdir): cd ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=2) -def test_shell_fail(testdir): +def test_shell_fail(pytester): string = """ ```sh cdc ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(failed=1) -def test_shell_expect_fail(testdir): +def test_shell_expect_fail(pytester): string = """ ```sh cdc ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(xfailed=1) -def test_shell_expect_fail_passed(testdir): +def test_shell_expect_fail_passed(pytester): string = """ ```sh cd ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(xpassed=1) -def test_shell_expect_output(testdir): +def test_shell_expect_output(pytester): string = """ ```sh echo abc @@ -67,12 +67,12 @@ def test_shell_expect_output(testdir): abc ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) -def test_shell_expect_output_fail(testdir): +def test_shell_expect_output_fail(pytester): string = """ ```sh echo abc @@ -82,12 +82,12 @@ def test_shell_expect_output_fail(testdir): ac ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(failed=1) -def test_bash(testdir): +def test_bash(pytester): string = """ ```bash foo=1 @@ -100,6 +100,6 @@ def test_bash(testdir): abc ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) diff --git a/tests/test_skipif.py b/tests/test_skipif.py index 8348157..fc0d72d 100644 --- a/tests/test_skipif.py +++ b/tests/test_skipif.py @@ -1,4 +1,4 @@ -def test_skip(testdir): +def test_skip(pytester): string = """ Lorem ipsum @@ -8,12 +8,12 @@ def test_skip(testdir): print(1 + 3) ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(skipped=1) -def test_skip_expected_output(testdir): +def test_skip_expected_output(pytester): string = """ Lorem ipsum @@ -30,12 +30,12 @@ def test_skip_expected_output(testdir): ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(skipped=1) -def test_skipif(testdir): +def test_skipif(pytester): string = """ Lorem ipsum @@ -45,12 +45,12 @@ def test_skipif(testdir): print(1 + 3) ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(skipped=1) -def test_skipif2(testdir): +def test_skipif2(pytester): string = """ Lorem ipsum @@ -60,12 +60,12 @@ def test_skipif2(testdir): print(1 + 3) ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) -def test_skipif_expected_output(testdir): +def test_skipif_expected_output(pytester): string = """ Lorem ipsum @@ -82,12 +82,12 @@ def test_skipif_expected_output(testdir): ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(skipped=1) -def test_skipif_expected_output2(testdir): +def test_skipif_expected_output2(pytester): string = """ Lorem ipsum @@ -104,12 +104,12 @@ def test_skipif_expected_output2(testdir): ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) -def test_importorskip(testdir): +def test_importorskip(pytester): string = """ Lorem ipsum @@ -119,12 +119,12 @@ def test_importorskip(testdir): print(1 + 3) ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(skipped=1) -def test_importorskip2(testdir): +def test_importorskip2(pytester): string = """ Lorem ipsum @@ -134,6 +134,6 @@ def test_importorskip2(testdir): print(1 + 3) ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) diff --git a/tests/test_unicode.py b/tests/test_unicode.py index f0937ad..ec273dd 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -1,4 +1,4 @@ -def test_unicode(testdir): +def test_unicode(pytester): # Unicode identifyers are in fact legal Python code string = """ ```python @@ -7,6 +7,6 @@ def test_unicode(testdir): α = 1 ``` """ - testdir.makefile(".md", string) - result = testdir.runpytest("--codeblocks") + pytester.makefile(".md", string) + result = pytester.runpytest("--codeblocks") result.assert_outcomes(passed=1) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 17a0bd9..0000000 --- a/tox.ini +++ /dev/null @@ -1,10 +0,0 @@ -[tox] -envlist = py3 -isolated_build = True - -[testenv] -deps = - pytest - pytest-cov -commands = - pytest {posargs} -p pytester