diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3f02c24..d2eba44 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -10,15 +10,13 @@ on: - "migrations-v2/**" - "tests/**" - ".github/workflows/**" + # Every pull request, with no path filter. The ruleset on `main` requires a + # CodeQL result, and a filtered run never comes for a branch that touches + # only `webapp/` or `docs/`: such a PR finished green and still BLOCKED, + # waiting for an analysis nothing would start. On those the analysis finds + # nothing new, which is the answer the requirement is waiting for. pull_request: branches: [main] - paths: - - "Cargo.toml" - - "Cargo.lock" - - "src/**" - - "migrations-v2/**" - - "tests/**" - - ".github/workflows/**" schedule: - cron: "34 7 * * 4" diff --git a/Cargo.toml b/Cargo.toml index 9c99c9a..995fb81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "2.0.0-beta.1" description = "Self-hosted music server with WaveFlow and Subsonic compatibility." authors = ["InstaZDLL "] edition = "2021" -rust-version = "1.94" +rust-version = "1.98" license = "AGPL-3.0-only" repository = "https://github.com/InstaZDLL/waveflow-server" homepage = "https://waveflow.app" diff --git a/Dockerfile b/Dockerfile index 6c7eefd..9d1088e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN bun install --frozen-lockfile COPY webapp/ ./ RUN bun run build -FROM rust:1.94-bookworm AS builder +FROM rust:1.98-bookworm AS builder WORKDIR /build # cmake builds the libopus that waveflow-core bundles (`opusic-sys`); the # rust image ships a C compiler but not cmake. diff --git a/README.md b/README.md index a8ca4e1..9d55e10 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

Version - Rust 1.94+ + Rust 1.98+ SQLite only OpenSubsonic Docker image on GHCR @@ -57,7 +57,7 @@ Open `http://your-host:4533` and sign in. The scan starts on its own. ### From source -Requires **Rust 1.94+** as well. +Requires **Rust 1.98+** as well. ```bash bun --cwd=webapp install diff --git a/src/media.rs b/src/media.rs index 26e0e34..028fa46 100644 --- a/src/media.rs +++ b/src/media.rs @@ -1316,9 +1316,19 @@ impl IntoResponse for MediaError { } } +/// How long `ffmpeg -version` and `ffprobe -version` get at start-up. +/// +/// The check refuses to boot on a binary that is missing or hangs; it is not +/// timing a healthy one. Five seconds was overrun on the Windows CI runner, +/// where a parallel test run starts several cold FFmpeg processes at once +/// (#281). Thirty still catches a hung binary, and a healthy boot answers long +/// before it, so the only thing the larger budget changes is how long a broken +/// installation takes to say so. +const TOOL_CHECK_TIMEOUT: Duration = Duration::from_secs(30); + async fn check_tool(path: &Path, name: &str) -> anyhow::Result<()> { let output = tokio::time::timeout( - Duration::from_secs(5), + TOOL_CHECK_TIMEOUT, Command::new(path) .arg("-version") .stdin(Stdio::null()) diff --git a/tests/migrations.rs b/tests/migrations.rs index b9a80a4..1a92482 100644 --- a/tests/migrations.rs +++ b/tests/migrations.rs @@ -40,11 +40,35 @@ use waveflow_server::Config; /// `git ls-tree --name-only migrations-v2/`, not read off the changelog, /// because the changelog records what was written and the tree records what /// shipped. +/// +/// Remembering was the whole mechanism, so +/// `the_version_being_shipped_has_its_schema_recorded` now holds it: the PR +/// that bumps `version` in `Cargo.toml` for a release fails until it adds that +/// tag here, with the newest migration of that moment as its baseline. const RELEASED_SCHEMAS: [(&str, i64); 2] = [ ("v2.0.0-beta.0", 20_260_823_020_000), ("v2.0.0-beta.1", 20_260_914_030_000), ]; +/// The crate's version is the tag it ships as, so it must name a line above. +/// +/// A release is cut by a PR that bumps `version` and is then tagged, and the +/// version stays put until the next one. So this holds between releases, and +/// fails exactly on the bump that forgot the schema — before the tag exists, +/// which is the only moment the baseline is still the newest migration and +/// needs no archaeology to find. +#[test] +fn the_version_being_shipped_has_its_schema_recorded() { + let tag = concat!("v", env!("CARGO_PKG_VERSION")); + assert!( + RELEASED_SCHEMAS + .iter() + .any(|(released, _)| *released == tag), + "Cargo.toml is at {tag}, and RELEASED_SCHEMAS has no line for it: add \ + (\"{tag}\", )" + ); +} + /// The identifiers the fixture below writes and the assertions read back. /// /// UUIDs because every real row's is: a fixture shaped unlike production is diff --git a/tests/uploads.rs b/tests/uploads.rs index c3986dc..2c6bfed 100644 --- a/tests/uploads.rs +++ b/tests/uploads.rs @@ -1465,8 +1465,18 @@ async fn abandoned_transfers_are_swept_without_a_negotiation() { // No negotiation, no request of any kind: only the scheduled sweep. state.services.spawn_upload_sweeper(); + // The sweep removes the file first and the row second, so waiting on the + // file alone and then counting the row once reads the gap between the + // two: a slow runner failed here with the row still present (#280). + let rows = || async { + sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM upload_session WHERE id=?") + .bind(&session) + .fetch_one(state.db.pool()) + .await + .unwrap() + }; let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); - while staging.exists() && std::time::Instant::now() < deadline { + while (staging.exists() || rows().await > 0) && std::time::Instant::now() < deadline { tokio::time::sleep(std::time::Duration::from_millis(25)).await; } @@ -1474,12 +1484,7 @@ async fn abandoned_transfers_are_swept_without_a_negotiation() { !staging.exists(), "an abandoned transfer must not hold the disk until someone happens to upload again" ); - let rows: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM upload_session WHERE id=?") - .bind(&session) - .fetch_one(state.db.pool()) - .await - .unwrap(); - assert_eq!(rows, 0); + assert_eq!(rows().await, 0); } /// A staging file that cannot be removed keeps its session.