Skip to content

A conformance corpus for FIT files: 104 edge cases with committed expected outputs

FIT is the binary format nearly every sports device records into. It's compact, well-specified — and in the wild, files are routinely imperfect: devices crash mid-write, batteries die during save, firmware writes timestamps from 1989, heart-rate straps emit 0xFF when they lose contact.

When I started building chiptime, the first artifact wasn't a parser. It was a catalog: 104 documented ways FIT files go wrong, compiled from format documentation, device forums, and years of public bug reports across the ecosystem. That catalog became the spec, via a structure I'd recommend to anyone parsing a hostile format.

1. Every edge case becomes a triplet

input.fit (generated by a committed script — never hand-edited, SHA-guarded), expected.json (the full parse output, committed), and case.json (what this case proves, plus the expected outcome in each parser mode). The corpus holds 71 public cases; real personal files live in a git-ignored private tier, because GPS traces are home addresses.

2. Expected outputs are byte-exact

The parser emits canonical JSON (RFC 8785): sorted keys, fixed number formatting, no environment leakage. Determinism turns "does the parser work?" into diff. Any behavior change shows up as a reviewable diff of expected outputs — there is no way to change behavior silently.

3. Modes are graded per case

Each case declares what strict, lenient, and forensic modes should do — raise this exact error code, recover with this warning, salvage this many records. Recovery behavior is pinned as precisely as happy-path behavior.

4. The corpus is the cross-language contract

The TypeScript implementation (in progress) must reproduce the same bytes from the same inputs. Ports drift when correctness lives in tests written per-language; they can't drift when correctness is a shared directory of files.

What it caught

A few things this structure surfaced that I'd never have unit-tested for: a session message whose totals disagree with its own records (kept both, surfaced the discrepancy); pool "lengths" of zero seconds from wall touches; a 12-bit timestamp field that rolls over every 4.27 seconds; sentinel heart-rate values that would silently poison averages if they ever reached statistics as numbers — they become null at decode, and zero stays zero, because coasting is real and dropout is absence.

The corpus, taxonomy, and parser are MIT: github.com/MaxGrgrv/chiptime — the full taxonomy is in the docs if you just want the catalog of horrors.