Files
TheRock/rocm-systems/projects/amdsmi/.claude/skills/test-driven-development/SKILL.md
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

6.8 KiB

name, description
name description
test-driven-development Use when implementing any amd-smi feature, bug fix, or behavior change — before writing implementation code. Enforces strict RED-GREEN-REFACTOR: failing test first, watch it fail, minimal code to pass, refactor.

Test-Driven Development — amd-smi

Write the test first. Watch it fail. Write minimal code to make it pass.

Core principle: If you didn't watch the test fail, you don't know what it tests.

Violating the letter of the rules is violating the spirit of the rules.

The Iron Law

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

Wrote code before the test? Delete it. Start over from the test.

No exceptions:

  • Don't keep it as "reference"
  • Don't "adapt" it while writing the test
  • Don't peek at it
  • Delete means delete

When to Use

Always:

  • New amdsmi_* API functions
  • CLI command additions or behavior changes
  • Bug fixes (write the test that reproduces the bug first)
  • Refactors that change observable behavior

Exceptions (ask first):

  • Pure auto-generated files (amdsmi_wrapper.py)
  • Build/packaging-only changes with no testable behavior
  • Throwaway diagnostic scripts

Thinking "this is too simple for a test"? Stop. That's rationalization.

Red-Green-Refactor

RED  → write one minimal failing test
     ↓
verify it fails (and fails for the right reason)
     ↓
GREEN → minimal code to make it pass
     ↓
verify it passes; all other tests still pass
     ↓
REFACTOR → clean up while staying green
     ↓
repeat for the next behavior

RED — Write the Failing Test

One behavior. Clear name. Real code (no mocks unless unavoidable).

C++ (GTest) example:

TEST_F(AmdSmiTest, GetGpuPowerCapHandlesInvalidHandle) {
    uint64_t cap = 0;
    amdsmi_status_t status = amdsmi_get_power_cap(nullptr, 0, &cap);
    EXPECT_EQ(status, AMDSMI_STATUS_INVAL);
}

Python example:

def test_get_gpu_power_cap_raises_on_invalid_handle():
    with pytest.raises(AmdSmiLibraryException) as exc:
        amdsmi.amdsmi_get_power_cap(None)
    assert exc.value.err_code == amdsmi.AmdSmiRetCode.STATUS_INVAL

Verify RED — Watch It Fail

MANDATORY. Never skip.

# C++
cd build && make -j$(nproc) amdsmitst && \
  ./tests/amd_smi_test/amdsmitst --gtest_filter=AmdSmiTest.GetGpuPowerCapHandlesInvalidHandle

# Python
cd tests/python_unittest && python3 -m pytest -v -k test_get_gpu_power_cap_raises_on_invalid_handle

Confirm:

  • Test fails (not errors out)
  • Failure message matches what you expected
  • It fails because the behavior is missing, not because of a typo

Test passes immediately? You're testing existing behavior. Fix the test.

GREEN — Minimal Code

Simplest code that makes the failing test pass. No extra features, no flexibility flags, no "while I'm here" improvements.

Verify GREEN

MANDATORY.

Re-run the same test. Confirm pass. Then run the relevant suite to confirm nothing else broke.

REFACTOR — Clean Up

After green only. Remove duplication, improve names, extract helpers. Keep tests green. Don't add behavior.

Repeat

One failing test for the next behavior.

One Test at a Time — No Horizontal Slicing

Do NOT write all the tests first, then all the implementation. Treating RED as "write every test" and GREEN as "write every implementation" is horizontal slicing and it produces crap tests:

  • Bulk-written tests verify imagined behavior, not actual behavior.
  • You end up testing the shape of things (signatures, struct fields) instead of observable behavior.
  • The tests go insensitive to real changes — they pass when behavior breaks.

Correct approach — vertical slices (tracer bullets): one test → one minimal implementation → repeat. Each test responds to what the previous cycle taught you.

WRONG (horizontal):
  RED:   test1, test2, test3, test4
  GREEN: impl1, impl2, impl3, impl4

RIGHT (vertical):
  RED→GREEN: test1→impl1
  RED→GREEN: test2→impl2
  ...

amd-smi cascade caution: it's tempting, when adding a C API function, to write the GTest and the Python unit test and the CLI test all up front. Don't. Drive one tracer bullet end-to-end first (e.g. C++ behavior), then add the next layer's test once that layer exists. The cascade is still fully tested — just one slice at a time, not all tests before any code.

Good Tests

Quality Good Bad
Minimal One thing. "and" in the name? Split it. test('validates handle and reads register and formats output')
Clear name test_get_power_cap_returns_inval_on_null_handle test_power_1
Real code Calls the actual amdsmi_* function Mocks the function being tested
Observable Tests behavior the user sees Tests internal state nobody else can read

Rationalizations — Reality

Excuse Reality
"I'll write tests after to verify it works" Tests-after pass immediately and prove nothing. You never saw them catch the bug.
"I already manually tested all edge cases" Manual testing is ad-hoc, undocumented, and doesn't re-run on CI.
"Deleting X hours of work is wasteful" Sunk cost. Working code without real tests is technical debt.
"TDD is dogmatic, being pragmatic means adapting" TDD IS pragmatic — finds bugs before commit, prevents regressions, enables refactoring.
"Tests-after achieve the same goal — it's about spirit" No. Tests-after answer "what does this do?" Tests-first answer "what should this do?"
"This is just a one-line fix" One-line fixes have caused production outages. Write the test.
"The CI will catch it" CI catches what you tested. If you didn't write the test, CI is blind.

Red Flags — STOP and Start Over

  • Code before test
  • "I already verified it manually"
  • "Tests after achieve the same purpose"
  • "It's about the spirit, not the ritual"
  • "This case is different because..."
  • Watching the test pass without ever seeing it fail
  • Adding production code while writing the test
  • Writing every layer's test up front before any implementation (horizontal slicing)

All of these mean: delete the code, start over with the test.

amd-smi-Specific Notes

  • Cascade testing: A new API function needs tests at multiple layers — at minimum the C++ GTest and a Python unit test. CLI changes need a CLI test.
  • Hardware dependency: Most amd-smi tests require an AMD GPU. If your test cannot run on the dev box, say so in the test plan in the spec; don't silently skip the RED step.
  • Auto-generated wrapper: When adding a C function, the wrapper regen is part of the GREEN step, not a separate "fix later".

Required Companion Skills

  • systematic-debugging — when a test fails in a way you don't understand
  • verification-before-completion — after every commit