diff --git a/.github/workflows/test-example-collection.yml b/.github/workflows/test-example-collection.yml new file mode 100644 index 0000000000..7461953dfa --- /dev/null +++ b/.github/workflows/test-example-collection.yml @@ -0,0 +1,58 @@ +name: Validate example collection + +on: + pull_request: + paths: + - "examples/mcp_server/**" + - "examples/openai_api/**" + - "tests/test_example_smoke_collection.py" + - "tests/test_mcp_server_example.py" + - "tests/test_openai_api_docker_validation.py" + - ".github/workflows/test-example-collection.yml" + push: + branches: [main] + paths: + - "examples/mcp_server/**" + - "examples/openai_api/**" + - "tests/test_example_smoke_collection.py" + - "tests/test_mcp_server_example.py" + - "tests/test_openai_api_docker_validation.py" + - ".github/workflows/test-example-collection.yml" + +permissions: + contents: read + +jobs: + collection: + runs-on: ubuntu-latest + timeout-minutes: 5 + strategy: + fail-fast: false + matrix: + python: ["3.10", "3.12"] + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + - name: Install test runner + run: python -m pip install pytest + - name: Check collection and standalone CLI compatibility + run: | + git rev-parse HEAD > source-commit.txt + python -m pip freeze > resolved-dependencies.txt + python -m pytest -q \ + tests/test_example_smoke_collection.py \ + tests/test_mcp_server_example.py \ + tests/test_openai_api_docker_validation.py \ + --junitxml=example-results.xml + - uses: actions/upload-artifact@v4 + if: always() + with: + name: example-collection-${{ matrix.python }} + path: | + source-commit.txt + resolved-dependencies.txt + example-results.xml diff --git a/examples/mcp_server/__init__.py b/examples/mcp_server/__init__.py new file mode 100644 index 0000000000..a188fda91e --- /dev/null +++ b/examples/mcp_server/__init__.py @@ -0,0 +1 @@ +"""Standalone MCP server example and its validation utilities.""" diff --git a/examples/openai_api/__init__.py b/examples/openai_api/__init__.py new file mode 100644 index 0000000000..577d094e49 --- /dev/null +++ b/examples/openai_api/__init__.py @@ -0,0 +1 @@ +"""Standalone OpenAI-compatible API example and its validation utilities.""" diff --git a/tests/test_example_smoke_collection.py b/tests/test_example_smoke_collection.py new file mode 100644 index 0000000000..9c63124ade --- /dev/null +++ b/tests/test_example_smoke_collection.py @@ -0,0 +1,52 @@ +"""Keep the standalone smoke CLIs collectable together with default pytest.""" + +import os +from pathlib import Path +import shutil +import subprocess +import sys + +import pytest + + +ROOT = Path(__file__).resolve().parents[1] +EXAMPLES = ("mcp_server", "openai_api") + + +@pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"]) +def test_smoke_scripts_collect_together(tmp_path, import_mode): + # Use real source files in the same directory layout, without local plugins. + scripts = [] + for name in EXAMPLES: + source = ROOT / "examples" / name + target = tmp_path / "examples" / name + target.mkdir(parents=True) + for filename in ("smoke_test.py", "__init__.py"): + if (source / filename).exists(): + shutil.copyfile(source / filename, target / filename) + scripts.append(str(target / "smoke_test.py")) + sentinel = tmp_path / "test_sentinel.py" + sentinel.write_text("def test_collection_sentinel():\n pass\n", encoding="utf-8") + env = dict(os.environ, PYTEST_DISABLE_PLUGIN_AUTOLOAD="1") + env.pop("PYTEST_ADDOPTS", None) + env.pop("PYTEST_PLUGINS", None) + result = subprocess.run( + [sys.executable, "-m", "pytest", "--collect-only", "-q", + "--import-mode=" + import_mode, *scripts, str(sentinel)], + cwd=tmp_path, env=env, capture_output=True, text=True, timeout=30, + ) + assert result.returncode == 0, result.stdout + result.stderr + assert "test_sentinel.py::test_collection_sentinel" in result.stdout + assert "1 test collected" in result.stdout + + +@pytest.mark.parametrize("example", EXAMPLES) +def test_smoke_script_help_still_runs_directly(example): + result = subprocess.run( + [sys.executable, str(ROOT / "examples" / example / "smoke_test.py"), "--help"], + cwd=ROOT / "examples" / example, + capture_output=True, text=True, timeout=30, + ) + assert result.returncode == 0, result.stdout + result.stderr + assert "usage:" in result.stdout + assert "--timeout" in result.stdout