Test fixture: a throwaway iPod with no hardware #3

Closed
opened 2026-08-25 22:19:36 +02:00 by wokoman · 1 comment
wokoman commented 2026-08-25 22:19:36 +02:00 (Migrated from code.nolog.cz)

What to build

Tests can stand up a real, empty iPod in a temporary directory and talk to it exactly as they would a mounted device — parse it, copy a track in, read the track back, remove it — with no hardware attached and nothing left behind afterwards.

Today the only constructor for a database is one that parses a mounted device, so every test that touches the copy or removal paths is #[ignore]d behind IRONPOD_MOUNT. That means the code with the most destructive failure modes is the code CI never runs. This ticket removes that constraint; it is a prefactor, and the tickets that follow are unverifiable without it.

libgpod can do this unaided: its initialisation entry point takes a mountpoint, seeds the model into a sysinfo file, creates the directory skeleton, adds a master playlist and writes an empty database. No physical device is involved anywhere in that path. It is already exposed through the existing bindings — no new FFI, no build changes.

Acceptance criteria

  • A test helper creates an empty iPod in a temp directory and returns a parsed database for it
  • The helper is visible to unit tests in src/ (integration tests in tests/ stay hardware-gated and are out of scope)
  • Cleanup is automatic, including when a test panics
  • A test proves the round trip: create a fixture, copy an audio file in, reopen it, and find the track with its metadata intact
  • The full test suite passes on a machine with no iPod attached, and adds no new #[ignore]
  • The temp directory and the database handle are released in the correct order, with a comment saying why the order matters — the handle points into the directory that cleanup deletes

Notes

Keep the helper private to the crate's own tests. There is a known community pattern for sharing fixtures with integration tests as well (a self dev-dependency behind a doc-hidden feature); it is deliberately not adopted here, because nothing in tests/ needs the fixture yet. Adopt it the day something does.

libgpod's initialisation function leaks its database handle on one error path — when directory creation fails. It is upstream, in vendored code, and affects only failed fixture setup. Noted, not fixed here.

Blocked by

  • None — can start immediately.
## What to build Tests can stand up a real, empty iPod in a temporary directory and talk to it exactly as they would a mounted device — parse it, copy a track in, read the track back, remove it — with no hardware attached and nothing left behind afterwards. Today the only constructor for a database is one that parses a mounted device, so every test that touches the copy or removal paths is `#[ignore]`d behind `IRONPOD_MOUNT`. That means the code with the most destructive failure modes is the code CI never runs. This ticket removes that constraint; it is a prefactor, and the tickets that follow are unverifiable without it. libgpod can do this unaided: its initialisation entry point takes a mountpoint, seeds the model into a sysinfo file, creates the directory skeleton, adds a master playlist and writes an empty database. No physical device is involved anywhere in that path. It is already exposed through the existing bindings — no new FFI, no build changes. ## Acceptance criteria - [ ] A test helper creates an empty iPod in a temp directory and returns a parsed database for it - [ ] The helper is visible to unit tests in `src/` (integration tests in `tests/` stay hardware-gated and are out of scope) - [ ] Cleanup is automatic, including when a test panics - [ ] A test proves the round trip: create a fixture, copy an audio file in, reopen it, and find the track with its metadata intact - [ ] The full test suite passes on a machine with no iPod attached, and adds no new `#[ignore]` - [ ] The temp directory and the database handle are released in the correct order, with a comment saying why the order matters — the handle points into the directory that cleanup deletes ## Notes Keep the helper private to the crate's own tests. There is a known community pattern for sharing fixtures with integration tests as well (a self dev-dependency behind a doc-hidden feature); it is deliberately **not** adopted here, because nothing in `tests/` needs the fixture yet. Adopt it the day something does. libgpod's initialisation function leaks its database handle on one error path — when directory creation fails. It is upstream, in vendored code, and affects only failed fixture setup. Noted, not fixed here. ## Blocked by - None — can start immediately.
Owner

Done on main in ac3e765.

src/fixture.rs — FakeIpod::new() calls itdb_init_ipod on a temp directory (no new FFI, the itdb_.* allowlist already had it) and open() parses it like any mounted device. Two things worth recording:

  • Model A002 (iPod Video 1st gen), not the test device's xB029. Classic 1st gen resolves to ITDB_CHECKSUM_HASH58, which needs a FirewireGuid a temp directory cannot supply; Video 1st gen is ITDB_CHECKSUM_NONE, so writes succeed.
  • Drop order is handled by declaration order rather than by a lifetime — let ipod = ...; let db = ipod.open(); drops the handle first, and the doc comment says so.

Beyond the acceptance criteria: db.rs already had a private TempDir + write_minimal_itunes_db pair in its test module. Its four tests now use the shared fixture and the duplicate is gone — one way to make a test database instead of two.

60 unit tests pass with nothing attached, no new #[ignore], clippy clean. The round trip asserts the copied file is where the reopened database says it is.

Done on `main` in ac3e765. `src/fixture.rs` — `FakeIpod::new()` calls `itdb_init_ipod` on a temp directory (no new FFI, the `itdb_.*` allowlist already had it) and `open()` parses it like any mounted device. Two things worth recording: - **Model `A002` (iPod Video 1st gen), not the test device's `xB029`.** Classic 1st gen resolves to `ITDB_CHECKSUM_HASH58`, which needs a `FirewireGuid` a temp directory cannot supply; Video 1st gen is `ITDB_CHECKSUM_NONE`, so writes succeed. - **Drop order is handled by declaration order** rather than by a lifetime — `let ipod = ...; let db = ipod.open();` drops the handle first, and the doc comment says so. Beyond the acceptance criteria: `db.rs` already had a private `TempDir` + `write_minimal_itunes_db` pair in its test module. Its four tests now use the shared fixture and the duplicate is gone — one way to make a test database instead of two. 60 unit tests pass with nothing attached, no new `#[ignore]`, clippy clean. The round trip asserts the copied file is where the reopened database says it is.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
wokoman/ironpod#3
No description provided.