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:
20 files changed
+174
-50
No files matched your search
@@ -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 \
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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; }
|
||||||
|
|||||||
Reference in new issue
Block a user