trainlog/docs/tests.md

9.4 KiB

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:

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:

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:

python tools/validate_import_contract.py

Coverage includes:

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:

 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:

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

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:

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:

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:

trainlog-usb-probe
trainlog-mtp-probe
trainlog-mtp-exchange-probe
trainlog-mtp-roundtrip-probe
trainlog-mtp-mobile-export-probe

Current physical baseline:

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:

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:

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

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:

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:

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:

25/25 PASS

12. Body analytics regression

The Meson suite adds:

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:

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.

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.