Skip to main content

supercov_engine/
rust_worker.rs

1//! A Rust Worker a browser suite drives through `wrangler dev`.
2//!
3//! The crate is instrumented in the suite's copy and `worker-build` compiles
4//! it to wasm, where the region runtime keeps its region in linear memory
5//! behind two exports per crate: its address and a clear. Supercov points the
6//! copy's wrangler configuration at an entry module that remembers every wasm
7//! instance the isolate creates, and at an inspector port of its own, so the
8//! suite's own `wrangler dev` command is left as written. While the suite
9//! runs, a poller takes each instance's region through the inspector and
10//! clears it every few milliseconds. Each take is a window, credited by time
11//! to the one browser test whose span it overlaps, as for a Go or JVM server;
12//! what no single test's window holds is the run's background.
13
14use std::{
15    collections::BTreeMap,
16    fs,
17    io::Write,
18    net::{SocketAddr, TcpListener, TcpStream},
19    path::{Path, PathBuf},
20    sync::{
21        Arc,
22        atomic::{AtomicBool, Ordering},
23    },
24    thread::JoinHandle,
25    time::{Duration, Instant, SystemTime, UNIX_EPOCH},
26};
27
28use base64::Engine as _;
29use serde_json::{Value, json};
30
31use crate::{
32    integrity::{FrontendIntegrityInputs, create_explicit_run_integrity},
33    lifecycle::{note_kept_evidence, remove_stored_tree_deferred},
34    run_store::{RunIntegrity, RunMetadata},
35    rust_project::PreparedRustProject,
36    served_tests::ServedTest,
37};
38
39/// The module wrangler bundles in place of the project's own entry: it
40/// installs the registry, then is the project's entry.
41const ENTRY_FILE: &str = "supercov-worker-entry.mjs";
42const REGISTRY_FILE: &str = "supercov-worker-registry.mjs";
43
44/// Remember every wasm instance the isolate creates, for the inspector:
45/// workerd answers no heap queries, so instances cannot be found later.
46const REGISTRY: &str = r#"// Generated by Supercov: remembers this isolate's wasm instances.
47const instances = (globalThis.__supercovWasm ??= []);
48const Instance = WebAssembly.Instance;
49function Recorded(module, imports) {
50  const instance = new Instance(module, imports);
51  instances.push(instance);
52  return instance;
53}
54Recorded.prototype = Instance.prototype;
55WebAssembly.Instance = Recorded;
56const instantiate = WebAssembly.instantiate;
57WebAssembly.instantiate = async function (source, imports) {
58  const result = await instantiate.call(WebAssembly, source, imports);
59  instances.push(result instanceof Instance ? result : result.instance);
60  return result;
61};
62"#;
63
64/// Evaluated in the Worker's context on every poll: each instrumented
65/// crate's region as its header and the pages a probe touched since the
66/// last take, then cleared. A region with nothing new is left out, except
67/// the first time, which carries what `cfg!` said of the crate's gates.
68const TAKE: &str = r#"(() => {
69  const seen = (globalThis.__supercovTaken ??= new WeakSet());
70  const out = [];
71  for (const instance of globalThis.__supercovWasm ?? []) {
72    const exports = instance.exports;
73    for (const name of Object.keys(exports)) {
74      if (!name.startsWith('__supercov_region_')) continue;
75      const crate = name.slice('__supercov_region_'.length);
76      const packed = BigInt.asUintN(64, BigInt(exports[name]()));
77      const at = Number(packed >> 32n), length = Number(packed & 0xffffffffn);
78      if (!length || !exports.memory) continue;
79      const view = new DataView(exports.memory.buffer, at, length);
80      const first = !seen.has(exports);
81      if (!first && view.getUint32(40, true) === 0 && view.getUint32(44, true) === 0) continue;
82      seen.add(exports);
83      const bytes = new Uint8Array(exports.memory.buffer, at, length);
84      const encode = (from, to) => {
85        let text = '';
86        for (let index = from; index < to; index += 0x8000)
87          text += String.fromCharCode.apply(null, bytes.subarray(index, Math.min(to, index + 0x8000)));
88        return btoa(text);
89      };
90      const bodyAt = view.getUint32(32, true);
91      const segments = [[0, encode(0, bodyAt)]];
92      for (let page = 0; bodyAt + page * 4096 < length; page++) {
93        if (bytes[128 + (page >> 3)] & (1 << (page & 7))) {
94          const from = bodyAt + page * 4096;
95          segments.push([from, encode(from, Math.min(length, from + 4096))]);
96        }
97      }
98      out.push([crate, length, segments]);
99      exports['__supercov_clear_' + crate]();
100    }
101  }
102  return out;
103})()"#;
104
105/// How often the poller takes the regions. A window shorter than the gap
106/// between two tests is what lets each be credited its own.
107const POLL_INTERVAL: Duration = Duration::from_millis(5);
108
109/// A wrangler configuration Supercov can point at its entry.
110#[derive(Debug, Clone, PartialEq, Eq)]
111struct WorkerConfig {
112    path: PathBuf,
113    main: String,
114    inspector_port: Option<u16>,
115}
116
117/// The wrangler configurations under `root` that build a Rust Worker: a
118/// `[build] command` running `worker-build`, beside the crate's Cargo.toml.
119///
120/// A plain walk: a `.gitignore` above the project (a monorepo's, or the
121/// checkout a fixture sits in) must not hide the project itself.
122fn worker_configs(root: &Path) -> Vec<PathBuf> {
123    const SKIPPED: &[&str] = &[
124        "node_modules",
125        "target",
126        "build",
127        "dist",
128        ".git",
129        ".supercov",
130        ".wrangler",
131    ];
132    let mut found = Vec::new();
133    let mut directories = vec![(root.to_path_buf(), 0)];
134    while let Some((directory, depth)) = directories.pop() {
135        let Ok(entries) = fs::read_dir(&directory) else {
136            continue;
137        };
138        for entry in entries.flatten() {
139            let path = entry.path();
140            let name = entry.file_name();
141            let Some(name) = name.to_str() else {
142                continue;
143            };
144            let Ok(kind) = entry.file_type() else {
145                continue;
146            };
147            if kind.is_dir() {
148                if depth < 6 && !SKIPPED.contains(&name) {
149                    directories.push((path, depth + 1));
150                }
151                continue;
152            }
153            if !kind.is_file()
154                || !matches!(name, "wrangler.toml" | "wrangler.json" | "wrangler.jsonc")
155            {
156                continue;
157            }
158            let builds_rust = read_config(&path).is_some_and(|(_, document)| {
159                build_command(&document).is_some_and(|command| command.contains("worker-build"))
160            });
161            if builds_rust && path.with_file_name("Cargo.toml").is_file() {
162                found.push(path);
163            }
164        }
165    }
166    found.sort();
167    found
168}
169
170/// A configuration as JSON whatever it was written in.
171fn read_config(path: &Path) -> Option<(String, Value)> {
172    let text = fs::read_to_string(path).ok()?;
173    let document = if path
174        .extension()
175        .is_some_and(|extension| extension == "toml")
176    {
177        let table = text.parse::<toml::Table>().ok()?;
178        serde_json::to_value(table).ok()?
179    } else {
180        serde_json::from_str(&strip_jsonc(&text)).ok()?
181    };
182    Some((text, document))
183}
184
185fn build_command(document: &Value) -> Option<String> {
186    match document.get("build")?.get("command")? {
187        Value::String(command) => Some(command.clone()),
188        Value::Array(parts) => Some(
189            parts
190                .iter()
191                .filter_map(Value::as_str)
192                .collect::<Vec<_>>()
193                .join(" "),
194        ),
195        _ => None,
196    }
197}
198
199fn parse_config(path: &Path) -> Option<WorkerConfig> {
200    let (_, document) = read_config(path)?;
201    Some(WorkerConfig {
202        path: path.to_owned(),
203        main: document.get("main")?.as_str()?.to_owned(),
204        inspector_port: document
205            .get("dev")
206            .and_then(|dev| dev.get("inspector_port"))
207            .and_then(Value::as_u64)
208            .and_then(|port| u16::try_from(port).ok()),
209    })
210}
211
212/// JSON with comments and trailing commas, as wrangler.jsonc allows, made
213/// plain JSON. Strings are copied as they are.
214fn strip_jsonc(text: &str) -> String {
215    let mut out = String::with_capacity(text.len());
216    let mut characters = text.chars().peekable();
217    while let Some(character) = characters.next() {
218        match character {
219            '"' => {
220                out.push('"');
221                while let Some(inner) = characters.next() {
222                    out.push(inner);
223                    match inner {
224                        '\\' => {
225                            if let Some(escaped) = characters.next() {
226                                out.push(escaped);
227                            }
228                        }
229                        '"' => break,
230                        _ => {}
231                    }
232                }
233            }
234            '/' if characters.peek() == Some(&'/') => {
235                for inner in characters.by_ref() {
236                    if inner == '\n' {
237                        out.push('\n');
238                        break;
239                    }
240                }
241            }
242            '/' if characters.peek() == Some(&'*') => {
243                characters.next();
244                let mut previous = '\0';
245                for inner in characters.by_ref() {
246                    if previous == '*' && inner == '/' {
247                        break;
248                    }
249                    previous = inner;
250                }
251            }
252            _ => out.push(character),
253        }
254    }
255    // A comma before a closing bracket, past any whitespace.
256    let mut cleaned = String::with_capacity(out.len());
257    let mut in_string = false;
258    let mut escaped = false;
259    let bytes = out.as_bytes();
260    for (index, character) in out.char_indices() {
261        if in_string {
262            cleaned.push(character);
263            if escaped {
264                escaped = false;
265            } else if character == '\\' {
266                escaped = true;
267            } else if character == '"' {
268                in_string = false;
269            }
270            continue;
271        }
272        if character == '"' {
273            in_string = true;
274        } else if character == ',' {
275            let next = bytes[index + 1..]
276                .iter()
277                .find(|byte| !byte.is_ascii_whitespace());
278            if matches!(next, Some(b'}' | b']')) {
279                continue;
280            }
281        }
282        cleaned.push(character);
283    }
284    cleaned
285}
286
287/// Point the copy's configuration at the entry module and, given one, the
288/// inspector port. The rest of the configuration is the project's.
289fn rewrite_config(config: &WorkerConfig, entry: &str, port: Option<u16>) -> Result<(), String> {
290    let path = &config.path;
291    let text = fs::read_to_string(path).map_err(|error| format!("{}: {error}", path.display()))?;
292    let rewritten = if path
293        .extension()
294        .is_some_and(|extension| extension == "toml")
295    {
296        let mut document = text
297            .parse::<toml_edit::DocumentMut>()
298            .map_err(|error| format!("{}: {error}", path.display()))?;
299        document["main"] = toml_edit::value(entry);
300        if let (None, Some(port)) = (config.inspector_port, port) {
301            if !document.contains_key("dev") {
302                document["dev"] = toml_edit::table();
303            }
304            document["dev"]["inspector_port"] = toml_edit::value(i64::from(port));
305        }
306        document.to_string()
307    } else {
308        let mut document: Value = serde_json::from_str(&strip_jsonc(&text))
309            .map_err(|error| format!("{}: {error}", path.display()))?;
310        document["main"] = json!(entry);
311        if let (None, Some(port)) = (config.inspector_port, port) {
312            if !document.get("dev").is_some_and(Value::is_object) {
313                document["dev"] = json!({});
314            }
315            document["dev"]["inspector_port"] = json!(port);
316        }
317        serde_json::to_string_pretty(&document).map_err(|error| error.to_string())?
318    };
319    fs::write(path, rewritten).map_err(|error| format!("{}: {error}", path.display()))
320}
321
322/// Write the registry and Supercov's entry beside a configuration and point
323/// the configuration at it. A configuration that already names the entry --
324/// a copy kept from an earlier run -- is left as it is: the entry would
325/// otherwise import itself.
326fn point_at_entry(config: &WorkerConfig, port: Option<u16>) -> Result<(), String> {
327    if config.main.trim_start_matches("./") == ENTRY_FILE {
328        return Ok(());
329    }
330    let directory = config.path.parent().unwrap_or(Path::new("."));
331    fs::write(directory.join(REGISTRY_FILE), REGISTRY).map_err(|error| error.to_string())?;
332    let main = config.main.trim_start_matches("./");
333    let entry = format!(
334        "// Generated by Supercov: the project's entry, with its wasm instances remembered.\nimport './{REGISTRY_FILE}';\nexport * from './{main}';\nexport {{ default }} from './{main}';\n"
335    );
336    fs::write(directory.join(ENTRY_FILE), entry).map_err(|error| error.to_string())?;
337    rewrite_config(config, ENTRY_FILE, port)
338}
339
340/// A port nothing listens on now.
341fn free_port() -> Result<u16, String> {
342    let listener = TcpListener::bind("127.0.0.1:0").map_err(|error| error.to_string())?;
343    listener
344        .local_addr()
345        .map(|address| address.port())
346        .map_err(|error| error.to_string())
347}
348
349/// The cfg values of wasm32-unknown-unknown, as the crate's own toolchain
350/// reports them: what decides which items the target leaves out.
351pub(crate) fn wasm_target_cfg(
352    crate_directory: &Path,
353) -> Option<crate::rust_instrumenter::TargetCfg> {
354    let output = std::process::Command::new("rustc")
355        .args(["--print", "cfg", "--target", "wasm32-unknown-unknown"])
356        .current_dir(crate_directory)
357        .output()
358        .ok()?;
359    if !output.status.success() {
360        return None;
361    }
362    let values = String::from_utf8_lossy(&output.stdout)
363        .lines()
364        .filter_map(|line| line.parse::<cargo_platform::Cfg>().ok())
365        .collect::<Vec<_>>();
366    Some(crate::rust_instrumenter::TargetCfg::new(&values))
367}
368
369fn now_ms() -> i64 {
370    SystemTime::now()
371        .duration_since(UNIX_EPOCH)
372        .map(|elapsed| elapsed.as_millis() as i64)
373        .unwrap_or(0)
374}
375
376/// Takes the regions of one `wrangler dev` through its inspector until told
377/// to stop, appending a window per take with anything in it.
378struct Poller {
379    stop: Arc<AtomicBool>,
380    thread: Option<JoinHandle<()>>,
381}
382
383impl Poller {
384    fn start(port: u16, windows: PathBuf) -> Self {
385        let stop = Arc::new(AtomicBool::new(false));
386        let flag = Arc::clone(&stop);
387        let thread = std::thread::Builder::new()
388            .name("supercov-worker-poller".into())
389            .spawn(move || poll(port, &windows, &flag))
390            .ok();
391        Self { stop, thread }
392    }
393
394    /// Take once more if wrangler is still there, then stop.
395    fn finish(mut self) {
396        self.stop.store(true, Ordering::Release);
397        if let Some(thread) = self.thread.take() {
398            let _ = thread.join();
399        }
400    }
401}
402
403impl Drop for Poller {
404    fn drop(&mut self) {
405        self.stop.store(true, Ordering::Release);
406    }
407}
408
409fn poll(port: u16, windows: &Path, stop: &AtomicBool) {
410    let Ok(mut file) = fs::OpenOptions::new()
411        .create(true)
412        .append(true)
413        .open(windows)
414    else {
415        return;
416    };
417    let address = SocketAddr::from(([127, 0, 0, 1], port));
418    // wrangler starts when the suite asks for it, and restarts the Worker on
419    // a rebuild: connect, and connect again, until the run is over.
420    while !stop.load(Ordering::Acquire) {
421        let Ok(stream) = TcpStream::connect_timeout(&address, Duration::from_millis(200)) else {
422            std::thread::sleep(Duration::from_millis(100));
423            continue;
424        };
425        let _ = stream.set_nodelay(true);
426        let _ = stream.set_read_timeout(Some(Duration::from_millis(20)));
427        let Ok((mut socket, _)) =
428            tungstenite::client::client(format!("ws://127.0.0.1:{port}/ws"), stream)
429        else {
430            std::thread::sleep(Duration::from_millis(100));
431            continue;
432        };
433        session(&mut socket, &mut file, stop);
434        let _ = socket.close(None);
435    }
436}
437
438/// One inspector connection: enable the runtime, learn the Worker's
439/// execution context, and take its regions on the clock. Returns when the
440/// connection ends, or after the last take once the run is over.
441fn session(socket: &mut tungstenite::WebSocket<TcpStream>, file: &mut fs::File, stop: &AtomicBool) {
442    use tungstenite::{Error, Message};
443    let send = |socket: &mut tungstenite::WebSocket<TcpStream>, message: Value| {
444        socket
445            .send(Message::text(message.to_string()))
446            .map_err(|_| ())
447    };
448    if send(socket, json!({"id": 1, "method": "Runtime.enable"})).is_err() {
449        return;
450    }
451    let mut contexts = Vec::<i64>::new();
452    let mut next_id = 2_u64;
453    // The take in flight: its id, when it was sent, and where its window
454    // starts.
455    let mut awaiting: Option<(u64, Instant)> = None;
456    let mut window_start = now_ms();
457    let mut last_take = Instant::now() - POLL_INTERVAL;
458    let mut final_sent = false;
459    loop {
460        let stopping = stop.load(Ordering::Acquire);
461        if stopping && awaiting.is_none() && (final_sent || contexts.is_empty()) {
462            return;
463        }
464        if awaiting.is_none()
465            && let Some(context) = contexts.last()
466            && (stopping || last_take.elapsed() >= POLL_INTERVAL)
467        {
468            let id = next_id;
469            next_id += 1;
470            let request = json!({
471                "id": id,
472                "method": "Runtime.evaluate",
473                "params": {"expression": TAKE, "contextId": context, "returnByValue": true},
474            });
475            if send(socket, request).is_err() {
476                return;
477            }
478            awaiting = Some((id, Instant::now()));
479            last_take = Instant::now();
480            final_sent = stopping;
481        }
482        match socket.read() {
483            Ok(Message::Text(text)) => {
484                let Ok(message) = serde_json::from_str::<Value>(text.as_str()) else {
485                    continue;
486                };
487                match message.get("method").and_then(Value::as_str) {
488                    Some("Runtime.executionContextCreated") => {
489                        if let Some(id) = message["params"]["context"]["id"].as_i64() {
490                            contexts.push(id);
491                        }
492                    }
493                    Some("Runtime.executionContextDestroyed") => {
494                        if let Some(id) = message["params"]["executionContextId"].as_i64() {
495                            contexts.retain(|context| *context != id);
496                        }
497                    }
498                    Some("Runtime.executionContextsCleared") => contexts.clear(),
499                    _ => {}
500                }
501                if let Some((id, _)) = awaiting
502                    && message.get("id").and_then(Value::as_u64) == Some(id)
503                {
504                    awaiting = None;
505                    let end = now_ms();
506                    let regions = &message["result"]["result"]["value"];
507                    if regions
508                        .as_array()
509                        .is_some_and(|regions| !regions.is_empty())
510                    {
511                        let line = json!({"start": window_start, "end": end, "regions": regions});
512                        let _ = writeln!(file, "{line}");
513                    }
514                    window_start = end;
515                }
516            }
517            Ok(Message::Close(_)) => return,
518            Ok(_) => {}
519            Err(Error::Io(error))
520                if matches!(
521                    error.kind(),
522                    std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
523                ) => {}
524            Err(_) => return,
525        }
526        // A take the Worker never answered (it was replaced mid-evaluation)
527        // is given up; the next one covers the same span.
528        if awaiting.is_some_and(|(_, sent)| sent.elapsed() > Duration::from_secs(5)) {
529            awaiting = None;
530        }
531    }
532}
533
534/// One take as the poller appended it.
535struct Window {
536    start: i64,
537    end: i64,
538    /// Each region, rebuilt: crate key and bytes.
539    regions: Vec<(String, Vec<u8>)>,
540}
541
542fn read_windows(directory: &Path) -> Result<Vec<Window>, String> {
543    let mut files = fs::read_dir(directory)
544        .map(|entries| {
545            entries
546                .filter_map(Result::ok)
547                .map(|entry| entry.path())
548                .filter(|path| {
549                    path.extension()
550                        .is_some_and(|extension| extension == "windows")
551                })
552                .collect::<Vec<_>>()
553        })
554        .unwrap_or_default();
555    files.sort();
556    let mut windows = Vec::new();
557    for file in &files {
558        let text =
559            fs::read_to_string(file).map_err(|error| format!("{}: {error}", file.display()))?;
560        // A line cut short by Supercov being stopped mid-write is skipped.
561        for line in text.lines() {
562            let Ok(value) = serde_json::from_str::<Value>(line) else {
563                continue;
564            };
565            if let Some(window) = window_from(&value) {
566                windows.push(window);
567            }
568        }
569    }
570    Ok(windows)
571}
572
573fn window_from(value: &Value) -> Option<Window> {
574    let mut regions = Vec::new();
575    for region in value.get("regions")?.as_array()? {
576        let crate_key = region.get(0)?.as_str()?.to_owned();
577        let length = usize::try_from(region.get(1)?.as_u64()?).ok()?;
578        let mut bytes = vec![0_u8; length];
579        for segment in region.get(2)?.as_array()? {
580            let offset = usize::try_from(segment.get(0)?.as_u64()?).ok()?;
581            let data = base64::engine::general_purpose::STANDARD
582                .decode(segment.get(1)?.as_str()?)
583                .ok()?;
584            bytes
585                .get_mut(offset..offset + data.len())?
586                .copy_from_slice(&data);
587        }
588        regions.push((crate_key, bytes));
589    }
590    Some(Window {
591        start: value.get("start")?.as_i64()?,
592        end: value.get("end")?.as_i64()?,
593        regions,
594    })
595}
596
597/// The Rust Workers of one Cargo workspace a browser suite may start.
598pub struct RustWorkerServer {
599    project: PreparedRustProject,
600    pollers: Vec<Poller>,
601    integrity: RunIntegrity,
602    source_inputs: crate::source_manifest::Inputs,
603    source_roots: Option<Vec<String>>,
604    /// `.supercov/work/<run>/rust-worker`, holding `evidence/`.
605    directory: PathBuf,
606}
607
608impl RustWorkerServer {
609    /// Whether `root` holds a Rust Worker a suite could start.
610    pub fn present(root: &Path) -> bool {
611        !worker_configs(root).is_empty()
612    }
613
614    /// Instrument the copy's Worker crates and point their configurations at
615    /// Supercov's entry, one server per Cargo workspace. Empty when the
616    /// project has no Rust Worker.
617    pub fn prepare(
618        root: &Path,
619        workspace: &Path,
620        run_id: &str,
621        command: &[String],
622        diagnostics: &mut dyn Write,
623    ) -> Result<Vec<Self>, String> {
624        let configs = worker_configs(workspace);
625        // Configurations by the Cargo workspace their crate belongs to: a
626        // workspace is instrumented once, whatever number of Workers it holds.
627        let mut by_workspace = BTreeMap::<PathBuf, Vec<WorkerConfig>>::new();
628        for path in configs {
629            let Some(config) = parse_config(&path) else {
630                writeln!(
631                    diagnostics,
632                    "[supercov] {} names no `main`; its Worker is not measured",
633                    path.display()
634                )
635                .map_err(|error| error.to_string())?;
636                continue;
637            };
638            let crate_directory = path.parent().unwrap_or(workspace).to_owned();
639            let cargo_root = cargo_workspace_root(&crate_directory).unwrap_or(crate_directory);
640            by_workspace.entry(cargo_root).or_default().push(config);
641        }
642        let mut servers = Vec::new();
643        for (index, (cargo_root, configs)) in by_workspace.into_iter().enumerate() {
644            let Ok(relative) = cargo_root.strip_prefix(workspace) else {
645                continue;
646            };
647            let source_roots = crate::source_discovery::configured_source_roots(
648                &std::env::vars().collect::<BTreeMap<_, _>>(),
649            );
650            // The project's inputs, as a later query recomputes them to judge
651            // whether the run is current.
652            let mut integrity_inputs = crate::rust_run::collect_integrity_inputs(root, command)?;
653            crate::source_discovery::fold_roots_into_configuration(
654                &mut integrity_inputs.execution_configuration,
655                source_roots.as_deref(),
656            );
657            let source_inputs =
658                crate::source_capture::capture(root, "rust", integrity_inputs.assertion_paths())?;
659            let integrity = create_explicit_run_integrity(
660                root,
661                &integrity_inputs,
662                &FrontendIntegrityInputs::embedded_rust(),
663            )
664            .map_err(|error| error.to_string())?;
665            let cfg = wasm_target_cfg(&cargo_root);
666            let mut project =
667                crate::rust_project::prepare_rust_project_for(&cargo_root, None, cfg.as_ref())
668                    .map_err(|error| error.to_string())?;
669            crate::rust_wasm_evidence::root_paths(&mut project, relative);
670            let directory = root
671                .join(".supercov/work")
672                .join(run_id)
673                .join(format!("rust-worker-{index}"));
674            let evidence = directory.join("evidence");
675            fs::create_dir_all(&evidence).map_err(|error| error.to_string())?;
676            let mut pollers = Vec::new();
677            for (number, config) in configs.iter().enumerate() {
678                let port = match config.inspector_port {
679                    Some(port) => port,
680                    None => free_port()?,
681                };
682                point_at_entry(config, Some(port))?;
683                pollers.push(Poller::start(
684                    port,
685                    evidence.join(format!("{number:04}.windows")),
686                ));
687            }
688            servers.push(Self {
689                project,
690                pollers,
691                integrity,
692                source_inputs,
693                source_roots,
694                directory,
695            });
696        }
697        Ok(servers)
698    }
699
700    pub fn source_files(&self) -> usize {
701        self.project.source_files.len()
702    }
703
704    /// The source this Worker was instrumented from: an image built from an
705    /// earlier copy holds probes placed in other source.
706    pub fn source_fingerprint(&self) -> String {
707        self.integrity.fingerprint.source.clone()
708    }
709
710    /// What the suite's environment needs: a lint on the rewritten source
711    /// must not fail the Worker's build under the crate's own lint policy.
712    ///
713    /// Capped at `warn`, not `allow` as for `cargo test`: Cargo learns which
714    /// crate types a target supports from rustc's "dropping unsupported crate
715    /// type" warnings, and wasm32 drops `dylib` and `proc-macro`, so with
716    /// every lint allowed it cannot read the answer and stops the build. The
717    /// probes' parentheses, the one lint they raise, are allowed by name.
718    pub fn environment(&self) -> BTreeMap<String, String> {
719        let mut rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
720        if !rustflags.is_empty() {
721            rustflags.push(' ');
722        }
723        rustflags.push_str("--cap-lints=warn -A unused_parens");
724        BTreeMap::from([("RUSTFLAGS".into(), rustflags)])
725    }
726
727    /// Publish what the Workers ran as a Rust run of its own: each window
728    /// credited, by time, to the one browser test whose span it overlaps;
729    /// what no single test's does is the run's background. None when no
730    /// Worker ran or no browser test reported.
731    pub fn publish(
732        mut self,
733        root: &Path,
734        _evidence_run_id: &str,
735        metadata: RunMetadata,
736        served: &[ServedTest],
737        diagnostics: &mut dyn Write,
738    ) -> Result<Option<(PathBuf, RunMetadata)>, String> {
739        for poller in std::mem::take(&mut self.pollers) {
740            poller.finish();
741        }
742        let evidence_directory = self.directory.join("evidence");
743        let result = read_windows(&evidence_directory).and_then(|windows| {
744            crate::rust_wasm_evidence::publish(
745                root,
746                crate::rust_wasm_evidence::WasmRun {
747                    language: "Rust Worker",
748                    archive: "rust-worker-evidence.raw.gz",
749                    project: &self.project,
750                    integrity: &self.integrity,
751                    source_inputs: &self.source_inputs,
752                    source_roots: self.source_roots.clone(),
753                },
754                windows
755                    .into_iter()
756                    .map(|window| crate::rust_wasm_evidence::WasmRecord {
757                        owner: crate::rust_wasm_evidence::Owner::Window(window.start, window.end),
758                        regions: window.regions,
759                    })
760                    .collect(),
761                metadata,
762                served,
763                diagnostics,
764            )
765        });
766        let result = result.map_err(|error| {
767            let run = self
768                .directory
769                .parent()
770                .and_then(Path::file_name)
771                .map(|name| name.to_string_lossy().into_owned())
772                .unwrap_or_default();
773            note_kept_evidence(
774                root,
775                &evidence_directory,
776                &format!("{run}-rust-worker"),
777                error,
778            )
779        });
780        let _ = remove_stored_tree_deferred(root, &self.directory);
781        result
782    }
783}
784
785/// The program Supercov's `wrangler` stands in for, named in the test
786/// processes' environment.
787pub const REAL_WRANGLER: &str = "SUPERCOV_WRANGLER";
788/// The hidden commands the stand-in and its poller run as.
789pub const WRANGLER_COMMAND: &str = "__wrangler";
790pub const WRANGLER_POLL_COMMAND: &str = "__wrangler-poll";
791
792/// The Rust Workers a `cargo test` suite starts itself, with `wrangler dev`.
793///
794/// The crate is instrumented with the rest of the workspace, and `wrangler
795/// dev` builds it in the copy as it builds it anywhere. What it ran is read
796/// by a `wrangler` of Supercov's, first on the test processes' `PATH`: it
797/// starts a poller on an inspector port of its own and then becomes the real
798/// `wrangler`, so the test keeps the process and the signals it expects. The
799/// poller writes what it takes into the evidence directory of the test that
800/// started the Worker, and the run reads it there as that test's own: exact,
801/// not credited by time.
802pub struct CargoTestWorkers {
803    environment: Vec<(String, std::ffi::OsString)>,
804}
805
806impl CargoTestWorkers {
807    /// Point the copy's Worker configurations at Supercov's entry and put the
808    /// stand-in in `tools`. None where the workspace has no Rust Worker, or
809    /// where no `wrangler` is installed: a test that looks for one must find
810    /// none, as it would without Supercov.
811    pub fn prepare(workspace: &Path, tools: &Path) -> Result<Option<Self>, String> {
812        let configs = worker_configs(workspace)
813            .iter()
814            .filter_map(|path| parse_config(path))
815            .collect::<Vec<_>>();
816        if configs.is_empty() {
817            return Ok(None);
818        }
819        let path = std::env::var_os("PATH").unwrap_or_default();
820        let Some(real) = on_path(&path, "wrangler") else {
821            return Ok(None);
822        };
823        for config in &configs {
824            point_at_entry(config, None)?;
825        }
826        let supercov = std::env::current_exe().map_err(|error| error.to_string())?;
827        fs::create_dir_all(tools).map_err(|error| format!("{}: {error}", tools.display()))?;
828        write_stand_in(tools, &supercov)?;
829        let mut directories = vec![tools.to_owned()];
830        directories.extend(std::env::split_paths(&path));
831        let joined = std::env::join_paths(directories).map_err(|error| error.to_string())?;
832        Ok(Some(Self {
833            environment: vec![
834                ("PATH".into(), joined),
835                (REAL_WRANGLER.into(), real.into_os_string()),
836            ],
837        }))
838    }
839
840    /// What each test process is started with.
841    pub fn environment(&self) -> impl Iterator<Item = (&str, &std::ffi::OsStr)> {
842        self.environment
843            .iter()
844            .map(|(name, value)| (name.as_str(), value.as_os_str()))
845    }
846}
847
848/// A program on `path`: a file of that name, or on Windows one with a
849/// PATHEXT extension.
850fn on_path(path: &std::ffi::OsStr, name: &str) -> Option<PathBuf> {
851    let names = if cfg!(windows) {
852        vec![
853            format!("{name}.cmd"),
854            format!("{name}.exe"),
855            name.to_owned(),
856        ]
857    } else {
858        vec![name.to_owned()]
859    };
860    std::env::split_paths(path).find_map(|directory| {
861        names
862            .iter()
863            .map(|name| directory.join(name))
864            .find(|candidate| fs::metadata(candidate).is_ok_and(|metadata| metadata.is_file()))
865    })
866}
867
868fn write_stand_in(tools: &Path, supercov: &Path) -> Result<(), String> {
869    #[cfg(unix)]
870    {
871        use std::os::unix::fs::PermissionsExt;
872        let quoted = supercov.to_string_lossy().replace('\'', r"'\''");
873        let script = tools.join("wrangler");
874        fs::write(
875            &script,
876            format!("#!/bin/sh\nexec '{quoted}' {WRANGLER_COMMAND} \"$@\"\n"),
877        )
878        .map_err(|error| format!("{}: {error}", script.display()))?;
879        fs::set_permissions(&script, fs::Permissions::from_mode(0o755))
880            .map_err(|error| format!("{}: {error}", script.display()))?;
881    }
882    #[cfg(windows)]
883    {
884        let script = tools.join("wrangler.cmd");
885        fs::write(
886            &script,
887            format!("@\"{}\" {WRANGLER_COMMAND} %*\r\n", supercov.display()),
888        )
889        .map_err(|error| format!("{}: {error}", script.display()))?;
890    }
891    Ok(())
892}
893
894/// `wrangler dev`, with options before or after the command: wrangler's
895/// global options that take a value (`--cwd worker dev`) are read past.
896fn starts_dev(arguments: &[std::ffi::OsString]) -> bool {
897    const VALUED: &[&str] = &["--cwd", "-c", "--config", "-e", "--env", "--env-file"];
898    let mut arguments = arguments.iter().map(|argument| argument.to_string_lossy());
899    while let Some(argument) = arguments.next() {
900        if VALUED.contains(&argument.as_ref()) {
901            arguments.next();
902        } else if !argument.starts_with('-') {
903            return argument == "dev";
904        }
905    }
906    false
907}
908
909/// The inspector port the arguments name, as `--inspector-port N` or
910/// `--inspector-port=N`.
911fn named_inspector_port(arguments: &[std::ffi::OsString]) -> Option<u16> {
912    let mut arguments = arguments.iter().map(|argument| argument.to_string_lossy());
913    while let Some(argument) = arguments.next() {
914        if argument == "--inspector-port" {
915            return arguments.next()?.parse().ok();
916        }
917        if let Some(port) = argument.strip_prefix("--inspector-port=") {
918            return port.parse().ok();
919        }
920    }
921    None
922}
923
924/// Supercov's `wrangler`: under a test, `wrangler dev` gets an inspector
925/// port and a poller writing into the test's evidence; then this process
926/// becomes the real `wrangler`. The build it runs caps lints at `warn`, as
927/// for a Worker under a browser suite.
928pub fn launch_wrangler(arguments: Vec<std::ffi::OsString>) -> i32 {
929    let Some(real) = std::env::var_os(REAL_WRANGLER) else {
930        eprintln!("[supercov] {WRANGLER_COMMAND} runs only as the wrangler of a measured test");
931        return 127;
932    };
933    let mut arguments = arguments;
934    let evidence = std::env::var_os("SUPERCOV_RUST_EVIDENCE_DIR").map(PathBuf::from);
935    if starts_dev(&arguments)
936        && let Some(evidence) = evidence
937    {
938        let port = match named_inspector_port(&arguments) {
939            Some(port) => Some(port),
940            None => free_port().ok().inspect(|port| {
941                arguments.push("--inspector-port".into());
942                arguments.push(port.to_string().into());
943            }),
944        };
945        if let (Some(port), Ok(supercov)) = (port, std::env::current_exe()) {
946            let windows = evidence.join(format!("worker-{}.windows", std::process::id()));
947            let _ = std::process::Command::new(supercov)
948                .arg(WRANGLER_POLL_COMMAND)
949                .arg(std::process::id().to_string())
950                .arg(port.to_string())
951                .arg(&windows)
952                .stdin(std::process::Stdio::null())
953                .stdout(std::process::Stdio::null())
954                .stderr(std::process::Stdio::null())
955                .spawn();
956        }
957    }
958    let mut rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
959    if !rustflags.is_empty() {
960        rustflags.push(' ');
961    }
962    rustflags.push_str("--cap-lints=warn -A unused_parens");
963    let mut command = std::process::Command::new(&real);
964    command.args(&arguments).env("RUSTFLAGS", rustflags);
965    #[cfg(unix)]
966    {
967        use std::os::unix::process::CommandExt;
968        let error = command.exec();
969        eprintln!(
970            "[supercov] could not start {}: {error}",
971            Path::new(&real).display()
972        );
973        126
974    }
975    #[cfg(not(unix))]
976    {
977        match command.status() {
978            Ok(status) => status.code().unwrap_or(1),
979            Err(error) => {
980                eprintln!(
981                    "[supercov] could not start {}: {error}",
982                    Path::new(&real).display()
983                );
984                126
985            }
986        }
987    }
988}
989
990/// The poller of one `wrangler dev` a test started: takes through `port`
991/// while process `pid` -- that wrangler -- lives, appending to a `.part`
992/// file it renames to `windows` when done, so the run knows it is whole.
993pub fn poll_wrangler(arguments: Vec<std::ffi::OsString>) -> i32 {
994    let [pid, port, windows] = arguments.as_slice() else {
995        return 2;
996    };
997    let (Ok(pid), Ok(port)) = (
998        pid.to_string_lossy().parse::<u32>(),
999        port.to_string_lossy().parse::<u16>(),
1000    ) else {
1001        return 2;
1002    };
1003    let windows = PathBuf::from(windows);
1004    let part = partial(&windows);
1005    let poller = Poller::start(port, part.clone());
1006    while process_alive(pid) {
1007        std::thread::sleep(Duration::from_millis(20));
1008    }
1009    poller.finish();
1010    if fs::symlink_metadata(&part).is_ok() {
1011        let _ = fs::rename(&part, &windows);
1012    }
1013    0
1014}
1015
1016fn partial(windows: &Path) -> PathBuf {
1017    let mut name = windows.as_os_str().to_owned();
1018    name.push(".part");
1019    PathBuf::from(name)
1020}
1021
1022#[cfg(unix)]
1023fn process_alive(pid: u32) -> bool {
1024    let Ok(pid) = libc::pid_t::try_from(pid) else {
1025        return false;
1026    };
1027    // SAFETY: signal 0 only asks whether the process exists.
1028    let answered = unsafe { libc::kill(pid, 0) };
1029    answered == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM)
1030}
1031
1032#[cfg(not(unix))]
1033fn process_alive(_pid: u32) -> bool {
1034    false
1035}
1036
1037/// How long the run waits for a poller to finish after its test: the poller
1038/// notices wrangler is gone within a few polls.
1039const POLLER_SETTLE: Duration = Duration::from_secs(3);
1040
1041/// The Worker takes in a test's evidence directory, written out as region
1042/// files the run reads as it reads a native process's; and the crates they
1043/// came from, which `wrangler dev` built though the run's own build did not.
1044/// A take whose region names another instrumentation is not this run's.
1045pub fn materialize_worker_windows(
1046    directory: &Path,
1047    token: &str,
1048) -> Result<std::collections::BTreeSet<String>, String> {
1049    let listed = |extension: &str| -> Vec<PathBuf> {
1050        let mut files = fs::read_dir(directory)
1051            .map(|entries| {
1052                entries
1053                    .filter_map(Result::ok)
1054                    .map(|entry| entry.path())
1055                    .filter(|path| {
1056                        path.file_name()
1057                            .and_then(|name| name.to_str())
1058                            .is_some_and(|name| {
1059                                name.starts_with("worker-") && name.ends_with(extension)
1060                            })
1061                    })
1062                    .collect::<Vec<_>>()
1063            })
1064            .unwrap_or_default();
1065        files.sort();
1066        files
1067    };
1068    let started = Instant::now();
1069    while !listed(".windows.part").is_empty() && started.elapsed() < POLLER_SETTLE {
1070        std::thread::sleep(Duration::from_millis(20));
1071    }
1072    // A poller still writing (its wrangler outlived the test) has given what
1073    // it has so far: every line it wrote is whole.
1074    let files = listed(".windows")
1075        .into_iter()
1076        .chain(listed(".windows.part"))
1077        .collect::<Vec<_>>();
1078    let mut crates = std::collections::BTreeSet::new();
1079    for (number, file) in files.iter().enumerate() {
1080        let text =
1081            fs::read_to_string(file).map_err(|error| format!("{}: {error}", file.display()))?;
1082        for (line, value) in text
1083            .lines()
1084            .filter_map(|line| serde_json::from_str::<Value>(line).ok())
1085            .enumerate()
1086        {
1087            let Some(window) = window_from(&value) else {
1088                continue;
1089            };
1090            for (index, (crate_key, bytes)) in window.regions.into_iter().enumerate() {
1091                if region_token(&bytes) != Some(token) {
1092                    continue;
1093                }
1094                let region = directory.join(format!(
1095                    "{token}-worker-{number:04}-{line:06}-{index}.region"
1096                ));
1097                fs::write(&region, &bytes)
1098                    .map_err(|error| format!("{}: {error}", region.display()))?;
1099                crates.insert(crate_key);
1100            }
1101        }
1102        fs::remove_file(file).map_err(|error| format!("{}: {error}", file.display()))?;
1103    }
1104    Ok(crates)
1105}
1106
1107/// The instrumentation token a region's header carries.
1108fn region_token(bytes: &[u8]) -> Option<&str> {
1109    let field = bytes.get(56..72)?;
1110    let end = field
1111        .iter()
1112        .position(|byte| *byte == 0)
1113        .unwrap_or(field.len());
1114    std::str::from_utf8(&field[..end]).ok()
1115}
1116
1117/// The root of the Cargo workspace `directory`'s crate belongs to.
1118pub(crate) fn cargo_workspace_root(directory: &Path) -> Option<PathBuf> {
1119    let output = std::process::Command::new("cargo")
1120        .args(["locate-project", "--workspace", "--message-format", "plain"])
1121        .current_dir(directory)
1122        .output()
1123        .ok()?;
1124    if !output.status.success() {
1125        return None;
1126    }
1127    let manifest = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim());
1128    manifest.parent().map(Path::to_path_buf)
1129}
1130
1131#[cfg(test)]
1132mod tests {
1133    use super::*;
1134
1135    #[test]
1136    fn jsonc_comments_and_trailing_commas_are_dropped_and_strings_kept() {
1137        let text = r#"{
1138  // the Worker
1139  "name": "a // not a comment",
1140  /* block */ "main": "build/worker/shim.mjs",
1141  "build": { "command": "worker-build --release", },
1142  "list": [1, 2, ],
1143  "quote": "say \"hi\", ]"
1144}"#;
1145        let value: Value = serde_json::from_str(&strip_jsonc(text)).unwrap();
1146        assert_eq!(value["name"], "a // not a comment");
1147        assert_eq!(value["main"], "build/worker/shim.mjs");
1148        assert_eq!(value["build"]["command"], "worker-build --release");
1149        assert_eq!(value["list"], json!([1, 2]));
1150        assert_eq!(value["quote"], "say \"hi\", ]");
1151    }
1152
1153    #[test]
1154    fn the_stand_in_reads_dev_and_an_inspector_port_wherever_they_stand() {
1155        let os = |arguments: &[&str]| {
1156            arguments
1157                .iter()
1158                .map(std::ffi::OsString::from)
1159                .collect::<Vec<_>>()
1160        };
1161        assert!(starts_dev(&os(&["dev", "--local"])));
1162        assert!(starts_dev(&os(&["--cwd", "worker", "dev"])));
1163        assert!(starts_dev(&os(&[
1164            "-c",
1165            "wrangler.toml",
1166            "--env=staging",
1167            "dev"
1168        ])));
1169        assert!(starts_dev(&os(&["--local", "dev"])));
1170        assert!(!starts_dev(&os(&["deploy"])));
1171        assert!(!starts_dev(&os(&[])));
1172        assert_eq!(
1173            named_inspector_port(&os(&["dev", "--inspector-port", "9230"])),
1174            Some(9230)
1175        );
1176        assert_eq!(
1177            named_inspector_port(&os(&["dev", "--inspector-port=9231"])),
1178            Some(9231)
1179        );
1180        assert_eq!(named_inspector_port(&os(&["dev", "--port", "8787"])), None);
1181        assert_eq!(
1182            named_inspector_port(&os(&["dev", "--inspector-port"])),
1183            None
1184        );
1185    }
1186
1187    /// A take becomes a region file of the test's own evidence, named by the
1188    /// run's token, and says which crate it came from; a take of another
1189    /// instrumentation, or a line cut short, is left out.
1190    #[test]
1191    fn a_take_becomes_a_region_file_of_the_test_that_started_the_worker() {
1192        let directory = temporary("materialize");
1193        let region = |token: &str| {
1194            let mut bytes = vec![0_u8; 4096 + 256];
1195            bytes[56..56 + token.len()].copy_from_slice(token.as_bytes());
1196            bytes[200] = 7;
1197            bytes
1198        };
1199        let segment =
1200            |bytes: &[u8]| json!([0, base64::engine::general_purpose::STANDARD.encode(bytes)]);
1201        let ours = region("abc123");
1202        let theirs = region("fff999");
1203        let line = json!({
1204            "start": 1,
1205            "end": 2,
1206            "regions": [
1207                ["abc123k1", ours.len(), [segment(&ours)]],
1208                ["fff999k2", theirs.len(), [segment(&theirs)]],
1209            ],
1210        });
1211        fs::write(
1212            directory.join("worker-41.windows"),
1213            format!("{line}\n{{\"start\": 3, \"end\""),
1214        )
1215        .unwrap();
1216        let crates = materialize_worker_windows(&directory, "abc123").unwrap();
1217        assert_eq!(
1218            crates,
1219            std::collections::BTreeSet::from(["abc123k1".to_owned()])
1220        );
1221        let mut names = fs::read_dir(&directory)
1222            .unwrap()
1223            .map(|entry| entry.unwrap().file_name().to_string_lossy().into_owned())
1224            .collect::<Vec<_>>();
1225        names.sort();
1226        assert_eq!(names, ["abc123-worker-0000-000000-0.region"]);
1227        assert_eq!(fs::read(directory.join(&names[0])).unwrap(), ours);
1228        // Read once: a second pass finds nothing new.
1229        assert!(
1230            materialize_worker_windows(&directory, "abc123")
1231                .unwrap()
1232                .is_empty()
1233        );
1234        assert_eq!(region_token(&ours), Some("abc123"));
1235        assert_eq!(region_token(&[0_u8; 10]), None);
1236        fs::remove_dir_all(&directory).unwrap();
1237    }
1238
1239    /// A workspace with a Worker and a `wrangler` on PATH gets the stand-in,
1240    /// and its configuration points at Supercov's entry once: a copy kept
1241    /// from an earlier run is not pointed at itself. Without a `wrangler`,
1242    /// nothing is put first on PATH.
1243    #[cfg(unix)]
1244    #[test]
1245    fn the_stand_in_is_put_first_only_where_a_wrangler_is_installed() {
1246        let workspace = temporary("stand-in");
1247        fs::create_dir_all(workspace.join("worker/src")).unwrap();
1248        fs::write(
1249            workspace.join("worker/Cargo.toml"),
1250            "[package]\nname = \"w\"\n",
1251        )
1252        .unwrap();
1253        fs::write(workspace.join("worker/src/lib.rs"), "").unwrap();
1254        fs::write(
1255            workspace.join("worker/wrangler.toml"),
1256            "name = \"w\"\nmain = \"build/worker/shim.mjs\"\n\n[build]\ncommand = \"worker-build --release\"\n",
1257        )
1258        .unwrap();
1259        let bin = workspace.join("bin");
1260        fs::create_dir_all(&bin).unwrap();
1261        let real = bin.join("wrangler");
1262        fs::write(&real, "#!/bin/sh\n").unwrap();
1263        let found = on_path(bin.as_os_str(), "wrangler");
1264        assert_eq!(found.as_deref(), Some(real.as_path()));
1265        assert_eq!(on_path(std::ffi::OsStr::new(""), "wrangler"), None);
1266        let tools = workspace.join("tools");
1267        let config = parse_config(&workspace.join("worker/wrangler.toml")).unwrap();
1268        point_at_entry(&config, None).unwrap();
1269        let pointed = fs::read_to_string(workspace.join("worker/wrangler.toml")).unwrap();
1270        assert!(
1271            pointed.contains(&format!("main = \"{ENTRY_FILE}\"")),
1272            "{pointed}"
1273        );
1274        assert!(!pointed.contains("inspector_port"), "{pointed}");
1275        let entry = fs::read_to_string(workspace.join("worker").join(ENTRY_FILE)).unwrap();
1276        // Again, as on a kept copy: the entry still names the project's.
1277        point_at_entry(
1278            &parse_config(&workspace.join("worker/wrangler.toml")).unwrap(),
1279            None,
1280        )
1281        .unwrap();
1282        assert_eq!(
1283            fs::read_to_string(workspace.join("worker").join(ENTRY_FILE)).unwrap(),
1284            entry
1285        );
1286        assert!(entry.contains("./build/worker/shim.mjs"), "{entry}");
1287        fs::create_dir_all(&tools).unwrap();
1288        write_stand_in(&tools, Path::new("/opt/it's/supercov")).unwrap();
1289        let script = fs::read_to_string(tools.join("wrangler")).unwrap();
1290        assert_eq!(
1291            script,
1292            format!("#!/bin/sh\nexec '/opt/it'\\''s/supercov' {WRANGLER_COMMAND} \"$@\"\n")
1293        );
1294        fs::remove_dir_all(&workspace).unwrap();
1295    }
1296
1297    fn temporary(name: &str) -> PathBuf {
1298        let path = std::env::temp_dir().join(format!(
1299            "supercov-rust-worker-{}-{}-{name}",
1300            std::process::id(),
1301            SystemTime::now()
1302                .duration_since(UNIX_EPOCH)
1303                .unwrap()
1304                .as_nanos()
1305        ));
1306        fs::create_dir_all(&path).unwrap();
1307        path
1308    }
1309
1310    #[test]
1311    fn only_a_configuration_that_builds_with_worker_build_beside_a_crate_is_a_rust_worker() {
1312        let root = temporary("configs");
1313        let write = |relative: &str, text: &str| {
1314            let path = root.join(relative);
1315            fs::create_dir_all(path.parent().unwrap()).unwrap();
1316            fs::write(path, text).unwrap();
1317        };
1318        write(
1319            "worker/wrangler.toml",
1320            "main = \"build/worker/shim.mjs\"\n[build]\ncommand = \"cargo install -q worker-build && worker-build --release\"\n",
1321        );
1322        write("worker/Cargo.toml", "[package]\nname = \"w\"\n");
1323        // JavaScript Workers and a Rust one without its crate are not.
1324        write("js/wrangler.toml", "main = \"src/index.js\"\n");
1325        write(
1326            "orphan/wrangler.jsonc",
1327            "{ \"main\": \"x.mjs\", // c\n \"build\": { \"command\": \"worker-build\" } }",
1328        );
1329        write(
1330            "json/wrangler.jsonc",
1331            "{ \"main\": \"build/worker/shim.mjs\", /* c */ \"build\": { \"command\": \"worker-build --dev\", }, }",
1332        );
1333        write("json/Cargo.toml", "[package]\nname = \"j\"\n");
1334        write(
1335            "node_modules/dep/wrangler.toml",
1336            "main = \"m\"\n[build]\ncommand = \"worker-build\"\n",
1337        );
1338        write("node_modules/dep/Cargo.toml", "");
1339        let found = worker_configs(&root)
1340            .into_iter()
1341            .map(|path| path.strip_prefix(&root).unwrap().to_owned())
1342            .collect::<Vec<_>>();
1343        assert_eq!(
1344            found,
1345            [
1346                PathBuf::from("json/wrangler.jsonc"),
1347                PathBuf::from("worker/wrangler.toml")
1348            ]
1349        );
1350        fs::remove_dir_all(root).unwrap();
1351    }
1352
1353    #[test]
1354    fn the_copy_points_at_the_entry_and_an_inspector_port_and_keeps_the_rest() {
1355        let root = temporary("rewrite");
1356        let toml_path = root.join("wrangler.toml");
1357        fs::write(
1358            &toml_path,
1359            "# the Worker\nname = \"grades\"\nmain = \"build/worker/shim.mjs\"\n\n[build]\ncommand = \"worker-build --release\"\n",
1360        )
1361        .unwrap();
1362        let config = parse_config(&toml_path).unwrap();
1363        rewrite_config(&config, ENTRY_FILE, Some(9444)).unwrap();
1364        let rewritten = fs::read_to_string(&toml_path).unwrap();
1365        assert!(rewritten.contains("# the Worker"), "{rewritten}");
1366        let (_, document) = read_config(&toml_path).unwrap();
1367        assert_eq!(document["main"], ENTRY_FILE);
1368        assert_eq!(document["dev"]["inspector_port"], 9444);
1369        assert_eq!(document["build"]["command"], "worker-build --release");
1370
1371        // A port the author chose is theirs, and the poller uses it.
1372        let json_path = root.join("wrangler.jsonc");
1373        fs::write(
1374            &json_path,
1375            "{\n  // mine\n  \"main\": \"./build/worker/shim.mjs\",\n  \"dev\": { \"inspector_port\": 9555 },\n}\n",
1376        )
1377        .unwrap();
1378        let config = parse_config(&json_path).unwrap();
1379        assert_eq!(config.inspector_port, Some(9555));
1380        assert_eq!(config.main, "./build/worker/shim.mjs");
1381        rewrite_config(&config, ENTRY_FILE, Some(1)).unwrap();
1382        let (_, document) = read_config(&json_path).unwrap();
1383        assert_eq!(document["main"], ENTRY_FILE);
1384        assert_eq!(document["dev"]["inspector_port"], 9555);
1385        fs::remove_dir_all(root).unwrap();
1386    }
1387
1388    #[test]
1389    fn a_window_is_rebuilt_from_its_header_and_touched_pages() {
1390        let encode = |bytes: &[u8]| base64::engine::general_purpose::STANDARD.encode(bytes);
1391        let value = json!({
1392            "start": 10,
1393            "end": 15,
1394            "regions": [["abc", 8192, [[0, encode(&[1, 2, 3])], [4096, encode(&[9; 4])]]]],
1395        });
1396        let window = window_from(&value).unwrap();
1397        assert_eq!((window.start, window.end), (10, 15));
1398        let (crate_key, bytes) = &window.regions[0];
1399        assert_eq!(crate_key, "abc");
1400        assert_eq!(bytes.len(), 8192);
1401        assert_eq!(&bytes[..4], &[1, 2, 3, 0]);
1402        assert_eq!(&bytes[4096..4101], &[9, 9, 9, 9, 0]);
1403        // A segment past the region is a malformed window, not a panic.
1404        let bad = json!({"start": 0, "end": 1, "regions": [["a", 4, [[2, encode(&[1, 2, 3])]]]]});
1405        assert!(window_from(&bad).is_none());
1406    }
1407}