Testing Requirements & Methodology (TESTING.md)
August 27, 2026 · View on GitHub
English | 简体中文
Philosophy: ship only what is tested
Every committed feature must have unit and/or end-to-end coverage; environment-limited items must be explicitly marked ⚠️ and never passed off as tested.
This principle caught 4 shipped bugs during development (see the git history for details) — all of them invisible to pure unit tests and only triggered by real composition loading, the real tool pipeline, and real devices.
Three test layers
1. Unit tests (node --test, currently 92 cases)
test/*.test.mjs: parsers (devices/logcat/meminfo/gfxinfo/battery/sysinfo), logcat filter helpers, classifyFailure error classification, baseline storage/diff, RPC endpoints (fake adb backend injection), panel pure functions (formatLogcatBlock/formatSnapshotBlock/formatReportBlock/agent activity extraction), health report (crash classification/log aggregation/health summary, pure functions + collection integration), report storage, skill registration.
npm test # = npm run build && node --test "test/*.test.mjs"
2. Deterministic verification (for pure functions like error classification)
No model or device needed: run node directly against the installed npm package (~/.dsh/profiles/bench/node_modules/dsh-adb/lib/adb.js) and assert error codes with real adb stderr text. Used to verify the 0.1.3 DEVICE_NOT_FOUND fix. Note: the model sees the error message (a stderr excerpt), not the code — verifying codes requires this approach, not reading headless task output.
3. End-to-end smoke (headless + real device)
Full chain: npm install → bundle load → config override → tool registration → real adb call → parse → structured result → model consumption.
Smoke environment (ready on this machine):
- adb: resolve via PATH /
ANDROID_HOME/ANDROID_SDK_ROOT(or set the pluginadbPathconfig) - Smoke profile:
~/.dsh/profiles/bench, bundles =@deepseek-ai/dsh-base+@deepseek-ai/dsh-headless+dsh-adb;cordis.patch.ymlsetsadbPath/defaultSerial - Model credentials:
DEEPSEEK_API_KEYin~/.dsh/.credentials.yaml(never write the value in docs; inject into$env:DEEPSEEK_API_KEYbefore a headless run) - Device: USB-connected (USB debugging enabled and authorized)
How to run:
$dsh = "$HOME\.dsh\profiles\node_modules\@deepseek-ai\dsh\lib\bin.js"
$key = (Select-String -Path "$HOME\.dsh\.credentials.yaml" -Pattern '^DEEPSEEK_API_KEY:\s*(.+)$').Matches[0].Groups[1].Value.Trim()
$env:DEEPSEEK_API_KEY = $key
node $dsh --profile bench "call adb_devices and report the result"
Notes:
- Upgrading the plugin in the profile:
node $dsh plugin --profile bench add "dsh-adb@<version>"(pnpm intermittently EPERMs — retry; if it keeps failing,npm install dsh-adb@<version> --no-saveinto the profile's node_modules) - Headless tasks can intermittently stall (flaky model responses): run them as background jobs or shrink the task; session logs live in
~/.dsh/sessions(not in the profile dir) - Wireless connect tests need the device in TCP mode first:
adb -s <serial> tcpip 5555(adb tcpiptemporarily drops the USB channel — standard adb behavior)
Coverage matrix (current v1.6.1, all green)
Verified on: Android 13 automotive bench + Android 14 phone (Redmi K50 Pro) + emulator.
| Feature | Unit | E2E |
|---|---|---|
| adb_devices | ✅ | ✅ bench + phone |
| adb_connect failure/success paths | ✅ classifier | ✅ CONNECT_FAILED on unreachable + wireless connect success on phone |
| adb_disconnect | - | ✅ |
| adb_logcat foreground (level/tail) | ✅ | ✅ |
| adb_logcat tag/keyword filters | ✅ | ✅ positive matches |
| adb_logcat background collection | - | ✅ job_output read 43k lines / 7MB |
| adb_install success/failure | ✅ classifier | ✅ Success + INSTALL_PARSE_FAILED_NOT_APK |
| adb_file ls/-lR/push/pull/rm | - | ✅ all operations |
| adb_perf_snapshot meminfo | ✅ | ✅ bench + phone |
| adb_perf_snapshot gfxinfo | ✅ | ✅ |
| adb_perf_snapshot battery | ✅ | ✅ real phone data |
| adb_perf_baseline diff/store logic | ✅ unit (diff calc / lifecycle / corrupted file) | ✅ real-device full cycle (save→compare→list→delete) |
| adb_crash_report collection | ✅ parser | ✅ real-device crash buffer 43 entries + dropbox/process/memory |
| adb_device_report (one-click health) | ✅ pure funcs + collection | ✅ real-device (crash classification / tag aggregation / health summary) |
| adb_wait_for conditions | ✅ conditions + poll sequences | ✅ real-device (device-online / process matched) |
| adb_operation_ledger | ✅ record/list/rollback logic | ✅ headless record→persist→list; ⚠️ rollback real-device path pending device-online |
| adb_screenshot | ✅ capture flow + pngDimensions | ✅ emulator (1280x720 PNG) |
| adb_watch_crash | ✅ signature/increment/format/poll | ✅ real-device seed dedup; ⚠️ positive trigger pending a reproducible real crash |
| config overrides | - | ✅ |
| core error codes (5) | ✅ classifier | ✅ smoke/deterministic |
Regression checklist (run after any code change)
npm test(all unit tests green)- Headless smoke:
adb_devices+ the changed tool's success and failure paths - If the execution layer / classifier changed: deterministic classifyFailure verification
- If the background path changed: background logcat start/read/stop flow
- Real-device (when bench/phone attached): battery real data + wireless connect positive
- Before publishing:
npm pack --dry-runconfirms contents (lib/ + cordis.patch.yml + README + LICENSE)