pl-e2e-test 0.1.1__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pl_e2e_test-0.1.1/src/pl_e2e_test.egg-info → pl_e2e_test-0.1.3}/PKG-INFO +79 -11
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/README.md +78 -11
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/pyproject.toml +3 -5
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3/src/pl_e2e_test.egg-info}/PKG-INFO +79 -11
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/pl_e2e_test.egg-info/SOURCES.txt +4 -0
- pl_e2e_test-0.1.3/src/pl_e2e_test.egg-info/entry_points.txt +2 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/pl_e2e_test.egg-info/requires.txt +1 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/__init__.py +2 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/__main__.py +1 -1
- pl_e2e_test-0.1.3/src/prairielearn_e2e/cli.py +58 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/fuzz.py +2 -2
- pl_e2e_test-0.1.3/src/prairielearn_e2e/parallel.py +250 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/regression.py +1 -1
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/reporting.py +3 -2
- pl_e2e_test-0.1.3/tests/test_cli.py +81 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_fuzz.py +1 -1
- pl_e2e_test-0.1.3/tests/test_parallel.py +400 -0
- pl_e2e_test-0.1.1/src/pl_e2e_test.egg-info/entry_points.txt +0 -5
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/LICENSE +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/MANIFEST.in +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/setup.cfg +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/pl_e2e_test.egg-info/dependency_links.txt +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/pl_e2e_test.egg-info/top_level.txt +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/_git.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/backend.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/config.json +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/config.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/contract.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/discovery.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/errors.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/harness.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/html_checks.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/metadata.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/pytest_plugin.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/question_tests.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/repository.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/runner.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/server.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/src/prairielearn_e2e/sharding.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/__init__.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/conftest.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/stub_git.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_contract.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_discovery.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_git.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_harness.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_html_checks.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_metadata.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_publish_version.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_pytest_plugin.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_regression.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_reporting.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_repository.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_server.py +0 -0
- {pl_e2e_test-0.1.1 → pl_e2e_test-0.1.3}/tests/test_sharding.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pl-e2e-test
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Fuzz PrairieLearn question lifecycles against the real Python element runtime
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/PrairieLearn/pl-e2e-test
|
|
@@ -18,6 +18,7 @@ Requires-Dist: platformdirs>=4.5
|
|
|
18
18
|
Requires-Dist: prairielearn
|
|
19
19
|
Requires-Dist: pyright>=1.1.414
|
|
20
20
|
Requires-Dist: pytest>=9.1
|
|
21
|
+
Requires-Dist: rich>=14
|
|
21
22
|
Requires-Dist: ruff>=0.16
|
|
22
23
|
Dynamic: license-file
|
|
23
24
|
|
|
@@ -28,12 +29,17 @@ generate, prepare, render, parse, grade, test, and file phases. It was extracted
|
|
|
28
29
|
from the `pl-oer-lv102` course so that courses can install one uv-managed tool
|
|
29
30
|
instead of copying the harness and its support scripts.
|
|
30
31
|
|
|
31
|
-
The package installs the reusable `prairielearn_e2e` Python API and
|
|
32
|
+
The package installs the reusable `prairielearn_e2e` Python API and a single
|
|
33
|
+
`pl-e2e-test` command:
|
|
32
34
|
|
|
33
|
-
- `pl-e2e-test`
|
|
34
|
-
|
|
35
|
-
- `pl-e2e-
|
|
36
|
-
- `pl-e2e-
|
|
35
|
+
- `pl-e2e-test` (no command, or any pytest/harness options) runs the installed
|
|
36
|
+
lifecycle pytest suite.
|
|
37
|
+
- `pl-e2e-test diff` fuzzes questions selected from a Git diff.
|
|
38
|
+
- `pl-e2e-test parallel` runs local shards and merges their results.
|
|
39
|
+
- `pl-e2e-test add-seed` records a failing concrete variant seed.
|
|
40
|
+
- `pl-e2e-test reports` merges parallel shard results into terminal and JUnit reports.
|
|
41
|
+
- `pl-e2e-test version` prints the installed version.
|
|
42
|
+
- `pl-e2e-test help [COMMAND]` prints top-level help, or one command's help.
|
|
37
43
|
|
|
38
44
|
## Requirements and installation
|
|
39
45
|
|
|
@@ -92,6 +98,68 @@ uv run pl-e2e-test \
|
|
|
92
98
|
variant set with `--fuzz-seed N`. Without either option, deterministic seeds start at
|
|
93
99
|
zero. Question metadata with `singleVariant: true` always uses seed zero.
|
|
94
100
|
|
|
101
|
+
### Local parallel runs
|
|
102
|
+
|
|
103
|
+
Run multiple shards in one machine or CI job, with six concurrent processes by default:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
uv run pl-e2e-test parallel --shard-count 6 run -- \
|
|
107
|
+
--question-path questions --fuzz-seeds
|
|
108
|
+
|
|
109
|
+
uv run pl-e2e-test parallel --shard-count 6 --junitxml results.xml diff -- \
|
|
110
|
+
--base main --fuzz-seed 42
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Launcher options go before the required `run` or `diff` selector; arguments after
|
|
114
|
+
`--` are forwarded to that command. Normal runs remain deterministic. For
|
|
115
|
+
`--fuzz-seeds` and diff runs, the launcher generates one shared master seed unless
|
|
116
|
+
`--fuzz-seed` is supplied, and prints a command for replaying the run without sharding.
|
|
117
|
+
|
|
118
|
+
Each shard runs in a separate Python process using the current environment and
|
|
119
|
+
working directory. Terminals show Rich progress; redirected output prints completion
|
|
120
|
+
lines. All shards finish before reports are merged, even when a test fails. Captured
|
|
121
|
+
failure output and successful stderr are shown before the combined summary.
|
|
122
|
+
Interrupting the launcher stops and reaps its children.
|
|
123
|
+
|
|
124
|
+
Intermediate reports are temporary. Pass the launcher's `--junitxml` option to retain
|
|
125
|
+
merged JUnit output. Forwarded `--shard`, `--shard-report`, `--junitxml`, and
|
|
126
|
+
`--junit-xml` options are rejected because the launcher manages those outputs.
|
|
127
|
+
Choose the course with the working directory, or `course_root` in the Python API.
|
|
128
|
+
|
|
129
|
+
The same orchestration is available to Python callers:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from prairielearn_e2e import run_parallel
|
|
133
|
+
|
|
134
|
+
status = run_parallel(
|
|
135
|
+
["--question-path", "questions", "--fuzz-seeds"],
|
|
136
|
+
command="run",
|
|
137
|
+
shard_count=6,
|
|
138
|
+
course_root="/path/to/course",
|
|
139
|
+
junitxml="results.xml",
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`run_parallel` returns an integer exit status, including 130 on interruption.
|
|
144
|
+
Relative JUnit paths are resolved against the course root. With `junitxml=None`,
|
|
145
|
+
merged output is temporary. Invalid launcher settings raise `ValueError`.
|
|
146
|
+
|
|
147
|
+
Courses using `scripts/run_e2e_shards.py` can replace its invocation with
|
|
148
|
+
`pl-e2e-test parallel`, move `--fuzz-seed` into the forwarded arguments, and explicitly
|
|
149
|
+
pass `--fuzz-seeds` for full-suite runs to preserve the script's fuzzing behavior.
|
|
150
|
+
For example:
|
|
151
|
+
|
|
152
|
+
```make
|
|
153
|
+
test-e2e-sharded:
|
|
154
|
+
uv run --active pl-e2e-test parallel --shard-count "$(E2E_SHARD_COUNT)" run -- \
|
|
155
|
+
$(E2E_TEST_ARGS) $(E2E_PRAIRIELEARN_ARG) --fuzz-seeds $(E2E_FUZZ_SEED_ARG) $(PYTEST_ARGS)
|
|
156
|
+
|
|
157
|
+
test-e2e-fuzz-sharded:
|
|
158
|
+
uv run --active pl-e2e-test parallel --shard-count "$(E2E_SHARD_COUNT)" diff -- \
|
|
159
|
+
--base "$(E2E_DIFF_BASE)" --head "$(E2E_DIFF_HEAD)" \
|
|
160
|
+
--seed-count "$(E2E_FUZZ_SEED_COUNT)" $(E2E_FUZZ_SEED_ARG) $(E2E_PRAIRIELEARN_ARG) $(PYTEST_ARGS)
|
|
161
|
+
```
|
|
162
|
+
|
|
95
163
|
### CI sharding
|
|
96
164
|
|
|
97
165
|
Use `--shard INDEX/COUNT` to divide the selected question variants between parallel
|
|
@@ -105,7 +173,7 @@ same checkout and arguments. Sharded fuzz runs must also use one explicit shared
|
|
|
105
173
|
merge them into a combined terminal summary and JUnit report:
|
|
106
174
|
|
|
107
175
|
```sh
|
|
108
|
-
uv run pl-e2e-
|
|
176
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
109
177
|
--junitxml pl-e2e-results.xml
|
|
110
178
|
```
|
|
111
179
|
|
|
@@ -161,7 +229,7 @@ jobs:
|
|
|
161
229
|
merge-multiple: true
|
|
162
230
|
- name: Merge reports
|
|
163
231
|
run: |
|
|
164
|
-
uv run pl-e2e-
|
|
232
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
165
233
|
--junitxml pl-e2e-results.xml
|
|
166
234
|
- name: Upload merged JUnit report
|
|
167
235
|
if: ${{ always() }}
|
|
@@ -208,7 +276,7 @@ questions automatically skip only the grade check.
|
|
|
208
276
|
Record the concrete seed shown in a failing pytest case with:
|
|
209
277
|
|
|
210
278
|
```sh
|
|
211
|
-
uv run pl-e2e-add-
|
|
279
|
+
uv run pl-e2e-test add-seed \
|
|
212
280
|
questions/chapter/example 1843927501
|
|
213
281
|
```
|
|
214
282
|
|
|
@@ -221,7 +289,7 @@ The diff runner tests changed question directories. A non-ignored changed file o
|
|
|
221
289
|
`questions/` selects every question:
|
|
222
290
|
|
|
223
291
|
```sh
|
|
224
|
-
uv run pl-e2e-test
|
|
292
|
+
uv run pl-e2e-test diff \
|
|
225
293
|
--base origin/main \
|
|
226
294
|
--head HEAD \
|
|
227
295
|
--seed-count 10
|
|
@@ -247,7 +315,7 @@ To migrate from `.question-e2e.diffignore`, copy its non-comment rules into the
|
|
|
247
315
|
|
|
248
316
|
In pull-request CI, fetch the base commit before running the command and pass the
|
|
249
317
|
appropriate base and head SHAs. Set `--fuzz-seed` to replay a reported run.
|
|
250
|
-
`pl-e2e-test
|
|
318
|
+
`pl-e2e-test diff` accepts the same `--shard` and `--shard-report` options. If a diff
|
|
251
319
|
selects no questions, it writes a successful empty shard report so the merge job can
|
|
252
320
|
still verify that every matrix job completed.
|
|
253
321
|
|
|
@@ -5,12 +5,17 @@ generate, prepare, render, parse, grade, test, and file phases. It was extracted
|
|
|
5
5
|
from the `pl-oer-lv102` course so that courses can install one uv-managed tool
|
|
6
6
|
instead of copying the harness and its support scripts.
|
|
7
7
|
|
|
8
|
-
The package installs the reusable `prairielearn_e2e` Python API and
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- `pl-e2e-test
|
|
12
|
-
|
|
13
|
-
- `pl-e2e-
|
|
8
|
+
The package installs the reusable `prairielearn_e2e` Python API and a single
|
|
9
|
+
`pl-e2e-test` command:
|
|
10
|
+
|
|
11
|
+
- `pl-e2e-test` (no command, or any pytest/harness options) runs the installed
|
|
12
|
+
lifecycle pytest suite.
|
|
13
|
+
- `pl-e2e-test diff` fuzzes questions selected from a Git diff.
|
|
14
|
+
- `pl-e2e-test parallel` runs local shards and merges their results.
|
|
15
|
+
- `pl-e2e-test add-seed` records a failing concrete variant seed.
|
|
16
|
+
- `pl-e2e-test reports` merges parallel shard results into terminal and JUnit reports.
|
|
17
|
+
- `pl-e2e-test version` prints the installed version.
|
|
18
|
+
- `pl-e2e-test help [COMMAND]` prints top-level help, or one command's help.
|
|
14
19
|
|
|
15
20
|
## Requirements and installation
|
|
16
21
|
|
|
@@ -69,6 +74,68 @@ uv run pl-e2e-test \
|
|
|
69
74
|
variant set with `--fuzz-seed N`. Without either option, deterministic seeds start at
|
|
70
75
|
zero. Question metadata with `singleVariant: true` always uses seed zero.
|
|
71
76
|
|
|
77
|
+
### Local parallel runs
|
|
78
|
+
|
|
79
|
+
Run multiple shards in one machine or CI job, with six concurrent processes by default:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
uv run pl-e2e-test parallel --shard-count 6 run -- \
|
|
83
|
+
--question-path questions --fuzz-seeds
|
|
84
|
+
|
|
85
|
+
uv run pl-e2e-test parallel --shard-count 6 --junitxml results.xml diff -- \
|
|
86
|
+
--base main --fuzz-seed 42
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Launcher options go before the required `run` or `diff` selector; arguments after
|
|
90
|
+
`--` are forwarded to that command. Normal runs remain deterministic. For
|
|
91
|
+
`--fuzz-seeds` and diff runs, the launcher generates one shared master seed unless
|
|
92
|
+
`--fuzz-seed` is supplied, and prints a command for replaying the run without sharding.
|
|
93
|
+
|
|
94
|
+
Each shard runs in a separate Python process using the current environment and
|
|
95
|
+
working directory. Terminals show Rich progress; redirected output prints completion
|
|
96
|
+
lines. All shards finish before reports are merged, even when a test fails. Captured
|
|
97
|
+
failure output and successful stderr are shown before the combined summary.
|
|
98
|
+
Interrupting the launcher stops and reaps its children.
|
|
99
|
+
|
|
100
|
+
Intermediate reports are temporary. Pass the launcher's `--junitxml` option to retain
|
|
101
|
+
merged JUnit output. Forwarded `--shard`, `--shard-report`, `--junitxml`, and
|
|
102
|
+
`--junit-xml` options are rejected because the launcher manages those outputs.
|
|
103
|
+
Choose the course with the working directory, or `course_root` in the Python API.
|
|
104
|
+
|
|
105
|
+
The same orchestration is available to Python callers:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from prairielearn_e2e import run_parallel
|
|
109
|
+
|
|
110
|
+
status = run_parallel(
|
|
111
|
+
["--question-path", "questions", "--fuzz-seeds"],
|
|
112
|
+
command="run",
|
|
113
|
+
shard_count=6,
|
|
114
|
+
course_root="/path/to/course",
|
|
115
|
+
junitxml="results.xml",
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`run_parallel` returns an integer exit status, including 130 on interruption.
|
|
120
|
+
Relative JUnit paths are resolved against the course root. With `junitxml=None`,
|
|
121
|
+
merged output is temporary. Invalid launcher settings raise `ValueError`.
|
|
122
|
+
|
|
123
|
+
Courses using `scripts/run_e2e_shards.py` can replace its invocation with
|
|
124
|
+
`pl-e2e-test parallel`, move `--fuzz-seed` into the forwarded arguments, and explicitly
|
|
125
|
+
pass `--fuzz-seeds` for full-suite runs to preserve the script's fuzzing behavior.
|
|
126
|
+
For example:
|
|
127
|
+
|
|
128
|
+
```make
|
|
129
|
+
test-e2e-sharded:
|
|
130
|
+
uv run --active pl-e2e-test parallel --shard-count "$(E2E_SHARD_COUNT)" run -- \
|
|
131
|
+
$(E2E_TEST_ARGS) $(E2E_PRAIRIELEARN_ARG) --fuzz-seeds $(E2E_FUZZ_SEED_ARG) $(PYTEST_ARGS)
|
|
132
|
+
|
|
133
|
+
test-e2e-fuzz-sharded:
|
|
134
|
+
uv run --active pl-e2e-test parallel --shard-count "$(E2E_SHARD_COUNT)" diff -- \
|
|
135
|
+
--base "$(E2E_DIFF_BASE)" --head "$(E2E_DIFF_HEAD)" \
|
|
136
|
+
--seed-count "$(E2E_FUZZ_SEED_COUNT)" $(E2E_FUZZ_SEED_ARG) $(E2E_PRAIRIELEARN_ARG) $(PYTEST_ARGS)
|
|
137
|
+
```
|
|
138
|
+
|
|
72
139
|
### CI sharding
|
|
73
140
|
|
|
74
141
|
Use `--shard INDEX/COUNT` to divide the selected question variants between parallel
|
|
@@ -82,7 +149,7 @@ same checkout and arguments. Sharded fuzz runs must also use one explicit shared
|
|
|
82
149
|
merge them into a combined terminal summary and JUnit report:
|
|
83
150
|
|
|
84
151
|
```sh
|
|
85
|
-
uv run pl-e2e-
|
|
152
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
86
153
|
--junitxml pl-e2e-results.xml
|
|
87
154
|
```
|
|
88
155
|
|
|
@@ -138,7 +205,7 @@ jobs:
|
|
|
138
205
|
merge-multiple: true
|
|
139
206
|
- name: Merge reports
|
|
140
207
|
run: |
|
|
141
|
-
uv run pl-e2e-
|
|
208
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
142
209
|
--junitxml pl-e2e-results.xml
|
|
143
210
|
- name: Upload merged JUnit report
|
|
144
211
|
if: ${{ always() }}
|
|
@@ -185,7 +252,7 @@ questions automatically skip only the grade check.
|
|
|
185
252
|
Record the concrete seed shown in a failing pytest case with:
|
|
186
253
|
|
|
187
254
|
```sh
|
|
188
|
-
uv run pl-e2e-add-
|
|
255
|
+
uv run pl-e2e-test add-seed \
|
|
189
256
|
questions/chapter/example 1843927501
|
|
190
257
|
```
|
|
191
258
|
|
|
@@ -198,7 +265,7 @@ The diff runner tests changed question directories. A non-ignored changed file o
|
|
|
198
265
|
`questions/` selects every question:
|
|
199
266
|
|
|
200
267
|
```sh
|
|
201
|
-
uv run pl-e2e-test
|
|
268
|
+
uv run pl-e2e-test diff \
|
|
202
269
|
--base origin/main \
|
|
203
270
|
--head HEAD \
|
|
204
271
|
--seed-count 10
|
|
@@ -224,7 +291,7 @@ To migrate from `.question-e2e.diffignore`, copy its non-comment rules into the
|
|
|
224
291
|
|
|
225
292
|
In pull-request CI, fetch the base commit before running the command and pass the
|
|
226
293
|
appropriate base and head SHAs. Set `--fuzz-seed` to replay a reported run.
|
|
227
|
-
`pl-e2e-test
|
|
294
|
+
`pl-e2e-test diff` accepts the same `--shard` and `--shard-report` options. If a diff
|
|
228
295
|
selects no questions, it writes a successful empty shard report so the merge job can
|
|
229
296
|
still verify that every matrix job completed.
|
|
230
297
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pl-e2e-test"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "Fuzz PrairieLearn question lifecycles against the real Python element runtime"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -19,6 +19,7 @@ dependencies = [
|
|
|
19
19
|
"prairielearn",
|
|
20
20
|
"pyright>=1.1.414",
|
|
21
21
|
"pytest>=9.1",
|
|
22
|
+
"rich>=14",
|
|
22
23
|
"ruff>=0.16",
|
|
23
24
|
]
|
|
24
25
|
|
|
@@ -31,10 +32,7 @@ Issues = "https://github.com/PrairieLearn/pl-e2e-test/issues"
|
|
|
31
32
|
Repository = "https://github.com/PrairieLearn/pl-e2e-test"
|
|
32
33
|
|
|
33
34
|
[project.scripts]
|
|
34
|
-
pl-e2e-test = "prairielearn_e2e.
|
|
35
|
-
pl-e2e-test-diff = "prairielearn_e2e.fuzz:main"
|
|
36
|
-
pl-e2e-add-regression-seed = "prairielearn_e2e.regression:main"
|
|
37
|
-
pl-e2e-merge-reports = "prairielearn_e2e.reporting:main"
|
|
35
|
+
pl-e2e-test = "prairielearn_e2e.cli:main"
|
|
38
36
|
|
|
39
37
|
[tool.setuptools.packages.find]
|
|
40
38
|
where = ["src"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pl-e2e-test
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Fuzz PrairieLearn question lifecycles against the real Python element runtime
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/PrairieLearn/pl-e2e-test
|
|
@@ -18,6 +18,7 @@ Requires-Dist: platformdirs>=4.5
|
|
|
18
18
|
Requires-Dist: prairielearn
|
|
19
19
|
Requires-Dist: pyright>=1.1.414
|
|
20
20
|
Requires-Dist: pytest>=9.1
|
|
21
|
+
Requires-Dist: rich>=14
|
|
21
22
|
Requires-Dist: ruff>=0.16
|
|
22
23
|
Dynamic: license-file
|
|
23
24
|
|
|
@@ -28,12 +29,17 @@ generate, prepare, render, parse, grade, test, and file phases. It was extracted
|
|
|
28
29
|
from the `pl-oer-lv102` course so that courses can install one uv-managed tool
|
|
29
30
|
instead of copying the harness and its support scripts.
|
|
30
31
|
|
|
31
|
-
The package installs the reusable `prairielearn_e2e` Python API and
|
|
32
|
+
The package installs the reusable `prairielearn_e2e` Python API and a single
|
|
33
|
+
`pl-e2e-test` command:
|
|
32
34
|
|
|
33
|
-
- `pl-e2e-test`
|
|
34
|
-
|
|
35
|
-
- `pl-e2e-
|
|
36
|
-
- `pl-e2e-
|
|
35
|
+
- `pl-e2e-test` (no command, or any pytest/harness options) runs the installed
|
|
36
|
+
lifecycle pytest suite.
|
|
37
|
+
- `pl-e2e-test diff` fuzzes questions selected from a Git diff.
|
|
38
|
+
- `pl-e2e-test parallel` runs local shards and merges their results.
|
|
39
|
+
- `pl-e2e-test add-seed` records a failing concrete variant seed.
|
|
40
|
+
- `pl-e2e-test reports` merges parallel shard results into terminal and JUnit reports.
|
|
41
|
+
- `pl-e2e-test version` prints the installed version.
|
|
42
|
+
- `pl-e2e-test help [COMMAND]` prints top-level help, or one command's help.
|
|
37
43
|
|
|
38
44
|
## Requirements and installation
|
|
39
45
|
|
|
@@ -92,6 +98,68 @@ uv run pl-e2e-test \
|
|
|
92
98
|
variant set with `--fuzz-seed N`. Without either option, deterministic seeds start at
|
|
93
99
|
zero. Question metadata with `singleVariant: true` always uses seed zero.
|
|
94
100
|
|
|
101
|
+
### Local parallel runs
|
|
102
|
+
|
|
103
|
+
Run multiple shards in one machine or CI job, with six concurrent processes by default:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
uv run pl-e2e-test parallel --shard-count 6 run -- \
|
|
107
|
+
--question-path questions --fuzz-seeds
|
|
108
|
+
|
|
109
|
+
uv run pl-e2e-test parallel --shard-count 6 --junitxml results.xml diff -- \
|
|
110
|
+
--base main --fuzz-seed 42
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Launcher options go before the required `run` or `diff` selector; arguments after
|
|
114
|
+
`--` are forwarded to that command. Normal runs remain deterministic. For
|
|
115
|
+
`--fuzz-seeds` and diff runs, the launcher generates one shared master seed unless
|
|
116
|
+
`--fuzz-seed` is supplied, and prints a command for replaying the run without sharding.
|
|
117
|
+
|
|
118
|
+
Each shard runs in a separate Python process using the current environment and
|
|
119
|
+
working directory. Terminals show Rich progress; redirected output prints completion
|
|
120
|
+
lines. All shards finish before reports are merged, even when a test fails. Captured
|
|
121
|
+
failure output and successful stderr are shown before the combined summary.
|
|
122
|
+
Interrupting the launcher stops and reaps its children.
|
|
123
|
+
|
|
124
|
+
Intermediate reports are temporary. Pass the launcher's `--junitxml` option to retain
|
|
125
|
+
merged JUnit output. Forwarded `--shard`, `--shard-report`, `--junitxml`, and
|
|
126
|
+
`--junit-xml` options are rejected because the launcher manages those outputs.
|
|
127
|
+
Choose the course with the working directory, or `course_root` in the Python API.
|
|
128
|
+
|
|
129
|
+
The same orchestration is available to Python callers:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from prairielearn_e2e import run_parallel
|
|
133
|
+
|
|
134
|
+
status = run_parallel(
|
|
135
|
+
["--question-path", "questions", "--fuzz-seeds"],
|
|
136
|
+
command="run",
|
|
137
|
+
shard_count=6,
|
|
138
|
+
course_root="/path/to/course",
|
|
139
|
+
junitxml="results.xml",
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`run_parallel` returns an integer exit status, including 130 on interruption.
|
|
144
|
+
Relative JUnit paths are resolved against the course root. With `junitxml=None`,
|
|
145
|
+
merged output is temporary. Invalid launcher settings raise `ValueError`.
|
|
146
|
+
|
|
147
|
+
Courses using `scripts/run_e2e_shards.py` can replace its invocation with
|
|
148
|
+
`pl-e2e-test parallel`, move `--fuzz-seed` into the forwarded arguments, and explicitly
|
|
149
|
+
pass `--fuzz-seeds` for full-suite runs to preserve the script's fuzzing behavior.
|
|
150
|
+
For example:
|
|
151
|
+
|
|
152
|
+
```make
|
|
153
|
+
test-e2e-sharded:
|
|
154
|
+
uv run --active pl-e2e-test parallel --shard-count "$(E2E_SHARD_COUNT)" run -- \
|
|
155
|
+
$(E2E_TEST_ARGS) $(E2E_PRAIRIELEARN_ARG) --fuzz-seeds $(E2E_FUZZ_SEED_ARG) $(PYTEST_ARGS)
|
|
156
|
+
|
|
157
|
+
test-e2e-fuzz-sharded:
|
|
158
|
+
uv run --active pl-e2e-test parallel --shard-count "$(E2E_SHARD_COUNT)" diff -- \
|
|
159
|
+
--base "$(E2E_DIFF_BASE)" --head "$(E2E_DIFF_HEAD)" \
|
|
160
|
+
--seed-count "$(E2E_FUZZ_SEED_COUNT)" $(E2E_FUZZ_SEED_ARG) $(E2E_PRAIRIELEARN_ARG) $(PYTEST_ARGS)
|
|
161
|
+
```
|
|
162
|
+
|
|
95
163
|
### CI sharding
|
|
96
164
|
|
|
97
165
|
Use `--shard INDEX/COUNT` to divide the selected question variants between parallel
|
|
@@ -105,7 +173,7 @@ same checkout and arguments. Sharded fuzz runs must also use one explicit shared
|
|
|
105
173
|
merge them into a combined terminal summary and JUnit report:
|
|
106
174
|
|
|
107
175
|
```sh
|
|
108
|
-
uv run pl-e2e-
|
|
176
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
109
177
|
--junitxml pl-e2e-results.xml
|
|
110
178
|
```
|
|
111
179
|
|
|
@@ -161,7 +229,7 @@ jobs:
|
|
|
161
229
|
merge-multiple: true
|
|
162
230
|
- name: Merge reports
|
|
163
231
|
run: |
|
|
164
|
-
uv run pl-e2e-
|
|
232
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
165
233
|
--junitxml pl-e2e-results.xml
|
|
166
234
|
- name: Upload merged JUnit report
|
|
167
235
|
if: ${{ always() }}
|
|
@@ -208,7 +276,7 @@ questions automatically skip only the grade check.
|
|
|
208
276
|
Record the concrete seed shown in a failing pytest case with:
|
|
209
277
|
|
|
210
278
|
```sh
|
|
211
|
-
uv run pl-e2e-add-
|
|
279
|
+
uv run pl-e2e-test add-seed \
|
|
212
280
|
questions/chapter/example 1843927501
|
|
213
281
|
```
|
|
214
282
|
|
|
@@ -221,7 +289,7 @@ The diff runner tests changed question directories. A non-ignored changed file o
|
|
|
221
289
|
`questions/` selects every question:
|
|
222
290
|
|
|
223
291
|
```sh
|
|
224
|
-
uv run pl-e2e-test
|
|
292
|
+
uv run pl-e2e-test diff \
|
|
225
293
|
--base origin/main \
|
|
226
294
|
--head HEAD \
|
|
227
295
|
--seed-count 10
|
|
@@ -247,7 +315,7 @@ To migrate from `.question-e2e.diffignore`, copy its non-comment rules into the
|
|
|
247
315
|
|
|
248
316
|
In pull-request CI, fetch the base commit before running the command and pass the
|
|
249
317
|
appropriate base and head SHAs. Set `--fuzz-seed` to replay a reported run.
|
|
250
|
-
`pl-e2e-test
|
|
318
|
+
`pl-e2e-test diff` accepts the same `--shard` and `--shard-report` options. If a diff
|
|
251
319
|
selects no questions, it writes a successful empty shard report so the merge job can
|
|
252
320
|
still verify that every matrix job completed.
|
|
253
321
|
|
|
@@ -12,6 +12,7 @@ src/prairielearn_e2e/__init__.py
|
|
|
12
12
|
src/prairielearn_e2e/__main__.py
|
|
13
13
|
src/prairielearn_e2e/_git.py
|
|
14
14
|
src/prairielearn_e2e/backend.py
|
|
15
|
+
src/prairielearn_e2e/cli.py
|
|
15
16
|
src/prairielearn_e2e/config.json
|
|
16
17
|
src/prairielearn_e2e/config.py
|
|
17
18
|
src/prairielearn_e2e/contract.py
|
|
@@ -21,6 +22,7 @@ src/prairielearn_e2e/fuzz.py
|
|
|
21
22
|
src/prairielearn_e2e/harness.py
|
|
22
23
|
src/prairielearn_e2e/html_checks.py
|
|
23
24
|
src/prairielearn_e2e/metadata.py
|
|
25
|
+
src/prairielearn_e2e/parallel.py
|
|
24
26
|
src/prairielearn_e2e/pytest_plugin.py
|
|
25
27
|
src/prairielearn_e2e/question_tests.py
|
|
26
28
|
src/prairielearn_e2e/regression.py
|
|
@@ -32,6 +34,7 @@ src/prairielearn_e2e/sharding.py
|
|
|
32
34
|
tests/__init__.py
|
|
33
35
|
tests/conftest.py
|
|
34
36
|
tests/stub_git.py
|
|
37
|
+
tests/test_cli.py
|
|
35
38
|
tests/test_contract.py
|
|
36
39
|
tests/test_discovery.py
|
|
37
40
|
tests/test_fuzz.py
|
|
@@ -39,6 +42,7 @@ tests/test_git.py
|
|
|
39
42
|
tests/test_harness.py
|
|
40
43
|
tests/test_html_checks.py
|
|
41
44
|
tests/test_metadata.py
|
|
45
|
+
tests/test_parallel.py
|
|
42
46
|
tests/test_publish_version.py
|
|
43
47
|
tests/test_pytest_plugin.py
|
|
44
48
|
tests/test_regression.py
|
|
@@ -5,6 +5,7 @@ from importlib.metadata import version
|
|
|
5
5
|
from .backend import PrairieLearnBackend
|
|
6
6
|
from .discovery import QuestionCase, discover_questions
|
|
7
7
|
from .harness import LifecycleResult, QuestionHarness
|
|
8
|
+
from .parallel import run_parallel
|
|
8
9
|
|
|
9
10
|
__version__ = version("pl-e2e-test")
|
|
10
11
|
|
|
@@ -15,4 +16,5 @@ __all__ = (
|
|
|
15
16
|
"QuestionHarness",
|
|
16
17
|
"__version__",
|
|
17
18
|
"discover_questions",
|
|
19
|
+
"run_parallel",
|
|
18
20
|
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Top-level command dispatch for the ``pl-e2e-test`` console script."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from importlib.metadata import version
|
|
8
|
+
|
|
9
|
+
from . import fuzz, parallel, regression, reporting, runner
|
|
10
|
+
|
|
11
|
+
_SUBCOMMANDS = {
|
|
12
|
+
"diff": fuzz.main,
|
|
13
|
+
"parallel": parallel.main,
|
|
14
|
+
"add-seed": regression.main,
|
|
15
|
+
"reports": reporting.main,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_TOP_LEVEL_HELP = """\
|
|
19
|
+
usage: pl-e2e-test [COMMAND] [ARGS...]
|
|
20
|
+
|
|
21
|
+
With no COMMAND, runs the installed PrairieLearn question lifecycle test
|
|
22
|
+
suite; any arguments (pytest options, --question-path, --seed-count, ...)
|
|
23
|
+
are passed straight through to pytest.
|
|
24
|
+
|
|
25
|
+
Commands:
|
|
26
|
+
parallel run local shards and merge their results
|
|
27
|
+
diff fuzz questions changed between two Git refs
|
|
28
|
+
add-seed record a concrete regression seed for a question
|
|
29
|
+
reports merge parallel shard reports into one terminal/JUnit report
|
|
30
|
+
version show the installed pl-e2e-test version
|
|
31
|
+
help show this message, or `pl-e2e-test help COMMAND` for one command
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _version_command(_argv: Sequence[str]) -> int:
|
|
36
|
+
print(f"pl-e2e-test {version('pl-e2e-test')}")
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _help_command(argv: Sequence[str]) -> int:
|
|
41
|
+
if argv and argv[0] in _SUBCOMMANDS:
|
|
42
|
+
return _SUBCOMMANDS[argv[0]](["--help"])
|
|
43
|
+
print(_TOP_LEVEL_HELP, end="")
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
_COMMANDS = {**_SUBCOMMANDS, "version": _version_command, "help": _help_command}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
51
|
+
arguments = list(sys.argv[1:] if argv is None else argv)
|
|
52
|
+
if arguments and arguments[0] in _COMMANDS:
|
|
53
|
+
return _COMMANDS[arguments[0]](arguments[1:])
|
|
54
|
+
return runner.main(arguments)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
raise SystemExit(main())
|
|
@@ -195,7 +195,7 @@ def positive_integer(value: str) -> int:
|
|
|
195
195
|
|
|
196
196
|
|
|
197
197
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
198
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
198
|
+
parser = argparse.ArgumentParser(prog="pl-e2e-test diff", description=__doc__)
|
|
199
199
|
parser.add_argument("--course-root", type=Path)
|
|
200
200
|
parser.add_argument("--base", default="main", help="base Git ref (default: main)")
|
|
201
201
|
parser.add_argument("--head", default="HEAD", help="head Git ref (default: HEAD)")
|
|
@@ -274,7 +274,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
274
274
|
seeds = variant_seeds({}, args.seed_count, fuzz_seed)
|
|
275
275
|
print(f"E2E fuzz seed: {fuzz_seed}")
|
|
276
276
|
print(f"Variant seeds: {', '.join(str(seed) for seed in seeds)}")
|
|
277
|
-
replay_arguments = ["pl-e2e-test
|
|
277
|
+
replay_arguments = ["pl-e2e-test", "diff", "--fuzz-seed", str(fuzz_seed)]
|
|
278
278
|
test_arguments: list[str] = []
|
|
279
279
|
if args.prairielearn_path is not None:
|
|
280
280
|
replay_arguments.extend(
|