Skip to content

Commit 44e9a60

Browse files
committed
ci: test output of examples
1 parent 32b9762 commit 44e9a60

28 files changed

+1286
-3
lines changed

.github/workflows/rust.yml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ jobs:
126126
127127
####################################################################################################
128128
# STEP 2: INTERMEDIATE
129-
# ["Test", "Package", "MSRV"]
129+
# ["Test", "Package", "Example", "MSRV"]
130130
####################################################################################################
131131

132132
Test:
@@ -175,6 +175,27 @@ jobs:
175175
- run: cd ${{ matrix.package }} && cargo nextest run --no-tests=warn
176176
- run: cd ${{ matrix.package }} && cargo clippy --all-targets -- -D warnings
177177

178+
Example:
179+
needs: ["Rustfmt", "Docs", "Audit", "Book", "Typos", "Jinja2-Assumptions", "DevSkim", "CargoSort"]
180+
strategy:
181+
matrix:
182+
package: [actix-web, axum, poem, rocket, salvo, warp]
183+
runs-on: ubuntu-latest
184+
steps:
185+
- uses: actions/checkout@v4
186+
- uses: dtolnay/rust-toolchain@stable
187+
- uses: actions/setup-python@v5
188+
with:
189+
python-version: '3.13'
190+
- uses: astral-sh/setup-uv@v5
191+
- uses: Swatinem/rust-cache@v2
192+
- run: cargo build
193+
working-directory: examples/${{ matrix.package }}-app
194+
- run: uv sync
195+
working-directory: examples/test-examples
196+
- run: uv run pytest --verbose tests/test_${{ matrix.package }}.py
197+
working-directory: examples/test-examples
198+
178199
MSRV:
179200
needs: ["Rustfmt", "Docs", "Audit", "Book", "Typos", "Jinja2-Assumptions", "DevSkim", "CargoSort"]
180201
runs-on: ubuntu-latest
@@ -191,7 +212,7 @@ jobs:
191212
####################################################################################################
192213

193214
Fuzz:
194-
needs: ["Test", "Package", "MSRV"]
215+
needs: ["Test", "Package", "Example", "MSRV"]
195216
strategy:
196217
matrix:
197218
fuzz_target:
@@ -217,7 +238,7 @@ jobs:
217238
RUSTFLAGS: '-Ctarget-feature=-crt-static'
218239

219240
Cluster-Fuzz:
220-
needs: ["Test", "Package", "MSRV"]
241+
needs: ["Test", "Package", "Example", "MSRV"]
221242
runs-on: ubuntu-latest
222243
permissions:
223244
security-events: write

examples/test-examples/.gitignore

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
# Usually these files are written by a python script from a template
31+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+
*.manifest
33+
*.spec
34+
35+
# Installer logs
36+
pip-log.txt
37+
pip-delete-this-directory.txt
38+
39+
# Unit test / coverage reports
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
.coverage
44+
.coverage.*
45+
.cache
46+
nosetests.xml
47+
coverage.xml
48+
*.cover
49+
*.py,cover
50+
.hypothesis/
51+
.pytest_cache/
52+
cover/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
.pybuilder/
76+
target/
77+
78+
# Jupyter Notebook
79+
.ipynb_checkpoints
80+
81+
# IPython
82+
profile_default/
83+
ipython_config.py
84+
85+
# pyenv
86+
# For a library or package, you might want to ignore these files since the code is
87+
# intended to run in multiple environments; otherwise, check them in:
88+
# .python-version
89+
90+
# pipenv
91+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
93+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
94+
# install all needed dependencies.
95+
#Pipfile.lock
96+
97+
# UV
98+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99+
# This is especially recommended for binary packages to ensure reproducibility, and is more
100+
# commonly ignored for libraries.
101+
#uv.lock
102+
103+
# poetry
104+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105+
# This is especially recommended for binary packages to ensure reproducibility, and is more
106+
# commonly ignored for libraries.
107+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108+
#poetry.lock
109+
110+
# pdm
111+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112+
#pdm.lock
113+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114+
# in version control.
115+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116+
.pdm.toml
117+
.pdm-python
118+
.pdm-build/
119+
120+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121+
__pypackages__/
122+
123+
# Celery stuff
124+
celerybeat-schedule
125+
celerybeat.pid
126+
127+
# SageMath parsed files
128+
*.sage.py
129+
130+
# Environments
131+
.env
132+
.venv
133+
env/
134+
venv/
135+
ENV/
136+
env.bak/
137+
venv.bak/
138+
139+
# Spyder project settings
140+
.spyderproject
141+
.spyproject
142+
143+
# Rope project settings
144+
.ropeproject
145+
146+
# mkdocs documentation
147+
/site
148+
149+
# mypy
150+
.mypy_cache/
151+
.dmypy.json
152+
dmypy.json
153+
154+
# Pyre type checker
155+
.pyre/
156+
157+
# pytype static type analyzer
158+
.pytype/
159+
160+
# Cython debug symbols
161+
cython_debug/
162+
163+
# PyCharm
164+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166+
# and can be added to the global gitignore or merged into this file. For a more nuclear
167+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
168+
#.idea/
169+
170+
# PyPI configuration file
171+
.pypirc
172+
173+
uv.lock
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13

examples/test-examples/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
This folder contains tests that ensure that our example applications generate the expected
2+
HTML code.
3+
4+
* Ensure that `uv` is installed: <https://docs.astral.sh/uv/#getting-started>,
5+
* then compile all examples in debug mode,
6+
* then execute:
7+
8+
```bash
9+
./test.sh
10+
11+
# OR
12+
13+
uv sync && uv run pytest --verbose
14+
```

examples/test-examples/pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[project]
2+
name = "test-examples"
3+
version = "0.1.0"
4+
description = "This folder contains tests that ensure that our example applications generate the expected HTML code."
5+
readme = "README.md"
6+
requires-python = ">=3.13"
7+
dependencies = [
8+
"pexpect >= 4.9, == 4.*",
9+
"pytest >= 8.3, == 8.*",
10+
"requests >= 2.32, == 2.*",
11+
]
12+
license.text = "MIT OR Apache-2.0"
13+
classifiers = ["Private :: Do Not Upload"]
14+
15+
[build-system]
16+
requires = ["setuptools >= 75"]
17+
18+
[tool.pytest.ini_options]
19+
addopts = ["--import-mode=importlib"]
20+
pythonpath = ["."]

examples/test-examples/test.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
if ! which uv > /dev/null; then
6+
echo 'Please install `uv` first: <https://docs.astral.sh/uv/#getting-started>'
7+
exit 1
8+
fi > /dev/stderr
9+
10+
cd "$(dirname "$0")"
11+
uv sync
12+
exec uv run pytest --verbose

examples/test-examples/tests/__init__.py

Whitespace-only changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from functools import wraps
2+
from pathlib import Path
3+
from signal import SIGINT
4+
5+
from pexpect.popen_spawn import PopenSpawn
6+
from requests import Session
7+
8+
9+
__all__ = ["with_server_and_session", "read"]
10+
11+
12+
def with_server_and_session(name):
13+
def inner(fn):
14+
@wraps(fn)
15+
def wrapped(self):
16+
server = PopenSpawn(
17+
["cargo", "+stable", "run"],
18+
timeout=30.0,
19+
cwd=Path(__file__).parent.parent.parent / f"{name}-app",
20+
encoding="UTF-8",
21+
)
22+
try:
23+
with Session() as session:
24+
return fn(self, server, session)
25+
finally:
26+
server.sendeof()
27+
server.kill(SIGINT)
28+
server.wait()
29+
30+
return wrapped
31+
32+
return inner
33+
34+
35+
def read(name):
36+
with open(Path(__file__).parent / "expected" / name) as f:
37+
return f.read()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.html -text binary diff
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>404: Not Found
6+
</title>
7+
8+
<meta http-equiv="expires" content="Sat, 01 Dec 2001 00:00:00 GMT" />
9+
<meta http-equiv="cache-control" content="no-cache, no-store, must-revalidate" />
10+
<meta http-equiv="pragma" content="no-cache" />
11+
<meta name="viewport" content="width=device-width, initial-scale=1" />
12+
<meta name="robots" content="noindex, nofollow" />
13+
<style>
14+
/*<![CDATA[*/
15+
html {
16+
background-color: #eee;
17+
color: #111;
18+
font-size: 62.5%;
19+
min-height: 100vh;
20+
color-scheme: light;
21+
}
22+
* {
23+
line-height: 1.2em;
24+
}
25+
body {
26+
background-color: #fff;
27+
font-size: 1.8rem;
28+
max-width: 40em;
29+
margin: 1em auto;
30+
padding: 2em;
31+
}
32+
h1 { font-size: 2.4rem; }
33+
h2 { font-size: 2.2rem; }
34+
h3 { font-size: 2.0rem; }
35+
a:link, a:visited {
36+
color: #36c;
37+
text-decoration: none;
38+
}
39+
a:active, a:hover, a:focus {
40+
text-decoration: underline;
41+
text-underline-offset: 0.3em;
42+
}
43+
#lang-select {
44+
font-size: 80%;
45+
width: max-content;
46+
margin: 2em 0 0 auto;
47+
display: flex;
48+
flex-direction: row;
49+
flex-wrap: wrap;
50+
}
51+
#lang-select li {
52+
flex-grow: 1;
53+
flex-basis: auto;
54+
margin: .25em 0 0 0;
55+
padding: 0 1em;
56+
text-align: center;
57+
list-style-type: none;
58+
border-left: 0.1rem solid currentColor;
59+
}
60+
#lang-select li:first-of-type {
61+
border-left: 0 none transparent;
62+
}
63+
#lang-select li:last-of-type {
64+
padding-right: 0;
65+
}
66+
/*]]>*/
67+
</style>
68+
</head>
69+
<body>
70+
<h1>404: Not Found
71+
</h1><h2><a href="/">Back to the first page.</a></h2>
72+
</body>
73+
</html>

0 commit comments

Comments
 (0)