krz/orgo

Lightning fast org-mode static site generator. fast go org-mode static-site-generator

Commit e4f2adf25b

e4f2adf25bbe5fa016ad55a6d32e79c6ad3981e1

parent: d78e4ddb72

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-11 21:51 UTC

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.

Layout: unified · split

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