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

193 lines
6.8 KiB
Markdown

---
name: test-driven-development
description: "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:**
```cpp
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:**
```python
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.**
```bash
# 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