krz/orgo

Lightning fast org-mode static site generator.

clone: git clone https://gitbay.org/krz/orgo.git

e4f2adf25bbe5fa016ad55a6d32e79c6ad3981e1

verified · cmc

author: Christian Cleberg <hello@cleberg.net> · 2026-08-11T21:51:52Z

Stop the watch test racing the watcher's own startup

The first CI run failed on Linux and passed on macOS. The test made its edit
300ms after spawning the watcher thread, and `watch` registers its OS watches
*after* an initial build — one that loads syntect's syntax set. On a cold runner
that takes longer than the head start, so the write landed before anything was
listening, produced no event, and looked exactly like a watcher that does not
work.

The edit now repeats every 400ms until the rebuild is observed, within 30s. A
missed event and a broken watcher are indistinguishable from outside, and only
one of them is worth failing a build over.

The feedback-loop check that follows it waits longer before its first reading,
so it measures rebuilds caused by the build's own writes rather than the one
caused by the last repeated edit.
 tests/watch.rs | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/tests/watch.rs b/tests/watch.rs
index 3c904dc..f19258b 100644
--- a/tests/watch.rs
+++ b/tests/watch.rs
@@ -152,13 +152,21 @@ fn watching_rebuilds_the_site_when_a_source_file_changes() {
         let _ = orgo::watch::run(&src_t, &out_t, &BuildOptions::default());
     });
 
-    // Give the watcher a moment to register before making the change it should see.
-    std::thread::sleep(Duration::from_millis(300));
-    std::fs::write(src.join("index.org"), "#+TITLE: Home\n\nSecond version.\n").unwrap();
-
-    let deadline = Instant::now() + Duration::from_secs(20);
+    // The edit is repeated rather than made once after a fixed head start. The watcher
+    // registers its OS watches *after* an initial build, and that build loads syntect's
+    // syntax set — on a cold CI runner, comfortably longer than any head start worth
+    // hard-coding. An edit made before anything is listening produces no event at all,
+    // which looks exactly like a watcher that does not work, and only one of those is a
+    // defect worth failing a build over. (Found by the first CI run on Linux, where a
+    // 300ms head start was not enough and macOS had never noticed.)
+    let deadline = Instant::now() + Duration::from_secs(30);
     let mut rebuilt = false;
+    let mut wrote_at: Option<Instant> = None;
     while Instant::now() < deadline {
+        if wrote_at.is_none_or(|t| t.elapsed() >= Duration::from_millis(400)) {
+            std::fs::write(src.join("index.org"), "#+TITLE: Home\n\nSecond version.\n").unwrap();
+            wrote_at = Some(Instant::now());
+        }
         if std::fs::read_to_string(out.join("index.html"))
             .map(|h| h.contains("Second version."))
             .unwrap_or(false)
@@ -168,7 +176,7 @@ fn watching_rebuilds_the_site_when_a_source_file_changes() {
         }
         std::thread::sleep(Duration::from_millis(50));
     }
-    assert!(rebuilt, "an edit should trigger a rebuild within 20s");
+    assert!(rebuilt, "an edit should trigger a rebuild within 30s");
 
     // The output lives inside the source, so the rebuild's own writes raised events. If
     // those are not filtered out, watch spins forever.
@@ -178,8 +186,10 @@ fn watching_rebuilds_the_site_when_a_source_file_changes() {
     // runaway loop — an assertion on it passes whether or not the filter works, which is
     // exactly what it did before this comment existed. `syntax.css` is rewritten on
     // every build, so its mtime is a direct record of how many builds have run.
+    // Long enough for the rebuild from the last repeated write to have landed before the
+    // first reading, or this measures that instead of a feedback loop.
     let stylesheet = out.join("syntax.css");
-    std::thread::sleep(Duration::from_millis(700));
+    std::thread::sleep(Duration::from_millis(1500));
     let first = std::fs::metadata(&stylesheet).unwrap().modified().unwrap();
     std::thread::sleep(Duration::from_millis(1200));
     let second = std::fs::metadata(&stylesheet).unwrap().modified().unwrap();