소스 정보
- 저장소
- mratsim/tattletale
- 최근 소스 활동
- 2026년 8월 26일 23:14
- 감지된 SKILL.md 언어
- 영어
- 스타
- 40
- 포크
- 4
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/mratsim/tattletale --skill testing명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
| name | testing |
| description | Nim testing conventions, unittest framework, and C++ compatibility patterns |
| license | MIT |
| compatibility | opencode |
| metadata | {"audience":"developers","workflow":"testing"} |
I provide guidance for writing tests in Nim that:
std/unittest frameworkTorchTensorUse this skill when:
Nim's standard library provides a simple testing framework:
import std/unittest
suite "my module tests":
test "addition works":
check 1 + 1 == 2
test "string handling":
let result = "hello".toUpperAscii()
check result == "HELLO"
Key procs:
suite(name, body) - Group related teststest(name, body) - Define a single testcheck(expr) - Assert expression is true, prints failed value on failuredoAssert(expr) - Like check but raises on failure (use for invariants)submitTest(result) - Submit test result from a procedureWhen you declare variables at module scope (top-level) in Nim tests, the generated C++ code uses = {} initialization:
TorchTensor expectedTensor = {}; // This fails!
expectedTensor = myFunction(a, b);
The C++ torch::Tensor type (and other FFI types with cppNonPod) does not accept brace initialization. This causes:
error: ambiguous overload for 'operator=' (operand types are 'at::Tensor' and '<brace-enclosed initializer list>')
Always wrap test code in a proc main():
import std/unittest, workspace/libtorch
proc generateTensor(): TorchTensor =
# This works - Nim generates:
# auto result = myFunction(a, b);
arange(10, kFloat32)
proc runTests*() =
suite "tensor tests":
test "generate tensor":
let tensor = generateTensor()
check tensor.numel() == 10
when isMainModule:
runTests()
This generates proper C++:
auto tensor = generateTensor(); // No {} initialization
Tests that load files should follow this pattern:
import std/unittest, std/os, workspace/safetensors, workspace/libtorch
const FIXTURES_DIR = currentSourcePath().parentDir() / "fixtures"
proc main() =
suite "safetensors loading":
test "load fixture":
let fixturePath = FIXTURES_DIR / "model.safetensors"
check fileExists(fixturePath)
var mf = memfiles.open(fixturePath, mode = fmRead)
defer: mf.close()
let (st, offset) = safetensors.load(mf)
check st.tensors.len > 0
when isMainModule:
main()
Key points:
currentSourcePath().parentDir() / "fixtures" for fixture pathsmemfiles.open with defer: mf.close()continue for missing fixturesDefine test parameters as const at module level:
const Patterns = ["gradient", "alternating", "repeating"]
const Shapes: array[4, seq[int64]] = [
@[int64 8],
@[int64 4, 4],
@[int64 2, 3, 4],
@[int64 3, 2, 2, 2]
]
const TestedDtypes = [F64, F32, F16, I64, I32, I16, I8, U64, U32, U16, U8]
Extract reusable logic into proc with * export:
proc generateExpectedTensor*(pattern: string, shape: seq[int64], dtype: ScalarKind): TorchTensor =
let shapeRef = shape.asTorchView()
let numel = shape.product()
case pattern
of "gradient":
arange(numel, dtype).reshape(shapeRef).to(dtype)
of "alternating":
let flat = arange(numel, kInt64)
let modVal = (flat % 2).to(kFloat64)
modVal.reshape(shapeRef).to(dtype)
else:
raise newException(ValueError, "Unknown pattern: " & pattern)
Note: Each branch of a case must assign to result.
Each module has a task defined in config.nims for running its tests:
# Test toktoktok
nim test_toktoktok
# Test libtorch
nim test_libtorch
# Test safetensors
nim test_safetensors
The command nim test_toktoktok compiles and runs all test files in workspace/toktoktok/tests/ that start with test_ or t_.
The project uses:
--path:. - Makes workspace/module imports worknim cpp -r plus flags for output and cache directoriesFor this project, fixtures are in:
workspace/toktoktok/tests/tokenizers/
Reference fixtures using:
const FIXTURES_DIR = currentSourcePath().parentDir() / "tokenizers"
If you have a parameter named shape and access a field info.shape:
proc generateExpectedTensor*(pattern: string, shape: seq[int64], ...): TorchTensor =
for info in tensors: # error: 'shape' shadows info.shape
check info.shape == shape
Fix: Rename parameter to avoid shadowing:
proc generateExpectedTensor*(pattern: string, shapeSeq: seq[int64], ...): TorchTensor =
for info in tensors:
check info.shape == shapeSeq # Now works
Each branch of a case must explicitly assign to result:
proc foo(x: int): int =
case x
of 1: result = 10 # Must use 'result ='
of 2: 20 # ERROR: doesn't assign!
Follow the naming convention: test_*.nim or t_*.nim in the module's tests/ directory.
# workspace/my_module/tests/test_myfeature.nim
import std/unittest, std/os
import workspace/my_module
proc runMyFeatureTests*() =
suite "my feature tests":
test "basic functionality":
let result = myModule.function()
check result == expectedValue
when isMainModule:
runMyFeatureTests()
The test will be discovered automatically by the test command:
# If it's in my_module:
nim c -r --task:test_my_module
Or run all tests for the module:
nim test_my_module
Create a fixtures/ directory and add test data:
workspace/my_module/tests/fixtures/
Reference in test code:
const FIXTURES_DIR = currentSourcePath().parentDir() / "fixtures"
let fixturePath = FIXTURES_DIR / "test_data.bin"
test_ or t_workspace/module/tests/proc runTests*()when isMainModule: runTests() at the enddefer for resource cleanup (files, etc.)*constFor AI/ML modules, test vectors are generated via Python scripts using torch and safetensors.
workspace/module/
├── tests/
│ ├── test_module.nim # Nim tests
│ ├── fixtures/ # Generated fixture files
│ │ ├── model.safetensors
│ │ └── tokenizer.json
│ └── testgen/ # Python test vector generators
│ └── generate_vectors.py
pyproject.toml with [dependency-groups] for shared dependencies:
[dependency-groups]
test-vectors = [
"torch>=2.0.0",
"safetensors>=0.7.0",
"transformers>=4.40.0",
"numpy>=2.4.2",
]
uv run --group test-vectors python workspace/module/tests/testgen/generate_vectors.pyimport torch
import numpy as np
from safetensors.numpy import save_file
import os
FIXTURES_DIR = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
"fixtures",
)
def generate_vandermonde():
x = torch.arange(1, 6, dtype=torch.float32)
vandermonde = torch.vander(x, increasing=True).T
return vandermonde.to(torch.bfloat16).view(torch.uint16).numpy()
def main():
fixtures = {
"BF16_vandermonde_5x5": generate_vandermonde(),
}
save_file(fixtures, os.path.join(FIXTURES_DIR, "vandermonde.safetensors"))
print("Fixtures generated")
if __name__ == "__main__":
main()
When adding new test vectors, regenerate the fixture files:
uv run --group test-vectors python workspace/module/tests/testgen/generate_vectors.py
Tests involving TorchTensor and other libtorch FFI types should use the shared test utilities:
import workspace/libtorch_testutils
Wrap test code that may throw C++ exceptions:
proc testTensorOps(): bool =
let a = ones(@[2, 3], kFloat32)
let b = zeros(@[2, 3], kFloat32)
let c = a + b
result = c.isDefined()
when isMainModule:
runCppTest("tensor operations", testTensorOps) # Handles exceptions automatically
Or use the template directly:
check catchCppExceptions(testTensorOps())
assertDefined - Check tensor is initialized:
let tensor = ones(@[2, 3], kFloat32)
assertDefined(tensor) # Raises if not defined
assertDefined(tensor, "weight") # Custom name in error
assertShape - Verify tensor dimensions:
let tensor = randn(@[2, 3, 4])
assertShape(tensor, 2, 3, 4)
assertDtype - Verify tensor dtype:
let tensor = ones(@[2, 3], kFloat32)
assertDtype(tensor, kFloat32)
assertAllClose / assertClose - Compare tensor values:
let actual = computeSomething()
let expected = ones(@[2, 3], kFloat32) * 2.0
assertAllClose(actual, expected) # Default rtol=2e-2, abstol=2e-2
assertClose(actual, expected, rtol=1e-5, abstol=1e-5) # Custom tolerance
printTensor - Print tensor with label:
printTensor(myTensor, "Weight matrix")
printTensorShape - Print shape and dtype:
printTensorShape(myTensor, "Input")
# Output: Input:
# Shape: [2, 3, 4], Dtype: kFloat32
ptrHex - Convert pointer to hex string for aliasing detection:
let tensor = ones(@[2, 3], kFloat32)
echo "data_ptr = 0x", tensor.data_ptr().ptrHex()
echo "shape.data() = 0x", tensor.shape.data().ptrHex()
# Useful for detecting memory aliasing issues
dataPtrHex / shapePtrHex - Convenience wrappers:
let tensor = ones(@[2, 3], kFloat32)
echo "data_ptr = 0x", tensor.dataPtrHex()
echo "shape_ptr = 0x", tensor.shapePtrHex()
# Equivalent to above but more convenient
printTensorShape(myTensor, "Input")
# Output: Input:
# Shape: [2, 3, 4], Dtype: kFloat32
traceExec - Debug macro to trace execution:
traceExec:
let a = ones(@[2, 3])
let b = zeros(@[2, 3])
let c = a + b
# Prints each statement before executing
Complete example:
# workspace/my_module/tests/test_feature.nim
import
std/unittest,
workspace/libtorch,
workspace/libtorch_testutils,
workspace/my_module
proc testBasicFunctionality(): bool =
let input = ones(@[2, 3], kFloat32)
let result = myModule.process(input)
assertDefined(result)
assertShape(result, 2, 3)
result = true
proc testEdgeCase(): bool =
let input = zeros(@[1], kFloat32)
let output = myModule.process(input)
assertAllClose(output, input)
result = true
when isMainModule:
runCppTest("basic functionality", testBasicFunctionality)
runCppTest("edge case", testEdgeCase)
workspace/libtorch_testutils for tests with TorchTensorrunCppTest for formatted output with automatic exception handling
(captures the C++ stacktrace; do NOT confuse it with crucible's private
proc runTest() scope wrapper — see workspace/crucible/AGENTS.md)catchCppExceptions when integrating with std/unittest checkassertDefined, assertShape, etc.) for clear error messagesprintTensor and printTensorShape for debugging failuresworkspace/module/tests/ directorytest_ or t_A kernel test that compares against a libtorch reference is three clearly separated parts, so a reader always sees which variable comes from the kernel, which from the reference, and where the comparison happens:
check*() proc that builds the shared inputs once, calls both, and
asserts.proc kernelUnderTest(xf: seq[float32], M, N: int): F.Tensor =
## Runs the kernel on the fp16-rounded inputs; returns the fp16
## output converted to fp32.
var engine = bkMetal.init()
engine.ingest(kernelMsl)
... # build fp16 buffers from xf, engine.run, read back
result = toTensor(outF).reshape(M, N)
proc reference(xf: seq[float32], M, N: int): F.Tensor =
## The reference: torch op over the same fp16-rounded inputs.
let xh = toTensor(xf).reshape(M, N).to(kFloat16)
result = F.some_op(xh.to(kFloat32))
proc checkFeature(): bool =
## Kernel output vs the torch reference on one random batch.
Torch.manual_seed(0x5EED'u64)
let xf = scaledRand(M, N, 2.0'f32)
let actual = kernelUnderTest(xf, M, N)
let expected = reference(xf, M, N)
echo &" worst |Δ| = {worstAbsDiff(actual, expected)} (tolerance 5e-3)"
assertAllClose(actual, expected, rtol = 0.0'f64, abstol = 5e-3'f64)
result = true
when isMainModule:
runCppTest("feature vs the torch reference", checkFeature)
Rules:
actual and expected (or kernel/ref), so the
kernel-vs-reference split is visible at the assert.check*() proc stays readable.worstAbsDiff(a, b: F.Tensor): float32 helper prints the worst
deviation before the assert; the assert carries the tolerance.A kernel test's reference must be libtorch math over the same fp16-rounded inputs — never a Nim reimplementation of the kernel's algorithm, and never a reference that shares the kernel's own code path.
Rebuilding the kernel's arithmetic in Nim (matmul loops, norms, rope rotations, ...) instead of calling libtorch is banned:
manual_silu_and_mul_fp16.nim); the reference call
must be a torch op — F.linear, F.rmsNorm,
F.scaled_dot_product_attention, ... from the transformers workspace
(workspace/transformers/src/layers/linear.nim pattern).Data-preparation helpers that mirror a storage format (e.g. a dequant decode table that rebuilds an fp16 weight matrix from its packed bits) are allowed — they are fixture-style reconstruction, not the op's arithmetic — but they must live once in a shared helper, not be copied per test.
A reference that cannot diverge from the kernel proves nothing:
The reference must be computed independently of the kernel: same random inputs, fp16 rounding applied identically, torch arithmetic.