From b03cf1783b6a50f03968d6ef0b53f80ccfb95581 Mon Sep 17 00:00:00 2001 From: botandbang <337765928+botandbang@users.noreply.github.com> Date: Fri, 30 Oct 2026 11:02:10 -0400 Subject: [PATCH 1/2] test(locallibrary): format and eject disk images in the weekly run --- .github/workflows/locallibrary-scheduled.yml | 51 +++- apps/locallibrary/CHANGELOG.md | 4 + apps/locallibrary/CONTRIBUTING.md | 5 + apps/locallibrary/TODO.md | 5 - crates/locallibrary/src/format/disk_image.rs | 260 ++++++++++++++++++ .../src/format/disk_image/tests.rs | 155 +++++++++++ crates/locallibrary/src/format/linux.rs | 8 +- crates/locallibrary/src/format/linux/tests.rs | 23 ++ crates/locallibrary/src/format/macos.rs | 5 +- crates/locallibrary/src/format/mod.rs | 31 ++- crates/locallibrary/src/format/tests.rs | 27 +- crates/locallibrary/src/format/windows.rs | 2 +- 12 files changed, 552 insertions(+), 24 deletions(-) create mode 100644 crates/locallibrary/src/format/disk_image.rs create mode 100644 crates/locallibrary/src/format/disk_image/tests.rs diff --git a/.github/workflows/locallibrary-scheduled.yml b/.github/workflows/locallibrary-scheduled.yml index dd36fcb06..f72690f92 100644 --- a/.github/workflows/locallibrary-scheduled.yml +++ b/.github/workflows/locallibrary-scheduled.yml @@ -2,8 +2,9 @@ name: locallibrary scheduled # The slow suites, weekly and on demand (Actions > locallibrary scheduled > Run workflow, on any # branch), so pull requests only wait for locallibrary-ci.yml's quick checks: every feature -# combination, Bombadil, mutation testing, fuzzing, the Hegel property tests on fresh inputs, the -# CLI's timings against a week earlier and a release build of every target. Also a weekly RustSec check, which catches advisories published against +# combination, formatting and ejecting disk images, Bombadil, mutation testing, fuzzing, the Hegel +# property tests on fresh inputs, the CLI's timings against a week earlier and a release build of +# every target. Also a weekly RustSec check, which catches advisories published against # Cargo.lock between pushes. on: schedule: @@ -58,6 +59,38 @@ jobs: - run: sudo apt-get update && sudo apt-get install -y libasound2-dev - run: cargo hack clippy --workspace --feature-powerset --all-targets --locked -- -D warnings + # format::apply and eject on a real disk, on each system: each test creates a small disk image, + # attaches it (hdiutil, a loop device, a VHD), formats or ejects it, and detaches it + # (crates/locallibrary/src/format/disk_image.rs). Without LOCALLIBRARY_DISK_IMAGE_TESTS they + # return at once, so no other run touches a device. losetup, sfdisk and mkfs need root on Linux, + # so the test binary runs under sudo as nextest's target runner; Windows runners are + # administrators already. + disk-images: + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 45 + env: + LOCALLIBRARY_DISK_IMAGE_TESTS: "1" + # nextest's `ci` profile, as the features job has it. + NEXTEST_PROFILE: ci + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - run: rustup toolchain install + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + - uses: taiki-e/install-action@8cca0918f8471c33515202b73f56ab27f2bed283 # v2.87.29 + with: + tool: cargo-nextest + - if: runner.os == 'Linux' + run: | + sudo apt-get update && sudo apt-get install -y dosfstools exfatprogs + echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER=sudo --preserve-env" >> "$GITHUB_ENV" + - run: cargo nextest run -p locallibrary --lib --locked --no-fail-fast -E 'test(/^format::disk_image::/)' + # Bombadil explores the TUI and the mix report (property-based UI testing; see # apps/locallibrary/bombadil/). # macOS and Linux only: Bombadil has no Windows build. @@ -443,7 +476,19 @@ jobs: # that passes closes it (scheduled-issue.yml). report: needs: - [advisories, features, bombadil, mutants, fuzz, hegel, cli-timings, release-plan, release-build, release-global] + [ + advisories, + features, + disk-images, + bombadil, + mutants, + fuzz, + hegel, + cli-timings, + release-plan, + release-build, + release-global, + ] if: ${{ !cancelled() && github.event_name == 'schedule' }} permissions: actions: read diff --git a/apps/locallibrary/CHANGELOG.md b/apps/locallibrary/CHANGELOG.md index 7b4efc9ed..803a2ae75 100644 --- a/apps/locallibrary/CHANGELOG.md +++ b/apps/locallibrary/CHANGELOG.md @@ -679,6 +679,10 @@ their old names. folders, `drive disks` and `format` refuse while it's set, it lists each volume once without the boot volume, and every command warns when it's set. `LOCALLIBRARY_AUDIO=silent`, another, has the player play to no output at all, in real time, so a harness can play tracks unheard. +- `format` always refuses disk images, loop devices and virtual disks: + `LOCALLIBRARY_FORMAT_VIRTUAL`, which let it format any of them, is gone. The weekly scheduled + run tests `format` and `eject` on a disk image it attaches (hdiutil, a loop device, a VHD), and + only a test build lets that one image through. - The sidecar refuses relative paths to write to (`art.embed.*` files included), `mix.export` refuses to replace a file without `replace: true`, `drive.reidentify` is a `.plan`/`.apply` pair (the old name still works), a second SIGINT or SIGTERM waits for a database swap under way, diff --git a/apps/locallibrary/CONTRIBUTING.md b/apps/locallibrary/CONTRIBUTING.md index 57b3e93d1..c5738577e 100644 --- a/apps/locallibrary/CONTRIBUTING.md +++ b/apps/locallibrary/CONTRIBUTING.md @@ -94,6 +94,11 @@ branch from the Actions tab (locallibrary scheduled, Run workflow): - the builds without the default features (on Linux, without the ALSA headers): the tests of the library and the CLI with none and of the CLI with only `account`, and clippy over every combination of features with [cargo-hack](https://github.com/taiki-e/cargo-hack); +- `format` and `eject` on a disk image each test creates and attaches (hdiutil on macOS, a loop + device on Linux, a VHD on Windows; `crates/locallibrary/src/format/disk_image.rs`). They skip + themselves without `LOCALLIBRARY_DISK_IMAGE_TESTS=1`. On a Mac, + `LOCALLIBRARY_DISK_IMAGE_TESTS=1 cargo nextest run -p locallibrary --lib -E 'test(/^format::disk_image::/)'` + runs them; - the Bombadil explorations below; - mutation testing of the past week's changes to the `loclib-*` crates, the plugins and `rekordbox-pdb` with [cargo-mutants](https://mutants.rs) (settings in `.cargo/mutants.toml`), in diff --git a/apps/locallibrary/TODO.md b/apps/locallibrary/TODO.md index cfb4dd0df..84666a7ae 100644 --- a/apps/locallibrary/TODO.md +++ b/apps/locallibrary/TODO.md @@ -166,11 +166,6 @@ Open work left by the 2026-10-11 and 2026-10-12 sessions was added on 2026-10-12 ## Tests to add -- [ ] A CI-only `format::apply` test on a disk image per OS (hdiutil, a diskpart vdisk, losetup). - Linux first needs loop devices treated as removable under `LOCALLIBRARY_FORMAT_VIRTUAL` - (`format/linux.rs`). -- [ ] `eject` on a two-partition image on the macOS leg of that job: refusing when the store is on - another volume, and naming the volumes it unmounts. - [ ] `check --audio` on tiny committed MP3, FLAC and M4A fixtures, truncated in `tests/it/check.rs` (only WAV truncation is tested). The same fixtures cover MP3 gapless. - [ ] The underline on the current hot cue, with a ratatui `TestBackend`. diff --git a/crates/locallibrary/src/format/disk_image.rs b/crates/locallibrary/src/format/disk_image.rs new file mode 100644 index 000000000..f1c150489 --- /dev/null +++ b/crates/locallibrary/src/format/disk_image.rs @@ -0,0 +1,260 @@ +//! Disk images for the tests that format and eject a real disk. [`TestImage`] creates an empty +//! image, attaches it, and detaches it when it goes, also when the test fails. +//! +//! Only the scheduled workflow runs these tests, with `LOCALLIBRARY_DISK_IMAGE_TESTS=1` on each +//! system's runner: without it they return at once, so an ordinary `cargo test` never touches a +//! device. Linux and Windows need administrator rights (`losetup`, `Mount-DiskImage`). +//! +//! The format code refuses every disk image. [`TestImage::admit`] lets through the one a test +//! attached, by its exact identifier, on that test's thread only ([`super::TEST_IMAGE`]). + +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicU32, Ordering}; + +use super::{Cmd, Disk}; + +/// Whether this run may attach and format disk images. +fn enabled() -> bool { + std::env::var_os("LOCALLIBRARY_DISK_IMAGE_TESTS").is_some_and(|v| v == "1") +} + +/// True, and says so, when this run may not attach disk images: the test then returns. +pub(super) fn skipped() -> bool { + if enabled() { + return false; + } + eprintln!("skipped: set LOCALLIBRARY_DISK_IMAGE_TESTS=1 to attach and format disk images"); + true +} + +/// A volume label no other test running on this machine uses (the OS mounts volumes by label, +/// `/Volumes/