Skainet testing
Skill SKaiNET-developers/SKaiNET-coding-skills/skainet-contributor-skills/skills/skainet-testing
Use ONLY when writing or editing unit tests INSIDE the SKaiNET repository — tests under `SKaiNET/skainet-*/src/commonTest/`, `*/jvmTest/`, or `SKaiNET/skainet-test/skainet-test-java/`. Enforces the in-repo test policy: Kotest spec runner on JVM, `kotlin.test` in commonTest, `TensorAssertions.assertTensorClose` with explicit `ToleranceConfig.{STRICT/STANDARD/RELAXED/GRADIENT}`, Java JUnit 5 in `skainet-test-java`. Trigger tokens include `assertTensorClose`, `assertArrayClose`, `ToleranceConfig`, `GroundTruthTensor`. Do NOT fire on a CONSUMER project writing its own tests against SKaiNET as a dependency — those tests don't have access to `skainet-test-groundtruth` and can use any framework they like.From its SKILL.md
npx -y skills add SKaiNET-developers/SKaiNET-coding-skills --skill skainet-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
9.1 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it
skainet-testing
Common unit-test strategy across every SKaiNET module: tolerance-aware tensor comparisons, Kotest as the JVM/common runner, JUnit 5 for the Java mirror, fixed source-set placement.
When to use
- A test under
*/commonTest/,*/jvmTest/, orskainet-test-java/src/test/is being added or edited. - The user mentions Kotest, JUnit,
assertTensorClose,ToleranceConfig,GroundTruthTensor, or "compare tensors / floats with tolerance". - A new module needs its test wiring (which dependencies, which source set, which assertion API).
When NOT to use
- Building tensors or pipelines for production code — that's
skainet-data-dsl. - Building neural networks for production code — that's
skainet-nn-dsl. - Editing
build.gradle.ktsto add a Kotest dependency — coordinate withgradle-multimodulefor the catalog accessor and withkmpfor the source set.
Hard rules
- Compare tensor data ONLY through
sk.ainet.test.groundtruth.TensorAssertions— neverkotlin.test.assertEqualson raw tensor data, neverassertArrayEqualswith hardcoded epsilons inside production tests. - Every comparison MUST pass an explicit
atolfromToleranceConfig(or callToleranceConfig.forOperation(name)). Bare numeric literals like1e-5fMUST NOT appear at the call site. - Tolerance picker, no exceptions:
STRICT(1e-6) foradd/subtract/multiply/divide/relu/flatten/reshape;STANDARD(1e-5) formatmul/conv1d/conv2d/conv3d/sum/mean/variance;RELAXED(1e-4) forsigmoid/gelu/silu/softmax/logSoftmax/leakyRelu;GRADIENT(1e-4) for any backward / autograd assertion;VERY_RELAXED(1e-3) only when the operation has documented numerical instability. - Kotlin tests live in
commonTest/whenever they don't reference JVM-only APIs; JVM-only tests live injvmTest/. Java tests live inskainet-test/skainet-test-java/src/test/java/sk/ainet/java/. - Kotest is the spec runner for JVM-targeted Kotlin tests. Pure
commonTest(multiplatform) useskotlin.testbecause Kotest's runner is JVM-only — do not import Kotest fromcommonTestsource sets. - A new test that compares a SKaiNET tensor against expected values MUST express the expected as a
GroundTruthTensor(or aFloatArray+Shape) and callTensorAssertions.assertTensorClose(...)/assertArrayClose(...). Never calltensor.getData().copyToFloatArray()and thenassertEqualselement by element.
Workflow
- Decide the source set:
- Pure logic (no JVM-only API)? →
commonTest/withkotlin.test. - JVM-only fixtures, file IO, or you want Kotest spec syntax? →
jvmTest/with Kotest. - Mirroring a Java consumer surface? →
skainet-test-java/src/test/java/...with JUnit 5.
- Pure logic (no JVM-only API)? →
- Pick the tolerance from rule 3 above by looking at the operation under test, not by trial-and-error.
- Build the expected value once (literal
FloatArray+Shape, or load aGroundTruthTensorfixture). - Call
TensorAssertions.assertTensorClose(expected, actual, atol = ToleranceConfig.STANDARD)— passrtolonly if the comparison is dominated by relative error (rare; default1e-5fis fine for most cases). - For shape-only assertions, use
TensorAssertions.assertShapeEquals(expected, actual). For Kotest infix style, preferactual shouldBeCloseTo expected. - Self-verify before reporting done: every numeric literal in the test that isn't a value being asserted on is gone (no
1e-5f,0.001f, etc. as tolerance arguments — they live inToleranceConfig).
Canonical examples
Kotest StringSpec on JVM (preferred for new tests in JVM-only modules):
import io.kotest.core.spec.style.StringSpec
import sk.ainet.context.DirectCpuExecutionContext
import sk.ainet.lang.tensor.Shape
import sk.ainet.lang.tensor.dsl.tensor
import sk.ainet.lang.types.FP32
import sk.ainet.test.groundtruth.GroundTruthTensor
import sk.ainet.test.groundtruth.TensorAssertions
import sk.ainet.test.groundtruth.ToleranceConfig
class MatmulSpec : StringSpec({
"2x2 matmul matches expected within STANDARD tolerance" {
val ctx = DirectCpuExecutionContext.create()
val a = tensor<FP32, Float>(ctx, FP32::class) {
tensor { shape(2, 2) { from(1f, 2f, 3f, 4f) } }
}
val b = tensor<FP32, Float>(ctx, FP32::class) {
tensor { shape(2, 2) { from(5f, 6f, 7f, 8f) } }
}
val c = a.ops.matmul(a, b)
val expected = GroundTruthTensor(
data = floatArrayOf(19f, 22f, 43f, 50f),
shape = Shape(2, 2)
)
TensorAssertions.assertTensorClose(
expected = expected,
actual = c,
atol = ToleranceConfig.STANDARD
)
}
})
Multiplatform commonTest (no Kotest — kotlin.test only):
import kotlin.test.Test
import sk.ainet.lang.nn.DefaultNeuralNetworkExecutionContext
import sk.ainet.lang.tensor.Shape
import sk.ainet.lang.tensor.dsl.tensor
import sk.ainet.lang.types.FP32
import sk.ainet.test.groundtruth.TensorAssertions
import sk.ainet.test.groundtruth.ToleranceConfig
class SoftmaxCommonTest {
@Test
fun `softmax over a 1x4 row sums to 1 within RELAXED tolerance`() {
val ctx = DefaultNeuralNetworkExecutionContext()
val x = tensor<FP32, Float>(ctx, FP32::class) {
tensor { shape(1, 4) { from(1f, 2f, 3f, 4f) } }
}
val y = x.ops.softmax(x, dim = -1)
val sum = y.ops.sum(y, null)
TensorAssertions.assertArrayClose(
expected = floatArrayOf(1f),
actual = floatArrayOf(sum.data.get(0) as Float),
expectedShape = Shape(1),
actualShape = sum.shape,
atol = ToleranceConfig.RELAXED
)
}
}
Java JUnit 5 mirror — see TensorJavaOpsTest.java:
@Test
void matmul() {
Tensor<?, ?> a = SKaiNET.tensor(ctx, new int[]{2, 3}, DType.fp32(),
new float[]{1f, 2f, 3f, 4f, 5f, 6f});
Tensor<?, ?> b = SKaiNET.tensor(ctx, new int[]{3, 2}, DType.fp32(),
new float[]{7f, 8f, 9f, 10f, 11f, 12f});
Tensor<?, ?> c = TensorJavaOps.matmul(a, b);
assertArrayEquals(new int[]{2, 2}, c.getShape().getDimensions());
float[] result = c.getData().copyToFloatArray();
assertArrayEquals(new float[]{58f, 64f, 139f, 154f}, result, 1e-4f);
}
// from: SKaiNET/skainet-test/skainet-test-java/src/test/java/sk/ainet/java/TensorJavaOpsTest.java:79-93
The Java mirror uses raw assertArrayEquals with a numeric epsilon because Java consumers don't depend on skainet-test-groundtruth. Inside Kotlin tests, TensorAssertions is mandatory.
Related skills
- Test wiring (
testImplementationlines, Kotest plugin) — see../gradle-multimodule/SKILL.md. - Source-set rules for
commonTestvsjvmTestplacement — see../kmp/SKILL.md. - Java facade convention being tested — see
../skainet-java-interop/SKILL.md. - Building the tensors used as inputs in tests — see the
skainet-data-dslskill (in the sibling consumer plugin).
Anti-patterns
// WRONG — bare epsilon at the call site
assertEquals(expected[0], actual.data[0] as Float, 1e-5f)
// RIGHT — TensorAssertions + ToleranceConfig
TensorAssertions.assertArrayClose(expected, actualData, expShape, actShape, atol = ToleranceConfig.STANDARD)
// WRONG — Kotest imported into commonTest
import io.kotest.core.spec.style.StringSpec // commonTest cannot run Kotest
// RIGHT — kotlin.test for commonTest, Kotest only in jvmTest / src/test
import kotlin.test.Test
// WRONG — comparing the wrong tolerance
TensorAssertions.assertTensorClose(expected, sigmoidOut, atol = ToleranceConfig.STRICT)
// sigmoid has accumulated transcendental error
// RIGHT — RELAXED for transcendentals
TensorAssertions.assertTensorClose(expected, sigmoidOut, atol = ToleranceConfig.RELAXED)
References
references/tolerance-table.md— the full operation → tolerance map taken fromToleranceConfig.forOperation.references/assertion-api.md— every public function onTensorAssertionsand the extension infix sugar, with signatures.
What ships with it: 3 files
8.9 KB alongside SKILL.md
evals/
- evals.json2.9 KB
references/
- assertion-api.md3.9 KB
- tolerance-table.md2.2 KB
Gives 0 of the 12 instructions most test skills give in ~2.3k tokens
Counted across 964 of the 1,571 authors here whose files we hold, read 2026-08-07
- Close the browser when donein 55 of 964, across 12 files
- Wait for network idle statein 51 of 964, across 6 files
- Launch Chromium in headless modein 49 of 964, across 6 files
- Use descriptive selectors for elementsin 49 of 964, across 6 files
- Run provided scripts with help flag firstin 49 of 964, across 6 files
- Add appropriate explicit waitsin 48 of 964, across 5 files
- Use bundled scripts as black boxesin 46 of 964, across 3 files
- Do not read script source codein 46 of 964, across 3 files
- Use sync playwright for scriptsin 46 of 964, across 3 files
- Inspect dom before executing actionsin 46 of 964, across 3 files
- Run the full test suitein 37 of 964
- Write the failing test firstin 29 of 964, across 23 files
Said here and by no other author read
- place pure logic tests in commonTest
- place jvm-only tests in jvmTest
- place java tests in skainet-test-java
- use kotlin.test for multiplatform commonTest
- use kotest spec runner for jvm-only kotlin tests
- compare tensor data only through tensorassertions
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.