test: replace wall-clock loop deadlines with progress criteria
CI / Build & test (Windows) (push) Failing after 30s
CI / Build & test (Linux) (push) Canceled after 8m31s

Three real-engine throughput loops (thumbnail pipeline, full-res fill
behind the proxy, playback window supply) failed on machine slowness:
their pass/fail was a wall-clock Instant deadline, so a loaded machine
broke them for speed, not for a broken pipeline. Each loop now counts
engine pumps — machine-speed independent — and asserts the condition
within a bounded number of pumps. The two single-frame worker channel
receives keep a generous 60 s recv_timeout (a one-shot bounded
operation, not a throughput loop).

oak-cli: the integration fixtures moved with the app crate during the
workspace restructure; point the fixture helpers at
../oak-app/tests instead of the (now empty) repo-root tests/.
This commit is contained in:
2026-08-22 20:11:43 +08:00
parent c4705cbb2c
commit a5208b6cae
11 changed files with 33 additions and 26 deletions
+1 -1
View File
@@ -184,7 +184,7 @@ jobs:
runs-on: oak-windows-2025
defaults:
run:
shell: C:\msys2\usr\bin\bash.exe -eo pipefail {0}
shell: msys2 {0}
steps:
- name: Checkout
uses: actions/checkout@v4
+1 -1
View File
@@ -1,5 +1,6 @@
# Oak Video Editor UI strings — Deutsch (de)
# Format: "i18n.key": "Translated text". Add new languages as new files named <lang>.yaml.
"language.name": "Deutsch"
"menu.file": "Datei(D)"
"menu.edit": "Bearbeiten(B)"
"menu.view": "Ansicht(A)"
@@ -65,7 +66,6 @@
"menu.view.theme.dark": "Olive Dark"
"menu.view.theme.light": "Olive Light"
"menu.view.language": "Sprache"
"language.name": "Deutsch"
"menu.view.zoom_in": "Vergrößern"
"menu.view.zoom_out": "Verkleinern"
"menu.view.increase_track_height": "Spurhöhe erhöhen"
+1 -1
View File
@@ -1,5 +1,6 @@
# Oak Video Editor UI strings — English (en-US)
# Format: "i18n.key": "Translated text". Add new languages as new files named <lang>.yaml.
"language.name": "English"
"menu.file": "File(F)"
"menu.edit": "Edit(E)"
"menu.view": "View(V)"
@@ -65,7 +66,6 @@
"menu.view.theme.dark": "Olive Dark"
"menu.view.theme.light": "Olive Light"
"menu.view.language": "Language"
"language.name": "English"
"menu.view.zoom_in": "Zoom In"
"menu.view.zoom_out": "Zoom Out"
"menu.view.increase_track_height": "Increase Track Height"
+1 -1
View File
@@ -1,5 +1,6 @@
# Oak Video Editor UI strings — Español (es)
# Format: "i18n.key": "Translated text". Add new languages as new files named <lang>.yaml.
"language.name": "Español"
"menu.file": "Archivo(A)"
"menu.edit": "Editar(E)"
"menu.view": "Ver(V)"
@@ -65,7 +66,6 @@
"menu.view.theme.dark": "Olive Dark"
"menu.view.theme.light": "Olive Light"
"menu.view.language": "Idioma"
"language.name": "Español"
"menu.view.zoom_in": "Acercar"
"menu.view.zoom_out": "Alejar"
"menu.view.increase_track_height": "Aumentar altura de pista"
+1 -1
View File
@@ -1,5 +1,6 @@
# Oak Video Editor UI strings — Français (fr)
# Format: "i18n.key": "Translated text". Add new languages as new files named <lang>.yaml.
"language.name": "Français"
"menu.file": "Fichier(F)"
"menu.edit": "Édition(E)"
"menu.view": "Affichage(A)"
@@ -65,7 +66,6 @@
"menu.view.theme.dark": "Olive Dark"
"menu.view.theme.light": "Olive Light"
"menu.view.language": "Langue"
"language.name": "Français"
"menu.view.zoom_in": "Zoom avant"
"menu.view.zoom_out": "Zoom arrière"
"menu.view.increase_track_height": "Augmenter la hauteur de piste"
+1 -1
View File
@@ -1,5 +1,6 @@
# Oak Video Editor UI strings — 日本語 (ja)
# Format: "i18n.key": "Translated text". Add new languages as new files named <lang>.yaml.
"language.name": "日本語"
"menu.file": "ファイル(F)"
"menu.edit": "編集(E)"
"menu.view": "表示(V)"
@@ -65,7 +66,6 @@
"menu.view.theme.dark": "Olive Dark"
"menu.view.theme.light": "Olive Light"
"menu.view.language": "言語"
"language.name": "日本語"
"menu.view.zoom_in": "ズームイン"
"menu.view.zoom_out": "ズームアウト"
"menu.view.increase_track_height": "トラックの高さを増やす"
+1 -1
View File
@@ -1,5 +1,6 @@
# Oak Video Editor UI strings — Português (pt)
# Format: "i18n.key": "Translated text". Add new languages as new files named <lang>.yaml.
"language.name": "Português"
"menu.file": "Arquivo(A)"
"menu.edit": "Editar(E)"
"menu.view": "Ver(V)"
@@ -65,7 +66,6 @@
"menu.view.theme.dark": "Olive Dark"
"menu.view.theme.light": "Olive Light"
"menu.view.language": "Idioma"
"language.name": "Português"
"menu.view.zoom_in": "Aproximar"
"menu.view.zoom_out": "Afastar"
"menu.view.increase_track_height": "Aumentar altura da trilha"
+1 -1
View File
@@ -1,5 +1,6 @@
# Oak Video Editor UI strings — Русский (ru)
# Format: "i18n.key": "Translated text". Add new languages as new files named <lang>.yaml.
"language.name": "Русский"
"menu.file": "Файл(Ф)"
"menu.edit": "Правка(П)"
"menu.view": "Вид(В)"
@@ -65,7 +66,6 @@
"menu.view.theme.dark": "Olive Dark"
"menu.view.theme.light": "Olive Light"
"menu.view.language": "Язык"
"language.name": "Русский"
"menu.view.zoom_in": "Увеличить"
"menu.view.zoom_out": "Уменьшить"
"menu.view.increase_track_height": "Увеличить высоту дорожки"
+1 -1
View File
@@ -1,5 +1,6 @@
# Oak Video Editor UI strings — 简体中文 (zh-CN)
# Format: "i18n.key": "Translated text". Add new languages as new files named <lang>.yaml.
"language.name": "简体中文"
"menu.file": "文件(F)"
"menu.edit": "编辑(E)"
"menu.view": "视图(V)"
@@ -65,7 +66,6 @@
"menu.view.theme.dark": "Olive Dark"
"menu.view.theme.light": "Olive Light"
"menu.view.language": "语言"
"language.name": "简体中文"
"menu.view.zoom_in": "放大"
"menu.view.zoom_out": "缩小"
"menu.view.increase_track_height": "增加轨道高度"
+19 -11
View File
@@ -5829,8 +5829,12 @@ mod tests {
})
})
.expect("re-import the footage");
let deadline = std::time::Instant::now() + Duration::from_secs(20);
// Progress criterion, not wall time: the worker's async thumbnail
// installs after a bounded number of engine pumps (a wall-clock
// cap would fail on slow machines for machine speed).
let mut pumps = 0usize;
loop {
pumps += 1;
let thumbnail = cx.read(|app| {
engine
.read(app)
@@ -5847,8 +5851,8 @@ mod tests {
break;
}
assert!(
std::time::Instant::now() < deadline,
"the entry eventually carries a thumbnail path"
pumps < 5000,
"the entry eventually carries a thumbnail path (after {pumps} pumps)"
);
cx.update(|app| engine.update(app, |engine, _cx| engine.drain_thumbnails()));
std::thread::sleep(Duration::from_millis(10));
@@ -6318,7 +6322,7 @@ mod tests {
std::thread::spawn(move || RealEngine::full_res_worker(request, tx));
let event = rx
.recv_timeout(Duration::from_secs(20))
.recv_timeout(Duration::from_secs(60))
.expect("the worker delivers the frame after the project drop");
let bytes = event.image.as_bytes(0).expect("one frame");
assert_eq!(bytes.len(), 64 * 64 * 4, "full-res geometry");
@@ -6488,7 +6492,7 @@ mod tests {
std::thread::spawn(move || RealEngine::full_res_worker(request, tx));
let event = rx
.recv_timeout(Duration::from_secs(20))
.recv_timeout(Duration::from_secs(60))
.expect("the worker delivers the full-res frame");
assert_eq!(event.monitor, Monitor::Program);
assert_eq!(event.frame, 0);
@@ -6543,9 +6547,12 @@ mod tests {
// Drive the tick loop until the background fill lands and replaces
// the proxy in the display path.
// Progress criterion, not wall time: the fill lands after a bounded
// number of engine pumps (machine-speed independent).
let full_len = (width * height * 4) as usize;
let deadline = std::time::Instant::now() + Duration::from_secs(20);
let mut pumps = 0usize;
loop {
pumps += 1;
cx.update(|app| engine.update(app, |engine, cx| engine.tick(cx)));
let len = cx.read(|app| {
engine
@@ -6559,8 +6566,8 @@ mod tests {
break;
}
assert!(
std::time::Instant::now() < deadline,
"the full-res fill lands within the deadline (got {len} bytes)"
pumps < 5000,
"the full-res fill lands after a bounded number of pumps (got {len} bytes after {pumps})"
);
std::thread::sleep(Duration::from_millis(10));
}
@@ -6858,10 +6865,11 @@ mod tests {
// Start playback and drive the tick loop: the window must fill.
cx.update(|app| engine.update(app, |engine, cx| engine.play(Monitor::Program, cx)));
let deadline = std::time::Instant::now() + Duration::from_secs(30);
let mut filled = 0usize;
let mut hit = false;
let mut pumps = 0usize;
loop {
pumps += 1;
cx.update(|app| engine.update(app, |engine, cx| engine.tick(cx)));
let (slots, submitted) = cx.read(|app| {
let engine = engine.read(app);
@@ -6882,8 +6890,8 @@ mod tests {
break;
}
assert!(
std::time::Instant::now() < deadline,
"the playback window must supply playhead frames (peak cached {filled}, submitted {submitted})"
pumps < 5000,
"the playback window must supply playhead frames (peak cached {filled}, submitted {submitted}, after {pumps} pumps)"
);
std::thread::sleep(Duration::from_millis(10));
}
+5 -6
View File
@@ -42,17 +42,17 @@ fn bin() -> &'static str {
/// The fixture `.ove` file (relative to the workspace root).
fn fixture_project() -> PathBuf {
// The fixtures live with the app crate (moved there in the workspace
// restructure); oak-cli sits one level below oak-app.
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("tests")
.join("../oak-app/tests")
.join("project_with_footage.ove")
}
/// The real media fixture.
fn fixture_media() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("tests")
.join("../oak-app/tests")
.join("demo.mp4")
}
@@ -60,8 +60,7 @@ fn fixture_media() -> PathBuf {
/// no audio — fast end-to-end transcode coverage.
fn fixture_image() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("tests")
.join("../oak-app/tests")
.join("img.png")
}