pl-e2e-test 0.1.0__tar.gz → 0.1.2__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.0/src/pl_e2e_test.egg-info → pl_e2e_test-0.1.2}/PKG-INFO +32 -15
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/README.md +31 -14
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/pyproject.toml +2 -5
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2/src/pl_e2e_test.egg-info}/PKG-INFO +32 -15
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/pl_e2e_test.egg-info/SOURCES.txt +2 -0
- pl_e2e_test-0.1.2/src/pl_e2e_test.egg-info/entry_points.txt +2 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/__main__.py +1 -1
- pl_e2e_test-0.1.2/src/prairielearn_e2e/cli.py +56 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/fuzz.py +37 -21
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/regression.py +1 -1
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/reporting.py +3 -2
- pl_e2e_test-0.1.2/tests/test_cli.py +81 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_fuzz.py +104 -8
- pl_e2e_test-0.1.0/src/pl_e2e_test.egg-info/entry_points.txt +0 -5
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/LICENSE +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/MANIFEST.in +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/setup.cfg +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/pl_e2e_test.egg-info/dependency_links.txt +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/pl_e2e_test.egg-info/requires.txt +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/pl_e2e_test.egg-info/top_level.txt +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/__init__.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/_git.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/backend.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/config.json +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/config.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/contract.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/discovery.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/errors.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/harness.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/html_checks.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/metadata.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/pytest_plugin.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/question_tests.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/repository.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/runner.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/server.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/src/prairielearn_e2e/sharding.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/__init__.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/conftest.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/stub_git.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_contract.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_discovery.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_git.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_harness.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_html_checks.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_metadata.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_publish_version.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_pytest_plugin.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_regression.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_reporting.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_repository.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/tests/test_server.py +0 -0
- {pl_e2e_test-0.1.0 → pl_e2e_test-0.1.2}/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.2
|
|
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
|
|
@@ -28,12 +28,16 @@ generate, prepare, render, parse, grade, test, and file phases. It was extracted
|
|
|
28
28
|
from the `pl-oer-lv102` course so that courses can install one uv-managed tool
|
|
29
29
|
instead of copying the harness and its support scripts.
|
|
30
30
|
|
|
31
|
-
The package installs the reusable `prairielearn_e2e` Python API and
|
|
31
|
+
The package installs the reusable `prairielearn_e2e` Python API and a single
|
|
32
|
+
`pl-e2e-test` command:
|
|
32
33
|
|
|
33
|
-
- `pl-e2e-test`
|
|
34
|
-
|
|
35
|
-
- `pl-e2e-
|
|
36
|
-
- `pl-e2e-
|
|
34
|
+
- `pl-e2e-test` (no command, or any pytest/harness options) runs the installed
|
|
35
|
+
lifecycle pytest suite.
|
|
36
|
+
- `pl-e2e-test diff` fuzzes questions selected from a Git diff.
|
|
37
|
+
- `pl-e2e-test add-seed` records a failing concrete variant seed.
|
|
38
|
+
- `pl-e2e-test reports` merges parallel shard results into terminal and JUnit reports.
|
|
39
|
+
- `pl-e2e-test version` prints the installed version.
|
|
40
|
+
- `pl-e2e-test help [COMMAND]` prints top-level help, or one command's help.
|
|
37
41
|
|
|
38
42
|
## Requirements and installation
|
|
39
43
|
|
|
@@ -105,7 +109,7 @@ same checkout and arguments. Sharded fuzz runs must also use one explicit shared
|
|
|
105
109
|
merge them into a combined terminal summary and JUnit report:
|
|
106
110
|
|
|
107
111
|
```sh
|
|
108
|
-
uv run pl-e2e-
|
|
112
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
109
113
|
--junitxml pl-e2e-results.xml
|
|
110
114
|
```
|
|
111
115
|
|
|
@@ -161,7 +165,7 @@ jobs:
|
|
|
161
165
|
merge-multiple: true
|
|
162
166
|
- name: Merge reports
|
|
163
167
|
run: |
|
|
164
|
-
uv run pl-e2e-
|
|
168
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
165
169
|
--junitxml pl-e2e-results.xml
|
|
166
170
|
- name: Upload merged JUnit report
|
|
167
171
|
if: ${{ always() }}
|
|
@@ -208,7 +212,7 @@ questions automatically skip only the grade check.
|
|
|
208
212
|
Record the concrete seed shown in a failing pytest case with:
|
|
209
213
|
|
|
210
214
|
```sh
|
|
211
|
-
uv run pl-e2e-add-
|
|
215
|
+
uv run pl-e2e-test add-seed \
|
|
212
216
|
questions/chapter/example 1843927501
|
|
213
217
|
```
|
|
214
218
|
|
|
@@ -221,20 +225,33 @@ The diff runner tests changed question directories. A non-ignored changed file o
|
|
|
221
225
|
`questions/` selects every question:
|
|
222
226
|
|
|
223
227
|
```sh
|
|
224
|
-
uv run pl-e2e-test
|
|
228
|
+
uv run pl-e2e-test diff \
|
|
225
229
|
--base origin/main \
|
|
226
230
|
--head HEAD \
|
|
227
231
|
--seed-count 10
|
|
228
232
|
```
|
|
229
233
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
+
Configure optional repository-relative ignore rules in the course's `pyproject.toml`:
|
|
235
|
+
|
|
236
|
+
```toml
|
|
237
|
+
[tool.pl-e2e-test]
|
|
238
|
+
diffignore = [
|
|
239
|
+
".github",
|
|
240
|
+
"README.md",
|
|
241
|
+
"*lock.yaml",
|
|
242
|
+
]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Rules use Git's native ignore syntax and are evaluated in order, so later rules may
|
|
246
|
+
negate earlier ones with `!`. A missing `pyproject.toml`, tool table, or `diffignore`
|
|
247
|
+
entry means that no paths are ignored. Additional arguments are forwarded to pytest.
|
|
248
|
+
|
|
249
|
+
To migrate from `.question-e2e.diffignore`, copy its non-comment rules into the
|
|
250
|
+
`diffignore` array. The former file and `--e2ediffignore` option are no longer used.
|
|
234
251
|
|
|
235
252
|
In pull-request CI, fetch the base commit before running the command and pass the
|
|
236
253
|
appropriate base and head SHAs. Set `--fuzz-seed` to replay a reported run.
|
|
237
|
-
`pl-e2e-test
|
|
254
|
+
`pl-e2e-test diff` accepts the same `--shard` and `--shard-report` options. If a diff
|
|
238
255
|
selects no questions, it writes a successful empty shard report so the merge job can
|
|
239
256
|
still verify that every matrix job completed.
|
|
240
257
|
|
|
@@ -5,12 +5,16 @@ 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
|
|
8
|
+
The package installs the reusable `prairielearn_e2e` Python API and a single
|
|
9
|
+
`pl-e2e-test` command:
|
|
9
10
|
|
|
10
|
-
- `pl-e2e-test`
|
|
11
|
-
|
|
12
|
-
- `pl-e2e-
|
|
13
|
-
- `pl-e2e-
|
|
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 add-seed` records a failing concrete variant seed.
|
|
15
|
+
- `pl-e2e-test reports` merges parallel shard results into terminal and JUnit reports.
|
|
16
|
+
- `pl-e2e-test version` prints the installed version.
|
|
17
|
+
- `pl-e2e-test help [COMMAND]` prints top-level help, or one command's help.
|
|
14
18
|
|
|
15
19
|
## Requirements and installation
|
|
16
20
|
|
|
@@ -82,7 +86,7 @@ same checkout and arguments. Sharded fuzz runs must also use one explicit shared
|
|
|
82
86
|
merge them into a combined terminal summary and JUnit report:
|
|
83
87
|
|
|
84
88
|
```sh
|
|
85
|
-
uv run pl-e2e-
|
|
89
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
86
90
|
--junitxml pl-e2e-results.xml
|
|
87
91
|
```
|
|
88
92
|
|
|
@@ -138,7 +142,7 @@ jobs:
|
|
|
138
142
|
merge-multiple: true
|
|
139
143
|
- name: Merge reports
|
|
140
144
|
run: |
|
|
141
|
-
uv run pl-e2e-
|
|
145
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
142
146
|
--junitxml pl-e2e-results.xml
|
|
143
147
|
- name: Upload merged JUnit report
|
|
144
148
|
if: ${{ always() }}
|
|
@@ -185,7 +189,7 @@ questions automatically skip only the grade check.
|
|
|
185
189
|
Record the concrete seed shown in a failing pytest case with:
|
|
186
190
|
|
|
187
191
|
```sh
|
|
188
|
-
uv run pl-e2e-add-
|
|
192
|
+
uv run pl-e2e-test add-seed \
|
|
189
193
|
questions/chapter/example 1843927501
|
|
190
194
|
```
|
|
191
195
|
|
|
@@ -198,20 +202,33 @@ The diff runner tests changed question directories. A non-ignored changed file o
|
|
|
198
202
|
`questions/` selects every question:
|
|
199
203
|
|
|
200
204
|
```sh
|
|
201
|
-
uv run pl-e2e-test
|
|
205
|
+
uv run pl-e2e-test diff \
|
|
202
206
|
--base origin/main \
|
|
203
207
|
--head HEAD \
|
|
204
208
|
--seed-count 10
|
|
205
209
|
```
|
|
206
210
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
+
Configure optional repository-relative ignore rules in the course's `pyproject.toml`:
|
|
212
|
+
|
|
213
|
+
```toml
|
|
214
|
+
[tool.pl-e2e-test]
|
|
215
|
+
diffignore = [
|
|
216
|
+
".github",
|
|
217
|
+
"README.md",
|
|
218
|
+
"*lock.yaml",
|
|
219
|
+
]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Rules use Git's native ignore syntax and are evaluated in order, so later rules may
|
|
223
|
+
negate earlier ones with `!`. A missing `pyproject.toml`, tool table, or `diffignore`
|
|
224
|
+
entry means that no paths are ignored. Additional arguments are forwarded to pytest.
|
|
225
|
+
|
|
226
|
+
To migrate from `.question-e2e.diffignore`, copy its non-comment rules into the
|
|
227
|
+
`diffignore` array. The former file and `--e2ediffignore` option are no longer used.
|
|
211
228
|
|
|
212
229
|
In pull-request CI, fetch the base commit before running the command and pass the
|
|
213
230
|
appropriate base and head SHAs. Set `--fuzz-seed` to replay a reported run.
|
|
214
|
-
`pl-e2e-test
|
|
231
|
+
`pl-e2e-test diff` accepts the same `--shard` and `--shard-report` options. If a diff
|
|
215
232
|
selects no questions, it writes a successful empty shard report so the merge job can
|
|
216
233
|
still verify that every matrix job completed.
|
|
217
234
|
|
|
@@ -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.2"
|
|
8
8
|
description = "Fuzz PrairieLearn question lifecycles against the real Python element runtime"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -31,10 +31,7 @@ Issues = "https://github.com/PrairieLearn/pl-e2e-test/issues"
|
|
|
31
31
|
Repository = "https://github.com/PrairieLearn/pl-e2e-test"
|
|
32
32
|
|
|
33
33
|
[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"
|
|
34
|
+
pl-e2e-test = "prairielearn_e2e.cli:main"
|
|
38
35
|
|
|
39
36
|
[tool.setuptools.packages.find]
|
|
40
37
|
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.2
|
|
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
|
|
@@ -28,12 +28,16 @@ generate, prepare, render, parse, grade, test, and file phases. It was extracted
|
|
|
28
28
|
from the `pl-oer-lv102` course so that courses can install one uv-managed tool
|
|
29
29
|
instead of copying the harness and its support scripts.
|
|
30
30
|
|
|
31
|
-
The package installs the reusable `prairielearn_e2e` Python API and
|
|
31
|
+
The package installs the reusable `prairielearn_e2e` Python API and a single
|
|
32
|
+
`pl-e2e-test` command:
|
|
32
33
|
|
|
33
|
-
- `pl-e2e-test`
|
|
34
|
-
|
|
35
|
-
- `pl-e2e-
|
|
36
|
-
- `pl-e2e-
|
|
34
|
+
- `pl-e2e-test` (no command, or any pytest/harness options) runs the installed
|
|
35
|
+
lifecycle pytest suite.
|
|
36
|
+
- `pl-e2e-test diff` fuzzes questions selected from a Git diff.
|
|
37
|
+
- `pl-e2e-test add-seed` records a failing concrete variant seed.
|
|
38
|
+
- `pl-e2e-test reports` merges parallel shard results into terminal and JUnit reports.
|
|
39
|
+
- `pl-e2e-test version` prints the installed version.
|
|
40
|
+
- `pl-e2e-test help [COMMAND]` prints top-level help, or one command's help.
|
|
37
41
|
|
|
38
42
|
## Requirements and installation
|
|
39
43
|
|
|
@@ -105,7 +109,7 @@ same checkout and arguments. Sharded fuzz runs must also use one explicit shared
|
|
|
105
109
|
merge them into a combined terminal summary and JUnit report:
|
|
106
110
|
|
|
107
111
|
```sh
|
|
108
|
-
uv run pl-e2e-
|
|
112
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
109
113
|
--junitxml pl-e2e-results.xml
|
|
110
114
|
```
|
|
111
115
|
|
|
@@ -161,7 +165,7 @@ jobs:
|
|
|
161
165
|
merge-multiple: true
|
|
162
166
|
- name: Merge reports
|
|
163
167
|
run: |
|
|
164
|
-
uv run pl-e2e-
|
|
168
|
+
uv run pl-e2e-test reports e2e-reports \
|
|
165
169
|
--junitxml pl-e2e-results.xml
|
|
166
170
|
- name: Upload merged JUnit report
|
|
167
171
|
if: ${{ always() }}
|
|
@@ -208,7 +212,7 @@ questions automatically skip only the grade check.
|
|
|
208
212
|
Record the concrete seed shown in a failing pytest case with:
|
|
209
213
|
|
|
210
214
|
```sh
|
|
211
|
-
uv run pl-e2e-add-
|
|
215
|
+
uv run pl-e2e-test add-seed \
|
|
212
216
|
questions/chapter/example 1843927501
|
|
213
217
|
```
|
|
214
218
|
|
|
@@ -221,20 +225,33 @@ The diff runner tests changed question directories. A non-ignored changed file o
|
|
|
221
225
|
`questions/` selects every question:
|
|
222
226
|
|
|
223
227
|
```sh
|
|
224
|
-
uv run pl-e2e-test
|
|
228
|
+
uv run pl-e2e-test diff \
|
|
225
229
|
--base origin/main \
|
|
226
230
|
--head HEAD \
|
|
227
231
|
--seed-count 10
|
|
228
232
|
```
|
|
229
233
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
+
Configure optional repository-relative ignore rules in the course's `pyproject.toml`:
|
|
235
|
+
|
|
236
|
+
```toml
|
|
237
|
+
[tool.pl-e2e-test]
|
|
238
|
+
diffignore = [
|
|
239
|
+
".github",
|
|
240
|
+
"README.md",
|
|
241
|
+
"*lock.yaml",
|
|
242
|
+
]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Rules use Git's native ignore syntax and are evaluated in order, so later rules may
|
|
246
|
+
negate earlier ones with `!`. A missing `pyproject.toml`, tool table, or `diffignore`
|
|
247
|
+
entry means that no paths are ignored. Additional arguments are forwarded to pytest.
|
|
248
|
+
|
|
249
|
+
To migrate from `.question-e2e.diffignore`, copy its non-comment rules into the
|
|
250
|
+
`diffignore` array. The former file and `--e2ediffignore` option are no longer used.
|
|
234
251
|
|
|
235
252
|
In pull-request CI, fetch the base commit before running the command and pass the
|
|
236
253
|
appropriate base and head SHAs. Set `--fuzz-seed` to replay a reported run.
|
|
237
|
-
`pl-e2e-test
|
|
254
|
+
`pl-e2e-test diff` accepts the same `--shard` and `--shard-report` options. If a diff
|
|
238
255
|
selects no questions, it writes a successful empty shard report so the merge job can
|
|
239
256
|
still verify that every matrix job completed.
|
|
240
257
|
|
|
@@ -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
|
|
@@ -32,6 +33,7 @@ src/prairielearn_e2e/sharding.py
|
|
|
32
33
|
tests/__init__.py
|
|
33
34
|
tests/conftest.py
|
|
34
35
|
tests/stub_git.py
|
|
36
|
+
tests/test_cli.py
|
|
35
37
|
tests/test_contract.py
|
|
36
38
|
tests/test_discovery.py
|
|
37
39
|
tests/test_fuzz.py
|
|
@@ -0,0 +1,56 @@
|
|
|
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, regression, reporting, runner
|
|
10
|
+
|
|
11
|
+
_SUBCOMMANDS = {
|
|
12
|
+
"diff": fuzz.main,
|
|
13
|
+
"add-seed": regression.main,
|
|
14
|
+
"reports": reporting.main,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_TOP_LEVEL_HELP = """\
|
|
18
|
+
usage: pl-e2e-test [COMMAND] [ARGS...]
|
|
19
|
+
|
|
20
|
+
With no COMMAND, runs the installed PrairieLearn question lifecycle test
|
|
21
|
+
suite; any arguments (pytest options, --question-path, --seed-count, ...)
|
|
22
|
+
are passed straight through to pytest.
|
|
23
|
+
|
|
24
|
+
Commands:
|
|
25
|
+
diff fuzz questions changed between two Git refs
|
|
26
|
+
add-seed record a concrete regression seed for a question
|
|
27
|
+
reports merge parallel shard reports into one terminal/JUnit report
|
|
28
|
+
version show the installed pl-e2e-test version
|
|
29
|
+
help show this message, or `pl-e2e-test help COMMAND` for one command
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _version_command(_argv: Sequence[str]) -> int:
|
|
34
|
+
print(f"pl-e2e-test {version('pl-e2e-test')}")
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _help_command(argv: Sequence[str]) -> int:
|
|
39
|
+
if argv and argv[0] in _SUBCOMMANDS:
|
|
40
|
+
return _SUBCOMMANDS[argv[0]](["--help"])
|
|
41
|
+
print(_TOP_LEVEL_HELP, end="")
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_COMMANDS = {**_SUBCOMMANDS, "version": _version_command, "help": _help_command}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
49
|
+
arguments = list(sys.argv[1:] if argv is None else argv)
|
|
50
|
+
if arguments and arguments[0] in _COMMANDS:
|
|
51
|
+
return _COMMANDS[arguments[0]](arguments[1:])
|
|
52
|
+
return runner.main(arguments)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
raise SystemExit(main())
|
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import secrets
|
|
8
8
|
import shlex
|
|
9
9
|
import subprocess
|
|
10
|
+
import tomllib
|
|
10
11
|
from collections.abc import Sequence
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from tempfile import TemporaryDirectory
|
|
@@ -17,7 +18,7 @@ from .runner import run_tests
|
|
|
17
18
|
from .sharding import Shard, parse_shard
|
|
18
19
|
|
|
19
20
|
QUESTIONS_ROOT = Path("questions")
|
|
20
|
-
|
|
21
|
+
PYPROJECT_CONFIG = Path("pyproject.toml")
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def git_repository_root(start: Path) -> Path:
|
|
@@ -32,18 +33,37 @@ def git_repository_root(start: Path) -> Path:
|
|
|
32
33
|
return Path(result.stdout.strip()).resolve()
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
def load_e2e_diff_ignore(config_path: Path
|
|
36
|
-
"""Load Git-style ignore rules
|
|
36
|
+
def load_e2e_diff_ignore(config_path: Path) -> list[str]:
|
|
37
|
+
"""Load Git-style ignore rules from a course's pyproject.toml."""
|
|
37
38
|
|
|
38
39
|
try:
|
|
39
|
-
|
|
40
|
+
with config_path.open("rb") as config_file:
|
|
41
|
+
config = tomllib.load(config_file)
|
|
40
42
|
except FileNotFoundError:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
raise ValueError(f"Unable to read {config_path}:
|
|
43
|
+
return []
|
|
44
|
+
except tomllib.TOMLDecodeError as exc:
|
|
45
|
+
raise ValueError(f"Unable to read {config_path}: invalid TOML: {exc}") from exc
|
|
44
46
|
except OSError as exc:
|
|
45
47
|
raise ValueError(f"Unable to read {config_path}: {exc}") from exc
|
|
46
48
|
|
|
49
|
+
tool_config = config.get("tool", {})
|
|
50
|
+
if not isinstance(tool_config, dict):
|
|
51
|
+
raise TypeError(f"Invalid configuration in {config_path}: tool must be a table")
|
|
52
|
+
e2e_config = tool_config.get("pl-e2e-test", {})
|
|
53
|
+
if not isinstance(e2e_config, dict):
|
|
54
|
+
raise TypeError(
|
|
55
|
+
f"Invalid configuration in {config_path}: tool.pl-e2e-test must be a table"
|
|
56
|
+
)
|
|
57
|
+
ignore_rules = e2e_config.get("diffignore", [])
|
|
58
|
+
if not isinstance(ignore_rules, list) or not all(
|
|
59
|
+
isinstance(rule, str) for rule in ignore_rules
|
|
60
|
+
):
|
|
61
|
+
raise TypeError(
|
|
62
|
+
f"Invalid configuration in {config_path}: "
|
|
63
|
+
"tool.pl-e2e-test.diffignore must be an array of strings"
|
|
64
|
+
)
|
|
65
|
+
return ignore_rules
|
|
66
|
+
|
|
47
67
|
|
|
48
68
|
def e2e_diff_ignored_files(
|
|
49
69
|
changed_files: Sequence[Path], ignore_rules: Sequence[str]
|
|
@@ -175,7 +195,7 @@ def positive_integer(value: str) -> int:
|
|
|
175
195
|
|
|
176
196
|
|
|
177
197
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
178
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
198
|
+
parser = argparse.ArgumentParser(prog="pl-e2e-test diff", description=__doc__)
|
|
179
199
|
parser.add_argument("--course-root", type=Path)
|
|
180
200
|
parser.add_argument("--base", default="main", help="base Git ref (default: main)")
|
|
181
201
|
parser.add_argument("--head", default="HEAD", help="head Git ref (default: HEAD)")
|
|
@@ -195,8 +215,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
195
215
|
)
|
|
196
216
|
parser.add_argument(
|
|
197
217
|
"--e2ediffignore",
|
|
198
|
-
|
|
199
|
-
help="E2E change-detection ignore file (default: COURSE_ROOT/.question-e2e.diffignore)",
|
|
218
|
+
help=argparse.SUPPRESS,
|
|
200
219
|
)
|
|
201
220
|
parser.add_argument(
|
|
202
221
|
"--shard",
|
|
@@ -211,6 +230,11 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
211
230
|
help="write a mergeable JSON result blob for this shard",
|
|
212
231
|
)
|
|
213
232
|
args, pytest_args = parser.parse_known_args(argv)
|
|
233
|
+
if args.e2ediffignore is not None:
|
|
234
|
+
parser.error(
|
|
235
|
+
"--e2ediffignore has been removed; configure "
|
|
236
|
+
"tool.pl-e2e-test.diffignore in COURSE_ROOT/pyproject.toml"
|
|
237
|
+
)
|
|
214
238
|
shard: Shard | None = args.shard
|
|
215
239
|
if args.shard_report is not None and shard is None:
|
|
216
240
|
parser.error("--shard-report requires --shard")
|
|
@@ -226,17 +250,9 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
226
250
|
except (OSError, subprocess.CalledProcessError) as exc:
|
|
227
251
|
parser.error(f"could not find the course Git repository: {exc}")
|
|
228
252
|
|
|
229
|
-
ignore_path = args.e2ediffignore
|
|
230
|
-
if ignore_path is None:
|
|
231
|
-
ignore_path = repository_root / DEFAULT_E2E_DIFF_IGNORE
|
|
232
|
-
missing_ok = True
|
|
233
|
-
else:
|
|
234
|
-
if not ignore_path.is_absolute():
|
|
235
|
-
ignore_path = repository_root / ignore_path
|
|
236
|
-
missing_ok = False
|
|
237
253
|
try:
|
|
238
|
-
ignored_patterns = load_e2e_diff_ignore(
|
|
239
|
-
except ValueError as exc:
|
|
254
|
+
ignored_patterns = load_e2e_diff_ignore(repository_root / PYPROJECT_CONFIG)
|
|
255
|
+
except (TypeError, ValueError) as exc:
|
|
240
256
|
parser.error(str(exc))
|
|
241
257
|
|
|
242
258
|
changed_files = changed_files_between(args.base, args.head, repository_root)
|
|
@@ -258,7 +274,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
258
274
|
seeds = variant_seeds({}, args.seed_count, fuzz_seed)
|
|
259
275
|
print(f"E2E fuzz seed: {fuzz_seed}")
|
|
260
276
|
print(f"Variant seeds: {', '.join(str(seed) for seed in seeds)}")
|
|
261
|
-
replay_arguments = ["pl-e2e-test
|
|
277
|
+
replay_arguments = ["pl-e2e-test", "diff", "--fuzz-seed", str(fuzz_seed)]
|
|
262
278
|
test_arguments: list[str] = []
|
|
263
279
|
if args.prairielearn_path is not None:
|
|
264
280
|
replay_arguments.extend(
|
|
@@ -96,7 +96,7 @@ def add_regression_seed(course_root: Path, question_path: Path, seed: int) -> bo
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
99
|
-
parser = argparse.ArgumentParser(description=__doc__)
|
|
99
|
+
parser = argparse.ArgumentParser(prog="pl-e2e-test add-seed", description=__doc__)
|
|
100
100
|
parser.add_argument("question_path", type=Path)
|
|
101
101
|
parser.add_argument("variant_seed", type=_variant_seed)
|
|
102
102
|
parser.add_argument("--course-root", type=Path)
|
|
@@ -659,7 +659,8 @@ def merge_reports(inputs: Sequence[Path], junitxml: Path) -> int:
|
|
|
659
659
|
|
|
660
660
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
661
661
|
parser = argparse.ArgumentParser(
|
|
662
|
-
|
|
662
|
+
prog="pl-e2e-test reports",
|
|
663
|
+
description="Merge pl-e2e-test shard reports into one terminal and JUnit report.",
|
|
663
664
|
)
|
|
664
665
|
parser.add_argument("reports", nargs="+", type=Path)
|
|
665
666
|
parser.add_argument("--junitxml", type=Path, default=Path("pl-e2e-results.xml"))
|
|
@@ -667,7 +668,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
667
668
|
try:
|
|
668
669
|
return merge_reports(args.reports, args.junitxml)
|
|
669
670
|
except (OSError, ReportError) as exc:
|
|
670
|
-
print(f"pl-e2e-
|
|
671
|
+
print(f"pl-e2e-test reports: error: {exc}", file=sys.stderr)
|
|
671
672
|
return 2
|
|
672
673
|
|
|
673
674
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from importlib.metadata import version
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from prairielearn_e2e import cli
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _stub(calls: list[Sequence[str]], result: int = 0):
|
|
12
|
+
def _main(argv: Sequence[str]) -> int:
|
|
13
|
+
calls.append(list(argv))
|
|
14
|
+
return result
|
|
15
|
+
|
|
16
|
+
return _main
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize("command", ["diff", "add-seed", "reports"])
|
|
20
|
+
def test_dispatches_known_commands_to_their_module(
|
|
21
|
+
monkeypatch: pytest.MonkeyPatch, command: str
|
|
22
|
+
) -> None:
|
|
23
|
+
calls: list[Sequence[str]] = []
|
|
24
|
+
monkeypatch.setitem(cli._COMMANDS, command, _stub(calls, result=3))
|
|
25
|
+
|
|
26
|
+
assert cli.main([command, "--example", "value"]) == 3
|
|
27
|
+
assert calls == [["--example", "value"]]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_no_command_falls_through_to_the_test_runner(
|
|
31
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
32
|
+
) -> None:
|
|
33
|
+
calls: list[Sequence[str]] = []
|
|
34
|
+
monkeypatch.setattr(cli, "runner", type("_Runner", (), {"main": _stub(calls)}))
|
|
35
|
+
|
|
36
|
+
assert cli.main(["--question-path", "questions/example", "-k", "sample"]) == 0
|
|
37
|
+
assert calls == [["--question-path", "questions/example", "-k", "sample"]]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_bare_invocation_falls_through_to_the_test_runner(
|
|
41
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
42
|
+
) -> None:
|
|
43
|
+
calls: list[Sequence[str]] = []
|
|
44
|
+
monkeypatch.setattr(cli, "runner", type("_Runner", (), {"main": _stub(calls)}))
|
|
45
|
+
|
|
46
|
+
assert cli.main([]) == 0
|
|
47
|
+
assert calls == [[]]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_version_command_prints_the_installed_version(
|
|
51
|
+
capsys: pytest.CaptureFixture[str],
|
|
52
|
+
) -> None:
|
|
53
|
+
assert cli.main(["version"]) == 0
|
|
54
|
+
assert capsys.readouterr().out == f"pl-e2e-test {version('pl-e2e-test')}\n"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_help_command_with_no_argument_prints_top_level_help(
|
|
58
|
+
capsys: pytest.CaptureFixture[str],
|
|
59
|
+
) -> None:
|
|
60
|
+
assert cli.main(["help"]) == 0
|
|
61
|
+
out = capsys.readouterr().out
|
|
62
|
+
assert "Commands:" in out
|
|
63
|
+
for command in ("diff", "add-seed", "reports", "version", "help"):
|
|
64
|
+
assert command in out
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_help_command_delegates_to_a_known_subcommand(
|
|
68
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
69
|
+
) -> None:
|
|
70
|
+
calls: list[Sequence[str]] = []
|
|
71
|
+
monkeypatch.setitem(cli._SUBCOMMANDS, "diff", _stub(calls))
|
|
72
|
+
|
|
73
|
+
assert cli.main(["help", "diff"]) == 0
|
|
74
|
+
assert calls == [["--help"]]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_help_command_with_unknown_argument_prints_top_level_help(
|
|
78
|
+
capsys: pytest.CaptureFixture[str],
|
|
79
|
+
) -> None:
|
|
80
|
+
assert cli.main(["help", "not-a-command"]) == 0
|
|
81
|
+
assert "Commands:" in capsys.readouterr().out
|
|
@@ -65,14 +65,83 @@ def test_root_anchored_gitignore_pattern() -> None:
|
|
|
65
65
|
) == {Path("README.md")}
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def
|
|
69
|
-
assert (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
def test_later_gitignore_pattern_can_negate_earlier_pattern() -> None:
|
|
69
|
+
assert fuzz.e2e_diff_ignored_files(
|
|
70
|
+
[Path("uv.lock"), Path("required.lock")], ["*.lock", "!required.lock"]
|
|
71
|
+
) == {Path("uv.lock")}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_loads_diff_ignore_from_pyproject(tmp_path: Path) -> None:
|
|
75
|
+
config_path = tmp_path / "pyproject.toml"
|
|
76
|
+
config_path.write_text(
|
|
77
|
+
"""
|
|
78
|
+
[tool.pl-e2e-test]
|
|
79
|
+
diffignore = ["/README.md", "*.lock", "!required.lock"]
|
|
80
|
+
""",
|
|
81
|
+
encoding="utf-8",
|
|
74
82
|
)
|
|
75
83
|
|
|
84
|
+
assert fuzz.load_e2e_diff_ignore(config_path) == [
|
|
85
|
+
"/README.md",
|
|
86
|
+
"*.lock",
|
|
87
|
+
"!required.lock",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.parametrize(
|
|
92
|
+
"contents",
|
|
93
|
+
[
|
|
94
|
+
"",
|
|
95
|
+
"[tool.pytest.ini_options]\n",
|
|
96
|
+
"[tool.pl-e2e-test]\n",
|
|
97
|
+
"[tool.pl-e2e-test]\ndiffignore = []\n",
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
def test_missing_or_empty_diff_ignore_configuration_is_empty(
|
|
101
|
+
tmp_path: Path, contents: str
|
|
102
|
+
) -> None:
|
|
103
|
+
config_path = tmp_path / "pyproject.toml"
|
|
104
|
+
config_path.write_text(contents, encoding="utf-8")
|
|
105
|
+
|
|
106
|
+
assert fuzz.load_e2e_diff_ignore(config_path) == []
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_missing_pyproject_is_empty(tmp_path: Path) -> None:
|
|
110
|
+
assert fuzz.load_e2e_diff_ignore(tmp_path / "pyproject.toml") == []
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_invalid_pyproject_is_rejected(tmp_path: Path) -> None:
|
|
114
|
+
config_path = tmp_path / "pyproject.toml"
|
|
115
|
+
config_path.write_text("[tool.pl-e2e-test\n", encoding="utf-8")
|
|
116
|
+
|
|
117
|
+
with pytest.raises(ValueError, match="invalid TOML"):
|
|
118
|
+
fuzz.load_e2e_diff_ignore(config_path)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_unreadable_pyproject_is_rejected(tmp_path: Path) -> None:
|
|
122
|
+
config_path = tmp_path / "pyproject.toml"
|
|
123
|
+
config_path.mkdir()
|
|
124
|
+
|
|
125
|
+
with pytest.raises(ValueError, match="Unable to read"):
|
|
126
|
+
fuzz.load_e2e_diff_ignore(config_path)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.mark.parametrize(
|
|
130
|
+
"configured_value",
|
|
131
|
+
['"README.md"', '["README.md", 1]'],
|
|
132
|
+
)
|
|
133
|
+
def test_diff_ignore_must_be_an_array_of_strings(
|
|
134
|
+
tmp_path: Path, configured_value: str
|
|
135
|
+
) -> None:
|
|
136
|
+
config_path = tmp_path / "pyproject.toml"
|
|
137
|
+
config_path.write_text(
|
|
138
|
+
f"[tool.pl-e2e-test]\ndiffignore = {configured_value}\n",
|
|
139
|
+
encoding="utf-8",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
with pytest.raises(TypeError, match="must be an array of strings"):
|
|
143
|
+
fuzz.load_e2e_diff_ignore(config_path)
|
|
144
|
+
|
|
76
145
|
|
|
77
146
|
def test_main_forwards_selected_questions_and_seed(
|
|
78
147
|
monkeypatch: pytest.MonkeyPatch,
|
|
@@ -82,11 +151,16 @@ def test_main_forwards_selected_questions_and_seed(
|
|
|
82
151
|
question = tmp_path / "questions" / "chapter" / "example"
|
|
83
152
|
question.mkdir(parents=True)
|
|
84
153
|
(question / "info.json").write_text("{}", encoding="utf-8")
|
|
154
|
+
(tmp_path / "pyproject.toml").write_text(
|
|
155
|
+
'[tool.pl-e2e-test]\ndiffignore = ["README.md"]\n',
|
|
156
|
+
encoding="utf-8",
|
|
157
|
+
)
|
|
85
158
|
monkeypatch.setattr(
|
|
86
159
|
fuzz,
|
|
87
160
|
"changed_files_between",
|
|
88
161
|
lambda base, head, repository_root: [
|
|
89
|
-
Path("
|
|
162
|
+
Path("README.md"),
|
|
163
|
+
Path("questions/chapter/example/server.py"),
|
|
90
164
|
],
|
|
91
165
|
)
|
|
92
166
|
calls: list[tuple[list[str], Path]] = []
|
|
@@ -131,6 +205,28 @@ def test_main_forwards_selected_questions_and_seed(
|
|
|
131
205
|
assert "E2E fuzz seed: 12345" in capsys.readouterr().out
|
|
132
206
|
|
|
133
207
|
|
|
208
|
+
def test_main_reports_invalid_pyproject(
|
|
209
|
+
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
210
|
+
) -> None:
|
|
211
|
+
(tmp_path / "pyproject.toml").write_text("[tool.pl-e2e-test\n", encoding="utf-8")
|
|
212
|
+
|
|
213
|
+
with pytest.raises(SystemExit, match="2"):
|
|
214
|
+
fuzz.main(["--course-root", str(tmp_path)])
|
|
215
|
+
|
|
216
|
+
assert "invalid TOML" in capsys.readouterr().err
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_removed_diff_ignore_option_reports_migration(
|
|
220
|
+
capsys: pytest.CaptureFixture[str],
|
|
221
|
+
) -> None:
|
|
222
|
+
with pytest.raises(SystemExit, match="2"):
|
|
223
|
+
fuzz.main(["--e2ediffignore", ".question-e2e.diffignore"])
|
|
224
|
+
|
|
225
|
+
error = capsys.readouterr().err
|
|
226
|
+
assert "--e2ediffignore has been removed" in error
|
|
227
|
+
assert "tool.pl-e2e-test.diffignore" in error
|
|
228
|
+
|
|
229
|
+
|
|
134
230
|
def test_main_uses_managed_cache_when_prairielearn_path_is_omitted(
|
|
135
231
|
monkeypatch: pytest.MonkeyPatch,
|
|
136
232
|
tmp_path: Path,
|
|
@@ -182,7 +278,7 @@ def test_main_uses_managed_cache_when_prairielearn_path_is_omitted(
|
|
|
182
278
|
for line in capsys.readouterr().out.splitlines()
|
|
183
279
|
if line.startswith("Replay")
|
|
184
280
|
)
|
|
185
|
-
assert replay == "Replay with: pl-e2e-test
|
|
281
|
+
assert replay == "Replay with: pl-e2e-test diff --fuzz-seed 9876"
|
|
186
282
|
|
|
187
283
|
|
|
188
284
|
def test_empty_diff_does_not_generate_a_seed(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|