diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a6de2ae..3740004 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,17 +28,21 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.13" - - name: Install and configure Poetry (this should ideally be done from pyproject.toml but..) - uses: snok/install-poetry@v1 + - name: Install and configure Poetry + uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a # v1 with: - version: 1.8.5 + version: 2.3.2 virtualenvs-create: true - name: Install poetry-dynamic-versioning run: poetry self add "poetry-dynamic-versioning[plugin]" - name: Install dependencies run: poetry install --with dev - - name: Run unit and integrations tests - run: poetry run pytest --junitxml=build/junit.xml --cov --cov-report=xml:build/coverage.xml tests + - name: Build wheel (used by e2e tests) + run: poetry build --format wheel + - name: Install plugin into Poetry (activates it for e2e tests) + run: poetry self add $(ls $PWD/dist/reqstool_python_poetry_plugin-*.whl | tail -1) + - name: Run tests + run: poetry run pytest --junitxml=build/junit.xml --cov --cov-report=xml:build/coverage.xml - name: Build project run: poetry build # Upload artifacts for later use diff --git a/.github/workflows/check-semantic-pr.yml b/.github/workflows/check-semantic-pr.yml index 5bacd57..459b868 100644 --- a/.github/workflows/check-semantic-pr.yml +++ b/.github/workflows/check-semantic-pr.yml @@ -2,8 +2,6 @@ name: Check Semantic PR on: pull_request: types: [opened, edited, synchronize, reopened] - pull_request_target: - types: [opened, edited, synchronize, reopened] jobs: check: diff --git a/pyproject.toml b/pyproject.toml index 15d1075..c32d7c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,15 +45,13 @@ addopts = [ "-s", "--import-mode=importlib", "--log-cli-level=DEBUG", - '-m not slow or not integration', ] pythonpath = [".", "src", "tests"] testpaths = ["tests"] norecursedirs = ["tests/fixtures"] markers = [ - "flaky: tests that can randomly fail through no change to the code", - "slow: marks tests as slow (deselect with '-m \"not slow\"')", "integration: tests that require external resources", + "e2e: end-to-end tests that run the full pipeline locally", ] [tool.black] diff --git a/tests/e2e/__init__.py b/tests/e2e/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/reqstool_python_poetry_plugin/__init__.py b/tests/e2e/reqstool_python_poetry_plugin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/e2e/reqstool_python_poetry_plugin/test_build_e2e.py b/tests/e2e/reqstool_python_poetry_plugin/test_build_e2e.py new file mode 100644 index 0000000..c38075c --- /dev/null +++ b/tests/e2e/reqstool_python_poetry_plugin/test_build_e2e.py @@ -0,0 +1,65 @@ +# Copyright © LFV +import os +import shutil +import subprocess +import tarfile +import tempfile +from pathlib import Path + +import pytest + +FIXTURE_DIR = Path(__file__).parents[2] / "fixtures" / "test_project" + +EXPECTED_IN_TARBALL = [ + "reqstool_config.yml", + "annotations.yml", + "requirements.yml", + "software_verification_cases.yml", +] + + +def _plugin_installed() -> bool: + """Return True if the reqstool poetry plugin is registered as an application plugin.""" + from importlib.metadata import entry_points + + eps = entry_points(group="poetry.application.plugin") + return any(ep.name == "reqstool" for ep in eps) + + +@pytest.mark.e2e +@pytest.mark.skipif(not shutil.which("poetry"), reason="poetry not on PATH") +@pytest.mark.skipif(not _plugin_installed(), reason="reqstool-python-poetry-plugin not installed in poetry") +def test_poetry_build_sdist_contains_reqstool_artifacts(): + """poetry build (sdist) triggers the reqstool plugin and bundles all artifacts.""" + with tempfile.TemporaryDirectory() as tmpdir: + tmp_project = Path(tmpdir) / "test_project" + shutil.copytree( + FIXTURE_DIR, + tmp_project, + ignore=shutil.ignore_patterns("dist", "build", "__pycache__", ".venv", "poetry.lock"), + ) + + result = subprocess.run( + ["poetry", "build", "--format", "sdist"], + cwd=tmp_project, + capture_output=True, + text=True, + env={k: v for k, v in os.environ.items() if k != "POETRY_ACTIVE"}, + ) + assert result.returncode == 0, f"poetry build failed:\n{result.stderr}" + + tarballs = sorted((tmp_project / "dist").glob("mypackage-*.tar.gz")) + assert tarballs, "No tarball found in dist/" + + with tarfile.open(tarballs[-1]) as tf: + names = tf.getnames() + for expected in EXPECTED_IN_TARBALL: + assert any( + expected in n for n in names + ), f"{expected!r} missing from {tarballs[-1].name};\ngot: {names}" + # Verify annotations.yml content — confirms the decorator processor ran + member = next(m for m in tf.getmembers() if "annotations.yml" in m.name) + annotations_content = tf.extractfile(member).read().decode() + + assert "REQ_001" in annotations_content, "annotations.yml missing REQ_001" + assert "SVC_001" in annotations_content, "annotations.yml missing SVC_001" diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/reqstool_python_poetry_plugin/__init__.py b/tests/integration/reqstool_python_poetry_plugin/__init__.py new file mode 100644 index 0000000..e69de29