Skip to content

Commit fa41beb

Browse files
Add test generator (#325)
Extra test cases may be supplied using `.meta/additional-test-cases.json`. Each exercise's `.meta/test_generator.py` defines `gen_case`, and optionally `test_name`, `HEADER` and `sort_key`. Generators are added for 63 exercises. The generated tests are identical to the existing tests, except with - affine-cipher - connect - wordy Python style is enforced by ruff, run by pre-commit and a new pre-commit workflow. I used Claude for this PR.
1 parent 610bd87 commit fa41beb

80 files changed

Lines changed: 1341 additions & 28 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/pre-commit.yml‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Pre-commit checks
2+
on:
3+
pull_request:
4+
branches:
5+
- main
6+
push:
7+
branches:
8+
- main
9+
workflow_dispatch:
10+
11+
jobs:
12+
pre-commit:
13+
name: Pre-commit checks
14+
runs-on: ubuntu-26.04
15+
steps:
16+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
17+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
18+
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
bin/configlet
44
bin/configlet.exe
55
temp/
6+
7+
__pycache__/
8+
*.pyc

‎.pre-commit-config.yaml‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
exclude: |
2+
(?x)^(
3+
exercises/practice/.*/.docs/introduction.md|
4+
exercises/practice/.*/.docs/instructions.md|
5+
exercises/practice/.*/.*/.*\.v|
6+
.github/CODEOWNERS|
7+
.github/labels.yml|
8+
bin/fetch-configlet
9+
)$
10+
11+
repos:
12+
- repo: https://github.com/pre-commit/pre-commit-hooks
13+
rev: v6.0.0
14+
hooks:
15+
- id: trailing-whitespace
16+
- id: end-of-file-fixer
17+
- id: check-yaml
18+
- id: check-added-large-files
19+
- repo: https://github.com/astral-sh/ruff-pre-commit
20+
rev: v0.16.9
21+
hooks:
22+
- id: ruff-check
23+
args: [--fix]
24+
- id: ruff-format

‎.ruff.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
target-version = "py314"

‎README.md‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ A good place to start in the docs is to understand how the [Hello World](https:/
7272
7373
#### How to implement a new exercise from start to finish
7474
75-
There are two ways to implement a practice exercise. You can follow all the 13 steps listed below from start to finish, or you can run `./bin/bootstrap_practice_exercise.sh [SLUG]` to create all the files and folders you'll need. This will allow you to skip a few steps and jump right into writing your example solution, but you'll need [bash](https://www.gnu.org/software/bash/) and [jq](https://stedolan.github.io/jq/) to run the script.
75+
There are two ways to implement a practice exercise. You can follow all the 14 steps listed below from start to finish, or you can run `./bin/bootstrap_practice_exercise.sh [SLUG]` to create all the files and folders you'll need. This will allow you to skip a few steps and jump right into writing your example solution, but you'll need [bash](https://www.gnu.org/software/bash/) and [jq](https://stedolan.github.io/jq/) to run the script.
7676
7777
1. Pick an exercise from the [problem-specifications](https://github.com/exercism/problem-specifications/tree/main/exercises) repo.
7878
2. Create a new entry for the exercise in [config.json](./config.json). Include
@@ -85,22 +85,23 @@ There are two ways to implement a practice exercise. You can follow all the 13 s
8585
- `bin/configlet sync --update --yes --docs --metadata --exercise [SLUG]`
8686
- `bin/configlet sync --update --tests include --exercise [SLUG]`
8787
4. Create 3 files in the new directory (located at `exercises/practice/[SLUG]`):
88-
- `run_test.v`
8988
- `[SLUG].v`
9089
- `.meta/example.v`
90+
- `.meta/test_generator.py`
9191
5. Write an example implementation in `[SLUG].v`
92-
6. Write a test suite in `run_test.v` based on the canonical data in [problem-specifications](https://github.com/exercism/problem-specifications/tree/main/exercises). Here's an example of [canonical data](https://github.com/exercism/problem-specifications/blob/main/exercises/grade-school/canonical-data.json) and here's the [corresponding test suite](./exercises/practice/grade-school/run_test.v).
93-
7. Run the test suite with `v -stats test run_test.v`
94-
8. Once all tests pass, make sure code is formatted properly with `v fmt -w [V_FILE]` on all the v files (example and test files)
95-
9. Copypaste everything in `[SLUG].v` into `.meta/example.v`
96-
10. Remove everything from `[SLUG].v` except the stub of the needed function, the `module main` at the top, and make a stub of a struct or two.
97-
11. Add needed info to the `...[SLUG]/.meta/config.json` file:
92+
6. Write a test generator in `.meta/test_generator.py`. It will receive the canonical data in [problem-specifications](https://github.com/exercism/problem-specifications/tree/main/exercises). Here's an example of [canonical data](https://github.com/exercism/problem-specifications/blob/main/exercises/grade-school/canonical-data.json).
93+
7. Generate `run_test.v` by running `generators/generate.py [SLUG]`
94+
8. Run the test suite with `v -stats test run_test.v`
95+
9. Once all tests pass, make sure code is formatted properly with `v fmt -w [V_FILE]` on all the v files (example and test files)
96+
10. Copypaste everything in `[SLUG].v` into `.meta/example.v`
97+
11. Remove everything from `[SLUG].v` except the stub of the needed function, the `module main` at the top, and make a stub of a struct or two.
98+
12. Add needed info to the `...[SLUG]/.meta/config.json` file:
9899
- Author's GitHub username
99100
- solution file name (should be `[SLUG].v`)
100101
- test file name (should be `run_test.v`)
101102
- example solution file name (should be `.meta/example.v`)
102-
12. Commit changes with [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
103-
13. Make your PR and do a little happy dance
103+
13. Commit changes with [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/)
104+
14. Make your PR and do a little happy dance
104105
105106
![](https://media3.giphy.com/media/ghVtt3BfMwYhi/giphy.gif?cid=790b7611a39608242e7ad1acf94c07788cdcada74ca02528&rid=giphy.gif&ct=g)
106107

‎bin/bootstrap_practice_exercise.sh‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,19 @@ exercise_dir="exercises/practice/${SLUG}"
1616
echo "Fetching latest version of configlet..."
1717
./bin/fetch-configlet
1818

19-
if [[ -z $author ]]; then
19+
if [[ -z ${author:-} ]]; then
2020
echo
2121
read -rp "What's your github username? " author
2222
fi
2323

2424
bin/configlet create --practice-exercise $SLUG --author "${author}" --difficulty 5
2525

26-
# Create V files
26+
# Create V files and the test generator
2727
touch $exercise_dir/.meta/example.v
2828
touch $exercise_dir/$SLUG.v
29-
touch $exercise_dir/run_test.v
29+
cat > $exercise_dir/.meta/test_generator.py <<'EOF'
30+
# Returns the body of the test function for one case of the canonical data.
31+
# Helpers for rendering V values and assertions are in generators/lib.py.
32+
def gen_case(case):
33+
raise NotImplementedError("write gen_case for this exercise")
34+
EOF

‎bin/test‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
# Synopsis:
44
# Test the track's exercises.
5-
#
6-
# At a minimum, this file must check if the example/exemplar solution of each
5+
#
6+
# At a minimum, this file must check if the example/exemplar solution of each
77
# Practice/Concept Exercise passes the exercise's tests.
88
#
99
# To check this, you usually have to (temporarily) replace the exercise's solution files

‎docs/INSTALLATION.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ Please follow the [instructions in the official documentation](https://github.co
66
It is highly recommended that you install from source and then follow the [instructions on symlinking](https://github.com/vlang/v/blob/master/README.md#symlinking) to be able to run V anywhere.
77

88
## Using an IDE
9-
The best way to use V in an IDE is to use the [VSCodium](https://vscodium.com/) [extension](https://marketplace.visualstudio.com/items?itemName=vlanguage.vscode-vlang). There is also support for [Vim](https://github.com/ollykel/v-vim), [Emacs](https://github.com/damon-kwok/v-mode), and [Sublime](https://github.com/elliotchance/vlang-sublime). To see a full list of supported IDEs, see the [V homepage](https://vlang.io/).
9+
The best way to use V in an IDE is to use the [VSCodium](https://vscodium.com/) [extension](https://marketplace.visualstudio.com/items?itemName=vlanguage.vscode-vlang). There is also support for [Vim](https://github.com/ollykel/v-vim), [Emacs](https://github.com/damon-kwok/v-mode), and [Sublime](https://github.com/elliotchance/vlang-sublime). To see a full list of supported IDEs, see the [V homepage](https://vlang.io/).

‎docs/TESTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ To run tests for a specific implementation of an exercise, here are the recommen
88
1. run `v -stats test run_test.v`
99

1010
## Testing in V
11-
For more information on testing in V, refer to the [official docs](https://github.com/vlang/v/blob/master/doc/docs.md#testing)
11+
For more information on testing in V, refer to the [official docs](https://github.com/vlang/v/blob/master/doc/docs.md#testing)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from lib import assert_eq, v_string
2+
3+
4+
def gen_case(case):
5+
return [
6+
f"phrase := {v_string(case['input']['phrase'])}",
7+
assert_eq("abbreviate(phrase)", v_string(case["expected"])),
8+
]

0 commit comments

Comments
 (0)