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() {
152 let _ = orgo::watch::run(&src_t, &out_t, &BuildOptions::default()); 152 let _ = orgo::watch::run(&src_t, &out_t, &BuildOptions::default());
153 }); 153 });
154 154
155 // Give the watcher a moment to register before making the change it should see. 155 // The edit is repeated rather than made once after a fixed head start. The watcher
156 std::thread::sleep(Duration::from_millis(300)); 156 // registers its OS watches *after* an initial build, and that build loads syntect's
157 std::fs::write(src.join("index.org"), "#+TITLE: Home\n\nSecond version.\n").unwrap(); 157 // syntax set — on a cold CI runner, comfortably longer than any head start worth
158 158 // hard-coding. An edit made before anything is listening produces no event at all,
159 let deadline = Instant::now() + Duration::from_secs(20); 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);
160 let mut rebuilt = false; 163 let mut rebuilt = false;
164 let mut wrote_at: Option<Instant> = None;
161 while Instant::now() < deadline { 165 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 }
162 if std::fs::read_to_string(out.join("index.html")) 170 if std::fs::read_to_string(out.join("index.html"))
163 .map(|h| h.contains("Second version.")) 171 .map(|h| h.contains("Second version."))
164 .unwrap_or(false) 172 .unwrap_or(false)
@@ -168,7 +176,7 @@ fn watching_rebuilds_the_site_when_a_source_file_changes() {
168 } 176 }
169 std::thread::sleep(Duration::from_millis(50)); 177 std::thread::sleep(Duration::from_millis(50));
170 } 178 }
171 assert!(rebuilt, "an edit should trigger a rebuild within 20s"); 179 assert!(rebuilt, "an edit should trigger a rebuild within 30s");
172 180
173 // The output lives inside the source, so the rebuild's own writes raised events. If 181 // The output lives inside the source, so the rebuild's own writes raised events. If
174 // those are not filtered out, watch spins forever. 182 // those are not filtered out, watch spins forever.
@@ -178,8 +186,10 @@ fn watching_rebuilds_the_site_when_a_source_file_changes() {
178 // runaway loop — an assertion on it passes whether or not the filter works, which is 186 // runaway loop — an assertion on it passes whether or not the filter works, which is
179 // exactly what it did before this comment existed. `syntax.css` is rewritten on 187 // exactly what it did before this comment existed. `syntax.css` is rewritten on
180 // every build, so its mtime is a direct record of how many builds have run. 188 // 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.
181 let stylesheet = out.join("syntax.css"); 191 let stylesheet = out.join("syntax.css");
182 std::thread::sleep(Duration::from_millis(700)); 192 std::thread::sleep(Duration::from_millis(1500));
183 let first = std::fs::metadata(&stylesheet).unwrap().modified().unwrap(); 193 let first = std::fs::metadata(&stylesheet).unwrap().modified().unwrap();
184 std::thread::sleep(Duration::from_millis(1200)); 194 std::thread::sleep(Duration::from_millis(1200));
185 let second = std::fs::metadata(&stylesheet).unwrap().modified().unwrap(); 195 let second = std::fs::metadata(&stylesheet).unwrap().modified().unwrap();