trainlog/docs/tests.md

345 lines
9.4 KiB
Markdown

# Tests and validation
## 1. Principle
A Trainlog feature is not complete without relevant validation.
Frozen formats, persistence migrations, synchronization semantics, and user-data
mutations require executable coverage where practical.
## 2. Frozen Trainlog JSON v1
Run:
```bash
python tools/validate_json.py
```
It validates:
- `examples/session-v1.json`;
- all positive fixtures;
- all negative fixtures.
A negative fixture passes only when Trainlog rejects it.
Semantic validation includes:
```text
stable-ID uniqueness
normalized exercise-name uniqueness
timestamp offsets
end > start
catalog/reference equality
tracking-mode consistency
load-mode/weight consistency
unknown-field rejection
non-blank notes
zero actual repetitions allowed
```
## 3. Import reconciliation contract
Run:
```bash
python tools/validate_import_contract.py
```
Coverage includes:
```text
same ID + same name/profile -> reuse
same ID + renamed display text -> controlled reuse/warning
same ID + incompatible mode -> reject
different ID + equivalent normalized name -> reject
new unique identity -> create
```
## 4. Desktop Meson suite
Current normal suite:
```text
1 database
2 catalog
3 equipment_catalog
4 session_detail
5 duration
6 body_metrics
7 bodyviz
8 exercise_performance
9 session_type_schema
10 session_edit
11 body_observation_edit
12 mtp
13 continuous_session
14 continuous_detail
15 exercise_profile_schema
16 usb
17 reps
18 variable_sets
19 schema_v5_migration
20 measured_max
21 body_analytics
22 terminal_input_event_type_policy
23 mobile_import_multi_occurrence
24 equipment_associations_exchange
25 mobile_import_variable_sets
```
Validated checkpoint:
```text
25/25 PASS
```
The desktop executable is additionally smoke-checked in isolated tmux PTYs at
100x30, the exact 72x20 minimum, and the 60x15 fallback; a resize down/up must
recover before a clean keyboard quit. Notcurses is verified as the executable's
direct terminal dependency with `readelf -d`.
Notable regression coverage:
- transactional persisted-session replacement;
- exercise removal from a session;
- body-observation stable-identity editing;
- profile-aware exercise constraints;
- continuous activity without fake sets;
- repetition shorthand/list/pyramid parsing;
- direct v4 -> v7 database migration;
- heterogeneous mobile-set import;
- Notcurses input lifecycle translation: PRESS/REPEAT are actionable while a
RELEASE event is consumed without creating a second navigation action.
- targetless mobile SETS persistence;
- mobile-import idempotence.
- V2 mobile import with repeated exercise occurrences and stable `entry_id`;
- companion equipment import after session import, including idempotent
reimport and independent associations for repeated occurrences;
- rejection of the historical equipment-import invocation without its required
`--database` target, followed by the corrected complete V2 export chain.
- stable-ID mobile-to-desktop rename reconciliation without duplicate catalog
rows or historical-reference replacement.
## 5. Build
```bash
meson setup --reconfigure build
meson compile -C build
meson test -C build --print-errorlogs
```
Strict warning flags remain active. Do not weaken warnings to make a change pass.
## 6. Android build
When Android code changes:
```bash
cd android
printf 'sdk.dir=%s\n' "$HOME/Android/Sdk" > local.properties
JAVA_HOME=/usr/lib/jvm/java-17-openjdk \
./gradlew assembleDebug
```
Install to the connected device when hardware behavior changes:
```bash
adb install -r app/build/outputs/apk/debug/app-debug.apk
```
Android repository host tests additionally cover exercise editing:
- trimmed rename preserves `exercise_id` and recalculates `normalized_name`;
- duplicate and invalid names are rejected;
- completed history and active-draft references resolve the renamed catalog row;
- a referenced profile change is explicitly rejected;
- same-ID PC-catalog rename reconciles in place without a duplicate.
## 7. Hardware MTP validation
Hardware probes and real synchronization are separate from the normal automated
suite because a test runner cannot assume an unlocked MTP phone.
Available probe binaries include:
```text
trainlog-usb-probe
trainlog-mtp-probe
trainlog-mtp-exchange-probe
trainlog-mtp-roundtrip-probe
trainlog-mtp-mobile-export-probe
```
Current physical baseline:
```text
USB_MTP_DETECTION=PASS
MTP_STORAGE_ACCESS=PASS
MTP_WRITE=PASS
MTP_LIST_FOLDER=PASS
MTP_READ=PASS
MTP_ROUNDTRIP=PASS
```
## 8. Bidirectional synchronization validation
Validated workflow:
```text
Android
-> Synchroniser maintenant
-> unique sr_ request
-> trainlog-syncd
-> shared engine
-> Android -> PC import
-> PC -> Android catalog
-> sy_ structured run
-> matching receipt
-> Android final status
```
Multiple distinct Android request IDs were processed successfully without
reprocessing one request as a new one.
The TUI also invokes the same engine manually and exposes structured run
details.
## 9. Sanitizers
For meaningful C checkpoints:
```bash
CC=clang meson setup build-asan \
-Db_sanitize=address,undefined \
-Db_lundef=false
meson compile -C build-asan
meson test -C build-asan --print-errorlogs
```
## 10. Pre-push checklist
```bash
meson compile -C build
meson test -C build --print-errorlogs
python tools/validate_json.py
python tools/validate_import_contract.py
git diff --check
git status --short
```
When Android changed, add:
```bash
cd android
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug
```
Documentation must describe the resulting state, not retain contradictory old
`NEXT` checkpoints.
## 11. Measured-max regression
The normal Meson suite contains:
```text
measured_max
```
Coverage proves:
- stronger ordinary training is ignored by measured-max classification;
- only `max_test` sessions participate;
- zero-repetition failed attempts are not promoted;
- newest successful explicit test is the current measurement;
- historical external-load record can remain older than current;
- lower assistance is better;
- no-load max tests compare actual reps/duration;
- external working loads round to the configured increment;
- working-load percentages reject assistance.
Current normal baseline:
```text
25/25 PASS
```
## 12. Body analytics regression
The Meson suite adds:
```text
body_analytics
```
Coverage includes:
- male circumference-formula branch;
- female circumference-formula branch;
- estimated fat and lean mass from real body weight;
- waist/hip, shoulder/waist, and chest/waist ratios;
- left/right asymmetry;
- profile-independent analytics without a configured estimation profile;
- missing required circumference handling;
- invalid estimation-profile rejection.
Current normal baseline:
```text
25/25 PASS
```
## 13. Android session draft v1
Android schema v4 introduced one durable active draft; the current additive
chain reaches schema v7 without clearing completed history or the draft. The
current host suite covers exercise
shapes and raw partial text, fresh repository restore, remove/discard, atomic
finalization and repeated-finalize rejection, rollback, catalog reconciliation,
missing-selection recovery, explicit DB-open failure, historical migration,
equipment selection and occurrence identity.
```bash
cd android
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew test
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew assembleDebug assembleDebugAndroidTest
adb install -r app/build/outputs/apk/debug/app-debug.apk
adb install -r app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk
adb shell am instrument -w -e package com.labfytools.trainlog \
com.labfytools.trainlog.test/androidx.test.runner.AndroidJUnitRunner
```
The device instrumentation suite has **5 tests**: two real-SQLite repository
checks and three production-screen Compose UI checks. Coverage includes exact
raw form restoration through Activity recreation, cancellation and confirmation
of discard, final save with no stale Resume after recreation, and refusal of a
second completion. All tests use isolated databases; the debug-only,
non-exported Activity renders production Home/Session screens without exporting
synthetic data. The phone must be unlocked and interactive. Do not interpret a
locked-screen `No compose hierarchies found` failure as a passing UI check.
The Samsung SM_G990B additionally passed the normal `MainActivity` matrix:
- two real catalog exercises with distinct continuous values survived Home,
another app, background `am kill` with verified PID exit, and force-stop;
- rotation recreated the Activity and Home Resume restored both exercises;
- removal of one exercise survived process death, with the other intact;
- raw duration/speed text survived process death and force-stop exactly;
- Back and the normal new-session action preserved the existing draft;
- discard cancellation preserved the DB exactly; confirmation and relaunch
left all draft tables empty and no Resume action;
- completed History and mobile export excluded the populated draft;
- every original domain row survived migration and the entire device matrix.
Final-save UI and repeated-finalization tests ran on-device with isolated data;
no fictitious completed session was added to the user's history. Real migration
and final state passed SQLite integrity/foreign-key checks. The pre-upgrade DB
and preferences backup is outside the repository. No uninstall, package-data
clear, desktop schema change or frozen artifact change is part of this repair.
Detailed retained evidence: [Android draft execution record](reviews/android_session_draft_v1_resume.md).