You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Document scored exercises and update submission_tests examples
Add a "Scored Exercises" section covering TestResult, scoring policies,
check vs. submit behavior adaptation, and multi-task scored exercises.
Update existing examples to use current decorator names (environment_test,
submission_test) and task_name parameter instead of deprecated aliases.
The Python file imports `ref_utils`, which provides two types of tests and various convenience functions.
109
+
110
+
Functions are converted into either an `environment test` or a `submission test` by using the respective decorator, which registers them. When testing a submission, first all environment tests are run. If one fails, testing is aborted (and the student informed about the failure). In the example above, the environment test checks whether an executable called `shellcode` exists. Once all environment tests pass, the submission test is executed. This two-stage design lets you first verify prerequisites (e.g., that specific binaries have been compiled) before checking whether their behavior matches the expected one.
111
+
112
+
When needed, both decorators accept an optional `task_name` argument (e.g., `@submission_test(task_name="part_one")`) by which specific tests can be grouped into independent tasks. A failure in task `"part_one"` will not abort the running of `"part_two"`. Each task can have multiple `@environment_test` functions but only one `@submission_test`.
119
113
114
+
Finally, `submission_tests` needs to call `rf.run_tests()` to execute all registered tests. To avoid leaking critical information (when hitting unexpected conditions in the submission tests themselves), `ref_utils` suppresses error output using `rf.ref_util_install_global_exception_hook()`. The `ref_utils` module provides various convenience functions, such as colored printing (`print_err`, `print_warn`, `print_ok`) or executing binaries with a specific payload (`rf.run_with_payload(..)`).
115
+
116
+
### Scored Exercises
117
+
118
+
The example above uses a boolean return value — the submission either passes or fails. For exercises that need a numeric score (e.g., code coverage percentage, number of tests passed, performance benchmarks), the `@submission_test` function can return a `TestResult` instead of a `bool`.
119
+
120
+
A `TestResult` carries two fields:
121
+
122
+
-`success` (`bool`) — whether the submission is considered successful.
123
+
-`score` (`float | None`) — the numeric score achieved. This value is recorded per task and displayed on the scoreboard.
coverage = run_coverage_measurement() # your scoring logic here
155
+
print_ok(f"[+] You got {coverage:.02f}% coverage")
156
+
return TestResult(success=True, score=coverage)
157
+
158
+
rf.run_tests()
120
159
```
121
160
122
-
There's a lot going on, so let's dissect this step-by-step. The Python file needs to import ref_utils, which provides two types of tests and various convenience functions.
161
+
The key differences from a pass/fail test:
123
162
124
-
Functions are converted into either an `environment test` or a `submission test` by using the respective decorator, which registers them. Conceptually, these two types are similar. When testing a submission, first all environment tests are run. If one fails, testing is aborted (and the user informed about the failure). In our example code above, the environment test merely checks whether the student created an executable called `shellcode`. Once all environment tests pass, the submission test(s) will be executed. This two-stage design enables to first test whether all prerequisites are in-place (for example, specific binaries have been compiled) via the environment tests before then checking whether their behavior matches the expected one via the submission tests. A failure in any test will abort the execution of subsequent ones.
163
+
1. Import `TestResult` from `ref_utils`.
164
+
2. Annotate the `@submission_test` function to return `TestResult` instead of `bool`.
165
+
3. Return `TestResult(success=..., score=...)` where `score` is the raw numeric value.
125
166
126
-
When needed, both decorators accept an optional `group: str` argument (e.g., `@add_submission_test(group="task_part_one")`) by which specific tests can be grouped. Grouping allows to run multiple, independent test groups; in particular, a failure in test group "task_part_one" will not abort the running of "task_part_two".
167
+
The raw score is stored in the database and shown on the scoreboard. Admins can optionally configure per-task **scoring policies**in the web interface to transform raw scores into final points. The available scoring modes are:
127
168
169
+
| Mode | Description | Parameters |
170
+
|------|-------------|------------|
171
+
|`none` (default) | Pass raw score through unchanged | — |
172
+
|`linear`| Linearly map a raw score range to points: `(raw - min_raw) / (max_raw - min_raw) * max_points`, clamped to `[0, max_points]`|`min_raw`, `max_raw`, `max_points`|
173
+
|`threshold`| Award fixed points if raw score meets a threshold, otherwise 0 |`threshold`, `points`|
174
+
|`tiered`| Multiple threshold tiers; the highest matching tier's points are awarded |`tiers` (list of `{above, points}`) |
175
+
|`discard`| Omit the task from scoring entirely (contributes 0, hidden from breakdown) | — |
176
+
177
+
#### Adapting behavior based on check vs. submit
178
+
179
+
Students can run `task check` (quick feedback loop) or `task submit` (final graded submission). Use `rf.test_result_will_be_submitted()` to detect which mode the test is running in and adjust accordingly (e.g., run a shorter measurement during check, full measurement during submit):
180
+
181
+
```Python
182
+
@submission_test() # type:ignore
183
+
deftest_submission() -> TestResult:
184
+
if rf.test_result_will_be_submitted():
185
+
duration =1800# 30 minutes for final submission
186
+
else:
187
+
duration =10# quick check
188
+
score = run_measurement(duration)
189
+
return TestResult(success=True, score=score)
190
+
```
191
+
192
+
#### Multi-task scored exercises
193
+
194
+
Exercises with multiple independently scored parts combine `task_name` with `TestResult`:
Finally, `submission_tests` needs to call `rf.run_tests()` to execute all registered tests. To avoid leaking critical information (when hitting unexpected conditions in the submission tests themselves), `ref_utils` suppresses error output using `rf.ref_util_install_global_exception_hook()`. The `ref_utils` module provides various convenience functions, such as colored printing (print_err, print_warn, print_ok) or executing binaries with a specific payload (rf.run_with_payload(..)).
208
+
Each task produces its own `TestResult` and can have its own scoring policy configured in the admin interface. Tasks are independent — a failure in one does not affect the others.
0 commit comments