Skip to content

Commit 4d6c2c4

Browse files
author
rodrigo.nogueira
committed
Add pytest smoke tests for examples
Per maintainer feedback, add subprocess-based smoke tests for examples. Tests verify examples complete without errors. - Add 'example' pytest marker excluded from default run - Create tests/test_examples.py with parametrized tests - Test 7 self-contained middleware examples - Tests must run with --numprocesses=0 due to port conflicts
1 parent 66d9261 commit 4d6c2c4

2 files changed

Lines changed: 72 additions & 2 deletions

File tree

setup.cfg

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ addopts =
6161
--cov=aiohttp
6262
--cov=tests/
6363

64-
# run tests that are not marked with dev_mode
65-
-m "not dev_mode"
64+
# run tests that are not marked with dev_mode or example
65+
-m "not dev_mode and not example"
6666
filterwarnings =
6767
error
6868
ignore:module 'ssl' has no attribute 'OP_NO_COMPRESSION'. The Python interpreter is compiled against OpenSSL < 1.0.0. Ref. https.//docs.python.org/3/library/ssl.html#ssl.OP_NO_COMPRESSION:UserWarning
@@ -96,5 +96,6 @@ testpaths = tests/
9696
xfail_strict = true
9797
markers =
9898
dev_mode: mark test to run in dev mode.
99+
example: smoke tests for examples folder, run as subprocess invocations.
99100
internal: tests which may cause issues for packagers, but should be run in aiohttp's CI.
100101
skip_blockbuster: mark test to skip the blockbuster fixture.

tests/test_examples.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python3
2+
"""Smoke tests for examples folder.
3+
4+
These tests run examples as subprocess invocations to verify they complete
5+
without errors or warnings. They are excluded from the main test suite and
6+
can be run separately with:
7+
8+
pytest -m example --numprocesses=0
9+
10+
Note: --numprocesses=0 is required because examples use hardcoded ports.
11+
"""
12+
13+
from __future__ import annotations
14+
15+
import subprocess
16+
import sys
17+
from pathlib import Path
18+
from typing import NamedTuple
19+
20+
import pytest
21+
22+
EXAMPLES_DIR = Path(__file__).parent.parent / "examples"
23+
PYTHON = sys.executable
24+
25+
26+
class ExampleConfig(NamedTuple):
27+
name: str
28+
timeout: int = 30
29+
30+
31+
SELF_CONTAINED_EXAMPLES = [
32+
ExampleConfig("rate_limit_middleware.py", timeout=60),
33+
ExampleConfig("logging_middleware.py", timeout=30),
34+
ExampleConfig("retry_middleware.py", timeout=60),
35+
ExampleConfig("basic_auth_middleware.py", timeout=30),
36+
ExampleConfig("digest_auth_qop_auth.py", timeout=30),
37+
ExampleConfig("combined_middleware.py", timeout=60),
38+
ExampleConfig("token_refresh_middleware.py", timeout=60),
39+
]
40+
41+
42+
def _run_example(example_path: Path, timeout: int) -> subprocess.CompletedProcess[str]:
43+
return subprocess.run(
44+
[PYTHON, str(example_path)],
45+
capture_output=True,
46+
text=True,
47+
timeout=timeout,
48+
cwd=str(example_path.parent),
49+
)
50+
51+
52+
@pytest.mark.example
53+
@pytest.mark.parametrize(
54+
"config",
55+
SELF_CONTAINED_EXAMPLES,
56+
ids=[e.name for e in SELF_CONTAINED_EXAMPLES],
57+
)
58+
def test_example_runs_successfully(config: ExampleConfig) -> None:
59+
"""Verify example completes without errors."""
60+
example_path = EXAMPLES_DIR / config.name
61+
assert example_path.exists(), f"Example not found: {example_path}"
62+
63+
result = _run_example(example_path, config.timeout)
64+
65+
assert result.returncode == 0, (
66+
f"Example {config.name} failed with exit code {result.returncode}\n"
67+
f"stdout:\n{result.stdout}\n"
68+
f"stderr:\n{result.stderr}"
69+
)

0 commit comments

Comments
 (0)