fix(ci): eliminate test-crates build warnings

Correct dead platform declarations, stale rustdoc and test warnings; validate complete gettext headers. Install LLD in the Qt job and explicitly validate committed shader packs without rebaking them. Keep the independent shader bake gate and add three build-policy tests.

The full local gate passed 2398 workspace tests and 715 Qt tests, QML audits, shader baking and the isolated offscreen smoke without compiler, rustdoc or gettext warnings.

cx
This commit is contained in:
vicrodh committed 2026-09-05 20:31:05 -06:00
1 parent 9fd39edf5f
commit 39ba3282f5
20 files changed
+174 -50

No files matched your search

+10 -4
View File
@@ -1,7 +1,8 @@
# CI for the crates/ workspace on the integration branch. # CI for the crates/ workspace on the integration branch.
# #
# Three jobs, three economies: # Three jobs, three economies:
# test — `cargo test` on every crate that needs no Qt (scripts/cargo-test.sh: # test — validate all eight gettext catalogs, then `cargo test` on
# every crate that needs no Qt (scripts/cargo-test.sh:
# excludes the frozen Slint crates and qbz-qt), then the named # excludes the frozen Slint crates and qbz-qt), then the named
# listen-log suite check (qbz-app::listen_log must exist and be # listen-log suite check (qbz-app::listen_log must exist and be
# green — the per-user listen store's schema, accumulator, end # green — the per-user listen store's schema, accumulator, end
@@ -74,7 +75,7 @@ jobs:
# OpenSSL (reqwest). # OpenSSL (reqwest).
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
build-essential pkg-config \ build-essential pkg-config gettext \
libasound2-dev libjack-jackd2-dev \ libasound2-dev libjack-jackd2-dev \
libdbus-1-dev libssl-dev libdbus-1-dev libssl-dev
@@ -135,13 +136,17 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
build-essential pkg-config cmake clang libclang-dev nasm dbus \ build-essential pkg-config cmake clang libclang-dev lld nasm dbus \
libasound2-dev libjack-jackd2-dev libdbus-1-dev libssl-dev \ libasound2-dev libjack-jackd2-dev libdbus-1-dev libssl-dev \
libgl1-mesa-dev libegl1-mesa-dev libxkbcommon-dev libwayland-dev libgl1-mesa-dev libegl1-mesa-dev libxkbcommon-dev libwayland-dev
# cxx-qt prefers LLD when present; without it, Ubuntu falls back to
# the deprecated gold linker. Do not add workspace-wide RUSTFLAGS.
ld.lld --version
# No qtshadertools here ON PURPOSE: with qsb on PATH build.rs re-bakes # No qtshadertools here ON PURPOSE: with qsb on PATH build.rs re-bakes
# shaders by mtime, which makes a fresh checkout nondeterministic. The # shaders by mtime, which makes a fresh checkout nondeterministic. The
# build ships the committed .qsb; shader health is the shader-gate job. # build explicitly validates/uses committed .qsb; shader health is the
# shader-gate job, independent of tools accidentally present on PATH.
- name: Install Qt ${{ env.QT_VERSION }} - name: Install Qt ${{ env.QT_VERSION }}
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
@@ -188,6 +193,7 @@ jobs:
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
QBZ_QT_QML_DIR: ${{ env.QT_ROOT_DIR }}/qml QBZ_QT_QML_DIR: ${{ env.QT_ROOT_DIR }}/qml
QBZ_PREBUILT_SHADERS: '1'
run: | run: |
set -euo pipefail set -euo pipefail
hits=$(cargo tree --manifest-path crates/Cargo.toml -p qbz-qt -e normal \ hits=$(cargo tree --manifest-path crates/Cargo.toml -p qbz-qt -e normal \
+1 -1
View File
@@ -384,7 +384,7 @@ mod tests {
} }
#[test] #[test]
fn the_fingerprint_identifies_the_MEDIUM_not_the_drive() { fn the_fingerprint_identifies_the_medium_not_the_drive() {
let a = fear_inoculum(); let a = fear_inoculum();
let mut b = fear_inoculum(); let mut b = fear_inoculum();
b.device = PathBuf::from("/dev/sr3"); b.device = PathBuf::from("/dev/sr3");
@@ -2,6 +2,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: qbz-slint\n" "Project-Id-Version: qbz-slint\n"
"PO-Revision-Date: 2026-09-05 19:52-0600\n"
"Last-Translator: QBZ contributors\n"
"Language-Team: QBZ de translators\n"
"Language: de\n" "Language: de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -1,6 +1,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: qbz-slint\n" "Project-Id-Version: qbz-slint\n"
"PO-Revision-Date: 2026-09-05 19:52-0600\n"
"Last-Translator: QBZ contributors\n"
"Language-Team: QBZ en translators\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@@ -2,6 +2,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: qbz-slint\n" "Project-Id-Version: qbz-slint\n"
"PO-Revision-Date: 2026-09-05 19:52-0600\n"
"Last-Translator: QBZ contributors\n"
"Language-Team: QBZ es translators\n"
"Language: es\n" "Language: es\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -2,6 +2,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: qbz-slint\n" "Project-Id-Version: qbz-slint\n"
"PO-Revision-Date: 2026-09-05 19:52-0600\n"
"Last-Translator: QBZ contributors\n"
"Language-Team: QBZ fr translators\n"
"Language: fr\n" "Language: fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -1,6 +1,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: qbz-slint\n" "Project-Id-Version: qbz-slint\n"
"PO-Revision-Date: 2026-09-05 19:52-0600\n"
"Last-Translator: QBZ contributors\n"
"Language-Team: QBZ ja translators\n"
"Language: ja\n" "Language: ja\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -2,6 +2,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: qbz-slint\n" "Project-Id-Version: qbz-slint\n"
"PO-Revision-Date: 2026-09-05 19:52-0600\n"
"Last-Translator: QBZ contributors\n"
"Language-Team: QBZ nl translators\n"
"Language: nl\n" "Language: nl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -2,6 +2,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: qbz-slint\n" "Project-Id-Version: qbz-slint\n"
"PO-Revision-Date: 2026-09-05 19:52-0600\n"
"Last-Translator: QBZ contributors\n"
"Language-Team: QBZ pt translators\n"
"Language: pt\n" "Language: pt\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -1,6 +1,9 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: qbz-slint\n" "Project-Id-Version: qbz-slint\n"
"PO-Revision-Date: 2026-09-05 19:52-0600\n"
"Last-Translator: QBZ contributors\n"
"Language-Team: QBZ ru translators\n"
"Language: ru\n" "Language: ru\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
+3 -3
View File
@@ -1,7 +1,7 @@
//! GitHub-ready diagnostics bundle formatter. //! GitHub-ready diagnostics bundle formatter.
//! //!
//! Produces a collapsible `<details>` markdown block (a bold field header + a fenced //! Produces a collapsible `<details>` markdown block (a bold field header + a
//! ```log block of the last N lines) suitable for pasting straight into a GitHub issue. //! log-language fenced block of the last N lines) for pasting into a GitHub issue.
//! Lines are redacted defensively even though the ring already holds redacted text. //! Lines are redacted defensively even though the ring already holds redacted text.
use crate::line::LogLine; use crate::line::LogLine;
@@ -20,7 +20,7 @@ pub struct DiagFields<'a> {
} }
/// Format a GitHub-ready diagnostics bundle: a `<details>` wrapper, a bold field header, /// Format a GitHub-ready diagnostics bundle: a `<details>` wrapper, a bold field header,
/// and a fenced ```log block of the last `max_lines.min(last_lines.len())` lines. /// and a log-language fenced block of the last `max_lines.min(last_lines.len())` lines.
pub fn format_diagnostics_bundle( pub fn format_diagnostics_bundle(
f: &DiagFields, f: &DiagFields,
last_lines: &[LogLine], last_lines: &[LogLine],
@@ -177,9 +177,9 @@ mod tests {
#[test] #[test]
fn windows_1252_accents_are_recovered() { fn windows_1252_accents_are_recovered() {
// "Sigur Rós" in Windows-1252: ó is 0xF3, which is invalid UTF-8 alone. // "Sigur Rós" in Windows-1252: ó is 0xF3, which is invalid UTF-8 alone.
let bytes = b"Sigur R\xF3s"; let bytes = b"Sigur R\xF3s".to_vec();
assert!(std::str::from_utf8(bytes).is_err()); assert!(std::str::from_utf8(&bytes).is_err());
assert_eq!(decode_text(bytes), "Sigur Rós"); assert_eq!(decode_text(&bytes), "Sigur Rós");
} }
#[test] #[test]
+19 -8
View File
@@ -2,6 +2,8 @@ use std::path::Path;
use cxx_qt_build::{CxxQtBuilder, QmlModule}; use cxx_qt_build::{CxxQtBuilder, QmlModule};
mod build_support;
/// Collect every file under `dir` (recursive), crate-root-relative — the /// Collect every file under `dir` (recursive), crate-root-relative — the
/// baked icon variants (qml/assets/icons/<tint>/<name>.svg) are too many to /// baked icon variants (qml/assets/icons/<tint>/<name>.svg) are too many to
/// list by hand. /// list by hand.
@@ -177,6 +179,8 @@ fn find_qsb() -> Option<std::path::PathBuf> {
/// ///
/// Missing `qsb` is a WARNING, not an error: the committed `.qsb` are still /// Missing `qsb` is a WARNING, not an error: the committed `.qsb` are still
/// in the tree and still load, so a box without Qt's shader tools can build. /// in the tree and still load, so a box without Qt's shader tools can build.
/// CI explicitly sets `QBZ_PREBUILT_SHADERS=1`: validate the committed packs
/// without looking for qsb or rewriting them. A separate gate tests baking.
/// ///
/// THE BAKE MUST BE IDEMPOTENT, and that is not a nicety — it is the whole /// THE BAKE MUST BE IDEMPOTENT, and that is not a nicety — it is the whole
/// reason `up_to_date` exists. `qsb` REWRITES its output unconditionally: /// reason `up_to_date` exists. `qsb` REWRITES its output unconditionally:
@@ -190,25 +194,32 @@ fn find_qsb() -> Option<std::path::PathBuf> {
/// 21e941bdf together with the bake itself, and it is why the tree started /// 21e941bdf together with the bake itself, and it is why the tree started
/// rebuilding on every run when nothing had changed. /// rebuilding on every run when nothing had changed.
fn build_shaders() { fn build_shaders() {
println!("cargo:rerun-if-env-changed=QBZ_PREBUILT_SHADERS");
println!("cargo:rerun-if-env-changed=QSB");
let prebuilt =
build_support::use_prebuilt_shaders(std::env::var("QBZ_PREBUILT_SHADERS").ok().as_deref())
.unwrap_or_else(|error| panic!("{error}"));
let dir = Path::new("qml/assets/shaders"); let dir = Path::new("qml/assets/shaders");
if !dir.is_dir() { let qsb = if prebuilt { None } else { find_qsb() };
return; if !prebuilt && qsb.is_none() {
}
let Some(qsb) = find_qsb() else {
println!( println!(
"cargo:warning=qsb not found — shaders keep their committed .qsb. \ "cargo:warning=qsb not found — shaders keep their committed .qsb. \
Set QSB=/path/to/qsb to re-bake them." Set QSB=/path/to/qsb to re-bake them."
); );
return; }
}; for entry in std::fs::read_dir(dir).expect("read shaders dir") {
for entry in std::fs::read_dir(dir).expect("read shaders dir").flatten() { let src = entry.expect("read shader entry").path();
let src = entry.path();
let ext = src.extension().and_then(|e| e.to_str()).unwrap_or(""); let ext = src.extension().and_then(|e| e.to_str()).unwrap_or("");
if !matches!(ext, "frag" | "vert") { if !matches!(ext, "frag" | "vert") {
continue; continue;
} }
println!("cargo:rerun-if-changed={}", src.display()); println!("cargo:rerun-if-changed={}", src.display());
let out = src.with_extension(format!("{ext}.qsb")); let out = src.with_extension(format!("{ext}.qsb"));
let Some(qsb) = qsb.as_ref() else {
build_support::require_prebuilt_shader(&src).unwrap_or_else(|error| panic!("{error}"));
println!("cargo:rerun-if-changed={}", out.display());
continue;
};
if up_to_date(&src, &out) { if up_to_date(&src, &out) {
continue; continue;
} }
+94
View File
@@ -0,0 +1,94 @@
//! Pure build-policy checks, also exercised by the Qt unit-test gate.
use std::path::Path;
pub fn use_prebuilt_shaders(value: Option<&str>) -> Result<bool, String> {
match value {
None | Some("0") => Ok(false),
Some("1") => Ok(true),
Some(value) => Err(format!(
"QBZ_PREBUILT_SHADERS must be 0 or 1, got {value:?}"
)),
}
}
/// Prebuilt mode must not turn a missing pack into a successful build.
/// Shader compilation/variant coverage remains the separate shader bake gate.
pub fn require_prebuilt_shader(source: &Path) -> Result<(), String> {
let extension = source.extension().and_then(|s| s.to_str()).unwrap_or("");
let pack = source.with_extension(format!("{extension}.qsb"));
match std::fs::metadata(&pack) {
Ok(meta) if meta.is_file() && meta.len() > 0 => Ok(()),
_ => Err(format!(
"missing or empty prebuilt shader {}; re-bake with qsb before building",
pack.display()
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
struct Fixture(PathBuf);
impl Fixture {
fn new() -> Self {
static NEXT: AtomicU64 = AtomicU64::new(0);
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let dir = std::env::temp_dir().join(format!(
"qbz-shader-policy-{}-{nonce}-{}",
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed)
));
std::fs::create_dir(&dir).unwrap();
Self(dir)
}
}
impl Drop for Fixture {
fn drop(&mut self) {
std::fs::remove_dir_all(&self.0).unwrap();
}
}
#[test]
fn prebuilt_mode_requires_an_explicit_valid_choice() {
assert!(!use_prebuilt_shaders(None).unwrap());
assert!(!use_prebuilt_shaders(Some("0")).unwrap());
assert!(use_prebuilt_shaders(Some("1")).unwrap());
assert!(use_prebuilt_shaders(Some("true")).is_err());
assert!(use_prebuilt_shaders(Some("")).is_err());
}
#[test]
fn prebuilt_mode_rejects_missing_empty_and_directory_packs() {
let fixture = Fixture::new();
let source = fixture.0.join("effect.frag");
let pack = fixture.0.join("effect.frag.qsb");
assert!(require_prebuilt_shader(&source).is_err());
std::fs::write(&pack, []).unwrap();
assert!(require_prebuilt_shader(&source).is_err());
std::fs::remove_file(&pack).unwrap();
std::fs::create_dir(&pack).unwrap();
assert!(require_prebuilt_shader(&source).is_err());
}
#[test]
fn prebuilt_mode_preserves_both_shader_stages_byte_for_byte() {
let fixture = Fixture::new();
for stage in ["frag", "vert"] {
let source = fixture.0.join(format!("effect.{stage}"));
let pack = fixture.0.join(format!("effect.{stage}.qsb"));
let bytes = b"prebuilt fixture";
std::fs::write(&pack, bytes).unwrap();
require_prebuilt_shader(&source).unwrap();
assert_eq!(std::fs::read(&pack).unwrap(), bytes);
}
}
}
+1
View File
@@ -74,6 +74,7 @@ fn select_link(args: &[String]) -> Option<String> {
/// ///
/// The single-instance server feeds it whatever arrived on the pipe, which is /// The single-instance server feeds it whatever arrived on the pipe, which is
/// not argv and is not ours. /// not argv and is not ours.
#[cfg(any(target_os = "windows", test))]
pub(crate) fn is_actionable(url: &str) -> bool { pub(crate) fn is_actionable(url: &str) -> bool {
if !is_qobuz_link(url) && !url.starts_with("qbz://") { if !is_qobuz_link(url) && !url.starts_with("qbz://") {
return false; return false;
+3
View File
@@ -13,6 +13,9 @@
//! async work; results hop back to Qt through the bridge's `CxxQtThread`. //! async work; results hop back to Qt through the bridge's `CxxQtThread`.
mod auth_qt; mod auth_qt;
#[cfg(test)]
#[path = "../build_support.rs"]
mod build_support;
mod deep_link_qt; mod deep_link_qt;
mod listen_log_qt; mod listen_log_qt;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
-14
View File
@@ -1341,12 +1341,6 @@ impl QtQconnectService {
self.enable_intent.current_token().is_some() self.enable_intent.current_token().is_some()
} }
/// Admit one complete owner-side mutation. The permit must stay alive
/// through every await and spawned continuation that can touch owner state.
pub fn try_owner_action_permit(&self) -> Option<AuthorityActionPermit> {
self.authority.try_owner_action_permit()
}
/// Admit the owner observation that will produce an async playback /// Admit the owner observation that will produce an async playback
/// snapshot, returning its exact authority generation with the permit. /// snapshot, returning its exact authority generation with the permit.
pub fn try_owner_action_permit_observed( pub fn try_owner_action_permit_observed(
@@ -1361,14 +1355,6 @@ impl QtQconnectService {
self.authority.observe_owner_authority() self.authority.observe_owner_authority()
} }
/// Re-admit only a continuation produced by the exact owner observation.
pub fn try_owner_action_permit_exact(
&self,
token: OwnerAuthorityToken,
) -> Option<AuthorityActionPermit> {
self.authority.try_owner_action_permit_exact(token)
}
/// Preserve already-stamped owner work across a fallible candidate fence. /// Preserve already-stamped owner work across a fallible candidate fence.
/// It returns `None` only once that exact owner generation is truly stale. /// It returns `None` only once that exact owner generation is truly stale.
pub async fn wait_for_exact_owner_action_permit( pub async fn wait_for_exact_owner_action_permit(
+1
View File
@@ -10,6 +10,7 @@ use std::ptr::NonNull;
unsafe extern "C" { unsafe extern "C" {
fn qbz_main_window_hwnd() -> *mut c_void; fn qbz_main_window_hwnd() -> *mut c_void;
fn qbz_install_commit_data_handler(cb: extern "C" fn()); fn qbz_install_commit_data_handler(cb: extern "C" fn());
#[cfg(target_os = "windows")]
fn qbz_install_hittest_filter(); fn qbz_install_hittest_filter();
} }
+5 -16
View File
@@ -108,7 +108,7 @@ pub fn play(state: &ApiState) -> Response<Cursor<Vec<u8>>> {
/// `POST /api/playback/pause` (02 §3.3.6). Never cold-starts; exit set is /// `POST /api/playback/pause` (02 §3.3.6). Never cold-starts; exit set is
/// 0 · 1 · 3 (no 5, §2.2) so a `Player::pause` channel failure is /// 0 · 1 · 3 (no 5, §2.2) so a `Player::pause` channel failure is
/// [`runtime_error`], not [`device_error`]. /// [`runtime_error`], not `audio_unavailable`.
pub fn pause(state: &ApiState) -> Response<Cursor<Vec<u8>>> { pub fn pause(state: &ApiState) -> Response<Cursor<Vec<u8>>> {
let _transport_lease = match transport_action_lease(state) { let _transport_lease = match transport_action_lease(state) {
Ok(lease) => lease, Ok(lease) => lease,
@@ -122,9 +122,10 @@ pub fn pause(state: &ApiState) -> Response<Cursor<Vec<u8>>> {
/// `POST /api/playback/toggle` (02 §3.3.7). Mirrors the desktop's /// `POST /api/playback/toggle` (02 §3.3.7). Mirrors the desktop's
/// `toggle_play_pause`: playing -> pause; paused-with-loaded-audio -> resume; /// `toggle_play_pause`: playing -> pause; paused-with-loaded-audio -> resume;
/// nothing loaded -> cold-start (same gate as `play`). Exit 5 is reserved for /// nothing loaded -> cold-start (same gate as `play`). The cold-start branch
/// the cold-start branch (`cold_start`'s own [`device_error`]) — the /// can return `audio_unavailable`; an accepted load reports later stream
/// pause/resume branches use [`runtime_error`] like plain `pause`/`stop`. /// failures through daemon state. The pause/resume branches use
/// [`runtime_error`] like plain `pause`/`stop`.
pub fn toggle(state: &ApiState) -> Response<Cursor<Vec<u8>>> { pub fn toggle(state: &ApiState) -> Response<Cursor<Vec<u8>>> {
let transport_lease = match transport_action_lease(state) { let transport_lease = match transport_action_lease(state) {
Ok(lease) => lease, Ok(lease) => lease,
@@ -534,18 +535,6 @@ fn auth_gate(state: &ApiState) -> Option<Response<Cursor<Vec<u8>>>> {
} }
} }
/// 503 `audio_unavailable` — the frozen taxonomy's device/audio bucket
/// (02 §3.1.3), exit 5. Reserved for GENUINE audio/device conditions: the
/// DSD-direct guards (handled inline via `err_json`, not this helper) and
/// cold-start's `play_track_resolved` failure (no device / stream resolve
/// failed). Each route's documented exit set (02 §2.2) decides which one
/// applies — `pause`/`stop`/plain `seek`/`volume`/`next`/`prev` never list
/// exit 5, so their `Player`/`QbzCore` command failures use
/// [`runtime_error`] instead.
fn device_error(message: &str) -> Response<Cursor<Vec<u8>>> {
err_json(503, "audio_unavailable", message, "check: qbzd status")
}
/// A generic runtime failure, exit 1 (02 §1.3's catch-all) — e.g. the /// A generic runtime failure, exit 1 (02 §1.3's catch-all) — e.g. the
/// player's command channel is dead. `code` "internal" is NOT one of /// player's command channel is dead. `code` "internal" is NOT one of
/// `error_from_envelope`'s special-cased codes, so it falls to /// `error_from_envelope`'s special-cased codes, so it falls to
+10 -1
View File
@@ -45,6 +45,11 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT" cd "$ROOT"
say() { printf '[cargo-test] %s\n' "$*"; } say() { printf '[cargo-test] %s\n' "$*"; }
say "gate: all eight gettext catalogs"
for locale in en es de fr pt ru ja nl; do
msgfmt --check --output-file=/dev/null "crates/qbz-i18n/translations/$locale/LC_MESSAGES/qbz-ui.po"
done
say "job test: cargo test --workspace --exclude qbz-qt (jobs=${CARGO_BUILD_JOBS:-all cores})" say "job test: cargo test --workspace --exclude qbz-qt (jobs=${CARGO_BUILD_JOBS:-all cores})"
cargo test \ cargo test \
--manifest-path crates/Cargo.toml \ --manifest-path crates/Cargo.toml \
@@ -125,10 +130,14 @@ hits=$(cargo tree --manifest-path crates/Cargo.toml -p qbz-qt -e normal \
[[ -z "$hits" ]] || { echo "qbz-qt graph resolves Slint crates:"; echo "$hits"; exit 1; } [[ -z "$hits" ]] || { echo "qbz-qt graph resolves Slint crates:"; echo "$hits"; exit 1; }
say "qt gate 4/5: cargo test -p qbz-qt (debug)" say "qt gate 4/5: cargo test -p qbz-qt (debug)"
# Match qt-gate: shader_bake_gate above proves compilation independently;
# the binary embeds the committed packs without mtime-driven rewrites.
export QBZ_PREBUILT_SHADERS=1
cargo test --manifest-path crates/Cargo.toml -p qbz-qt --no-fail-fast cargo test --manifest-path crates/Cargo.toml -p qbz-qt --no-fail-fast
say "qt gate 5/5: offscreen boot of the debug binary" say "qt gate 5/5: offscreen boot of the debug binary"
cargo build --manifest-path crates/Cargo.toml -p qbz-qt cargo build --manifest-path crates/Cargo.toml -p qbz-qt
target_dir="${CARGO_TARGET_DIR:-$ROOT/crates/target}"
log="$(mktemp "${TMPDIR:-/tmp}/qbz-test-smoke-XXXXXX")" log="$(mktemp "${TMPDIR:-/tmp}/qbz-test-smoke-XXXXXX")"
# Isolated: its own XDG dirs (never the developer's config/session) and a # Isolated: its own XDG dirs (never the developer's config/session) and a
# PRIVATE session bus — the single-instance lock is a D-Bus well-known name, # PRIVATE session bus — the single-instance lock is a D-Bus well-known name,
@@ -137,7 +146,7 @@ log="$(mktemp "${TMPDIR:-/tmp}/qbz-test-smoke-XXXXXX")"
iso="$(mktemp -d "${TMPDIR:-/tmp}/qbz-test-xdg-XXXXXX")" iso="$(mktemp -d "${TMPDIR:-/tmp}/qbz-test-xdg-XXXXXX")"
bus=(); command -v dbus-run-session >/dev/null && bus=(dbus-run-session --) bus=(); command -v dbus-run-session >/dev/null && bus=(dbus-run-session --)
XDG_CONFIG_HOME="$iso/config" XDG_DATA_HOME="$iso/data" XDG_CACHE_HOME="$iso/cache" XDG_STATE_HOME="$iso/state" \ XDG_CONFIG_HOME="$iso/config" XDG_DATA_HOME="$iso/data" XDG_CACHE_HOME="$iso/cache" XDG_STATE_HOME="$iso/state" \
QT_QPA_PLATFORM=offscreen RUST_LOG=info "${bus[@]}" timeout 75 ./crates/target/debug/qbz > "$log" 2>&1 || true QT_QPA_PLATFORM=offscreen RUST_LOG=info "${bus[@]}" timeout 75 "$target_dir/debug/qbz" > "$log" 2>&1 || true
rm -rf "$iso" rm -rf "$iso"
lines=$(wc -l < "$log") lines=$(wc -l < "$log")
(( lines >= 10 )) || { cat "$log"; echo "smoke: the app did not start ($lines lines)"; exit 1; } (( lines >= 10 )) || { cat "$log"; echo "smoke: the app did not start ($lines lines)"; exit 1; }