agentleFS
VyrnexisVyrnexis/nim-agent-template/skills/nim-testing/SKILL.md

nim-testing

Skill4 starsChanged 3 months agoMITView on GitHub

Write and run deterministic Nim tests, including isolated test files, expected-exception checks, multi-configuration builds, and sanitizer integration. Use when setting up a Nim test suite, testing failure behavior, running tests across debug/release/danger modes, or adding AddressSanitizer support.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

9 rules. About 0 are common, 2 are distinctive, and 7 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Import std/assertions. Use block scopes with doAssert and doAssertRaises:

    line 12
  2. Very specific:

    Use ordinary call syntax and grouped negation inside doAssert:

    line 32
  3. Very specific:

    Avoid command-call syntax in assertions.

    line 44
  4. Very specific:

    Test files use t prefix: tbasic.nim, tedge.nim, terrors.nim, tintegration.nim.

    line 61
  5. Very specific:

    Run from project root: nim c -r tests/tester.nim

    line 93
  6. Distinctive:

    Keep test files self-contained when practical.

    line 99
  7. Very specific:

    Set up layout: Create src/, tests/, and tests/config.nims.

    line 103
  8. Very specific:

    Create the runner: Add tests/tester.nim with the auto-discover pattern.

    line 105
  9. Distinctive:

    Run with ASan: if the project uses unsafe constructs.

    line 114

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Nim Testing

Rules

Use block-based tests with doAssert

Import std/assertions. Use block scopes with doAssert and doAssertRaises:

import std/assertions

block add_basic:
  doAssert add(1, 2) == 3, "add should sum positive integers"

block greet_empty:
  doAssert greet("") == "hello "

block parse_bad_input:
  doAssertRaises ValueError:
    discard parseThing("")

doAssert raises AssertionDefect on failure. doAssertRaises passes only when the requested exception type is raised; otherwise the test exits non-zero.

Write unambiguous assertions

Use ordinary call syntax and grouped negation inside doAssert:

proc foo(x: int): int = x
proc same(x, y: int): bool = x == y

doAssert foo(1) == 1, "foo should return its argument"
doAssert same(1, 1), "same values should match"
doAssert not (foo(1) < 0), "foo should not return a negative value"
doAssert 4 notin @[1, 2, 3]

Avoid command-call syntax in assertions. doAssert foo 1 == 1 can bind the comparison to foo, and doAssert same 1, 1 can bind the comma to doAssert.

Project layout

project/
  src/
    mylib.nim
  tests/
    config.nims        # shared compiler switches
    tester.nim         # central test runner (auto-discovers t*.nim)
    tbasic.nim
    tedge.nim
    terrors.nim

Test files use t prefix: tbasic.nim, tedge.nim, terrors.nim, tintegration.nim.

tests/config.nims

switch("path", "$projectdir/../src")

The compiler loads this config automatically when compiling files in tests/.

tests/tester.nim

import std/os

proc fatal(msg: string) = quit "FAILURE " & msg

proc exec(cmd: string) =
  echo "Running: ", cmd
  if execShellCmd(cmd) != 0: fatal cmd

let testDir = getCurrentDir() / "tests"
for f in walkFiles(testDir / "t*.nim"):
  let name = f.extractFilename
  if name == "tester.nim":
    discard
  else:
    exec "nim c -r " & quoteShell(testDir / name)

echo "All test files completed."

Run from project root: nim c -r tests/tester.nim

New test files are auto-discovered — no runner edits needed.

Shared Test Code

Keep test files self-contained when practical. If setup is repeated, extract domain helpers such as sample builders, temp-file setup, or fixture data.

Workflow

  1. Set up layout. Create src/, tests/, and tests/config.nims.
  2. Write test files. Name files with t prefix. Use block, doAssert, and doAssertRaises.
  3. Create the runner. Add tests/tester.nim with the auto-discover pattern.
  4. Run all configurations:

    nim c -r tests/tester.nim
    nim c -d:release -r tests/tester.nim
    nim c -d:danger -r tests/tester.nim
  5. Run with ASan if the project uses unsafe constructs. See "AddressSanitizer" below.
  6. Set up CI. See references/ci_github_actions.md.

Multi-configuration testing

ModeOverflow checksStack traces (file:line)
default / -d:debugYesFull
-d:releaseYesRaising frame only
-d:dangerNoRaising frame only
  • Overflow checks: Disabled in danger. Use when defined(danger) guards.
  • Stack traces: Release and danger show only the raising frame. Add --lineTrace:on to restore full traces with line numbers.
  • assert: Compiled out in danger. Use doAssert.

AddressSanitizer

nim c \
  --passC:"-fsanitize=address -fno-omit-frame-pointer" \
  --passL:"-fsanitize=address -fno-omit-frame-pointer" \
  -g -d:noSignalHandler -d:useMalloc \
  -r tests/tester.nim
  • --passC / --passL: Both required.
  • -g: Embeds debug info for Nim source locations in reports.
  • -d:noSignalHandler: Lets ASan report directly instead of Nim's signal handler.
  • -d:useMalloc: Uses C's malloc so ASan tracks every allocation.

Windows (MSVC): nim c --cc:vcc --passC:"/fsanitize=address" -r tests/tester.nim

Sanitizer config in tests/config.nims

switch("path", "$projectdir/../src")

when defined(addressSanitizer):
  switch("debugger", "native")
  switch("define", "noSignalHandler")
  switch("define", "useMalloc")
  when defined(windows):
    switch("passC", "/fsanitize=address")
  else:
    switch("passC", "-fsanitize=address -fno-omit-frame-pointer")
    switch("passL", "-fsanitize=address -fno-omit-frame-pointer")

Then: nim c -d:addressSanitizer -r tests/tester.nim

Common Mistakes

MistakeWhy it is wrong
Using assert instead of doAssertassert is compiled out in danger. Use doAssert.
Relying on OverflowDefect without when defined(danger)Never raised in danger mode.
Running ASan without -d:useMallocNim's default allocator is not intercepted by ASan.
Running ASan without -d:noSignalHandlerNim's signal handler intercepts SIGSEGV before ASan reports.
Using only --passC without --passL for ASanThe sanitizer runtime must be linked.
Writing doAssert foo 1 == 1Nim parses it like doAssert foo(1 == 1). Use doAssert foo(1) == 1.

References

  • references/block_test_pattern.md — An isolated test layout with block tests, runner, and commands.
  • references/ci_github_actions.md — Cross-platform GitHub Actions CI with Linux ASan.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.