Skip to content

Commit 06572c0

Browse files
authored
exercises: provide stubs for every exercise (#474)
Before this commit, the user-facing solution file was empty for every exercise apart from: - `clock` - `dnd-character` - `hello-world` - `react` Add initial content to that file for every other exercise. Also, check in CI that every exercise has a test/stub file pair that: - compiles without error - runs _with_ an error (because we want the stub to fail the tests) This required adding the missing fields to the `Character` object in `dnd-character`. There are some remaining questions, such as: - What is the best placeholder return type for a procedure that could return e.g. a Table, CountTable, or array? - How should we handle parameters with default values in e.g. `scale-generator`, `twelve-days`, and `two-fer`? - To what extent should we provide doc comments? But let's consider those questions later. One implemented decision: always use `proc` instead of `func`, to better support debugging via `echo`. Closes: #409 Closes: #410
1 parent cb93de6 commit 06572c0

File tree

69 files changed

+347
-0
lines changed

Some content is hidden

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

69 files changed

+347
-0
lines changed

.github/workflows/exercises.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,9 @@ jobs:
5555

5656
- name: Run `check_exercises`
5757
run: _test/check_exercises
58+
59+
- name: Compile `check_stubs.nim`
60+
run: nim c --styleCheck:error _test/check_stubs.nim
61+
62+
- name: Run `check_stubs`
63+
run: _test/check_stubs

_test/check_stubs.nim

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import std/[os, osproc, strformat, strutils]
2+
3+
iterator walkExerciseDirs: string =
4+
const repoRootDir = currentSourcePath().parentDir().parentDir()
5+
for exerciseKind in ["concept", "practice"]:
6+
for exerciseDir in walkDirs(repoRootDir / "exercises" / exerciseKind / "*"):
7+
yield exerciseDir
8+
9+
proc checkStubs: seq[string] =
10+
## Compiles and runs the test file for every exercise, using the user-facing
11+
## solution stub.
12+
##
13+
## Returns the exercise slugs for which the corresponding test file either:
14+
##
15+
## - compiles with an error
16+
##
17+
## - runs without an error (the solution stub is supposed to fail the tests)
18+
result = @[]
19+
stderr.writeLine "Checking stubs..."
20+
for exerciseDir in walkExerciseDirs():
21+
let slug = exerciseDir.lastPathPart()
22+
let testPath = exerciseDir / &"test_{slug.replace('-', '_')}.nim"
23+
stderr.writeLine &"{slug}"
24+
const nimOptions = "--hints:off --usenimcache --filenames:canonical " &
25+
"--spellSuggest:0 --styleCheck:error"
26+
let (outpCompile, errCompile) = execCmdEx(&"nim c {nimOptions} {testPath}")
27+
if errCompile == 0:
28+
let (outpRun, errRun) = execCmdEx(&"nim r --hints:off {testPath}")
29+
if errRun == 0:
30+
stderr.write outpRun
31+
stderr.writeLine &"Error: the {slug} stub passed the tests\n"
32+
result.add slug
33+
else:
34+
stderr.writeLine outpCompile
35+
result.add slug
36+
37+
proc main =
38+
let errorSlugs = checkStubs()
39+
if errorSlugs.len > 0:
40+
let msg = fmt"""
41+
42+
Error: there were {errorSlugs.len} exercises with a problematic stub:
43+
{errorSlugs.join(", ")}""".unindent()
44+
echo msg
45+
quit 1
46+
else:
47+
const msg = """
48+
49+
Success. Every exercise has a test file and stub that:
50+
- compiles without error
51+
- runs with an error (we want the stub to fail the tests)""".unindent()
52+
echo msg
53+
54+
when isMainModule:
55+
main()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
let expectedMinutesInOven* = 5
2+
3+
proc remainingMinutesInOven*(actualMinutesInOven: int): int =
4+
## Returns the number of minutes that the lasagna still needs to remain in the
5+
## oven to be properly prepared.
6+
discard
7+
8+
proc preparationTimeInMinutes*(numberOfLayers: int): int =
9+
## Returns the total preparation time for a given number of layers.
10+
discard
11+
12+
proc totalTimeInMinutes*(numberOfLayers, actualMinutesInOven: int): int =
13+
## Returns the total time required to prepare the lasagna.
14+
discard
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
proc abbreviate*(s: string): string =
2+
discard
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
proc convert*(digits: openArray[int], fromBase: int, toBase: int): seq[int] =
2+
discard
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type
2+
Allergen* = enum
3+
Eggs, Peanuts, Shellfish, Strawberries, Tomatoes, Chocolate, Pollen, Cats
4+
5+
proc isAllergicTo*(score: int, allergen: Allergen): bool =
6+
discard
7+
8+
proc allergies*(score: int): set[Allergen] =
9+
discard
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
proc detectAnagrams*(word: string, candidates: openArray[string]): seq[string] =
2+
discard
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
proc isArmstrongNumber*(n: int): bool =
2+
discard
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
proc encode*(s: string): string =
2+
discard
3+
4+
proc decode*(s: string): string =
5+
discard
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
proc binary*(s: string): int =
2+
discard

0 commit comments

Comments
 (0)