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
193 lines
6.8 KiB
Markdown
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
|