7117 lines · 265.9 KB · Rust
RawDownload
mod support; use agentos_execution::{    v8_runtime::map_bridge_method, CreateJavascriptContextRequest, GuestRuntimeConfig,    JavascriptExecution, JavascriptExecutionEvent, JavascriptExecutionLimits,    JavascriptExecutionResult, JavascriptSyncRpcRequest, StartJavascriptExecutionRequest,};use base64::Engine;use serde::Deserialize;use serde_json::{json, Value};use std::collections::{BTreeMap, VecDeque};use std::fs;use std::io::{Read, Write};use std::os::unix::fs::symlink;use std::os::unix::fs::PermissionsExt;use std::os::unix::process::CommandExt;use std::os::unix::process::ExitStatusExt;use std::path::Path;use std::process::{Child, ChildStdin, Command, Stdio};use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};use std::thread;use std::time::{Duration, Instant};use tempfile::tempdir; /*US-040 execution-test audit Deleted coverage:- `tests/javascript.rs`: removed because the file only exercised the old  `legacy-js-tests` host-Node guest path (`loader.mjs`, `runner.mjs`,  import-cache mutation, and `Command::new("node")` process behavior). The V8  isolate path no longer uses that guest execution model.- `permission_flags::node_permission_flags_do_not_expose_workspace_root_or_entrypoint_parent_writes`:  removed because its JavaScript assertions depended on host-Node permission  flags emitted for guest JS launches. V8 guest JS now stays in-process, while  the remaining permission-flag tests still cover the real host-Node launches  that remain for Python and WASM.- `benchmark::javascript_benchmark_harness_covers_required_startup_and_import_scenarios`:  removed because it depended on pre-V8 benchmark marker behavior from the old  startup harness instead of validating the current V8 execution path. The  stable artifact and markdown benchmark tests remain.*/ // Timing-sensitive assertions flake under the CPU contention of a parallel test// run (see CLAUDE.md > Testing). Gated off by default; the nightly timing lane// sets AGENTOS_RUN_TIMING_TESTS=1 to enforce them.fn run_timing_sensitive_tests() -> bool {    std::env::var_os("AGENTOS_RUN_TIMING_TESTS").is_some()} fn write_fixture(path: &Path, contents: &str) {    if let Some(parent) = path.parent() {        fs::create_dir_all(parent).expect("create fixture parent dirs");    }    fs::write(path, contents).expect("write fixture");} fn run_host_node_json(cwd: &Path, entrypoint: &Path) -> Value {    let output = Command::new("node")        .arg(entrypoint)        .current_dir(cwd)        .output()        .expect("run host node");     assert!(        output.status.success(),        "host node failed with status {:?}\nstdout:\n{}\nstderr:\n{}",        output.status.code(),        String::from_utf8_lossy(&output.stdout),        String::from_utf8_lossy(&output.stderr)    );     serde_json::from_slice(&output.stdout).expect("parse host JSON")} fn write_fake_node_binary(path: &Path, log_path: &Path) {    let script = format!(        "#!/bin/sh\nset -eu\nprintf 'guest-node-invoked\\n' >> \"{}\"\nexit 99\n",        log_path.display()    );    fs::write(path, script).expect("write fake node binary");    let mut permissions = fs::metadata(path)        .expect("fake node metadata")        .permissions();    permissions.set_mode(0o755);    fs::set_permissions(path, permissions).expect("chmod fake node binary");} #[derive(Debug, Deserialize, Default)]#[serde(rename_all = "camelCase")]struct TestJavascriptChildProcessSpawnOptions {    #[serde(default)]    argv0: Option<String>,    #[serde(default)]    cwd: Option<String>,    #[serde(default)]    env: BTreeMap<String, String>,    #[serde(default)]    internal_bootstrap_env: BTreeMap<String, String>,    #[serde(default)]    shell: bool,    #[serde(default)]    timeout: Option<u64>,    #[serde(default, rename = "killSignal")]    kill_signal: Option<String>,} #[derive(Debug, Deserialize)]struct TestJavascriptChildProcessSpawnRequest {    command: String,    #[serde(default)]    args: Vec<String>,    #[serde(default)]    options: TestJavascriptChildProcessSpawnOptions,} type TestJavascriptChildProcessSpawnSyncRequest = (    TestJavascriptChildProcessSpawnRequest,    Option<usize>,    Option<Vec<u8>>,); #[derive(Debug, Deserialize, Default)]#[serde(rename_all = "camelCase")]struct TestLegacyJavascriptChildProcessSpawnOptions {    #[serde(default)]    argv0: Option<String>,    #[serde(default)]    cwd: Option<String>,    #[serde(default)]    env: BTreeMap<String, String>,    #[serde(default)]    input: Option<Value>,    #[serde(default)]    shell: bool,    #[serde(default, rename = "maxBuffer")]    max_buffer: Option<usize>,    #[serde(default)]    timeout: Option<u64>,    #[serde(default, rename = "killSignal")]    kill_signal: Option<String>,} enum HostChildOutputEvent {    Stdout(Vec<u8>),    Stderr(Vec<u8>),    StreamClosed,} struct HostChildRecord {    child: Child,    stdin: Option<ChildStdin>,    output_events: Receiver<HostChildOutputEvent>,    pending_events: VecDeque<Value>,    exit_status: Option<(Option<i32>, Option<String>)>,    open_streams: usize,} #[derive(Default)]struct HostChildProcessHarness {    next_child_id: usize,    children: BTreeMap<String, HostChildRecord>,} impl HostChildProcessHarness {    fn handle_request(        &mut self,        host_cwd: &Path,        request: JavascriptSyncRpcRequest,    ) -> Result<Value, String> {        match request.method.as_str() {            "child_process.spawn" => self.spawn(host_cwd, &request.args),            "child_process.spawn_sync" => self.spawn_sync(host_cwd, &request.args),            "child_process.poll" => self.poll(&request.args),            "child_process.write_stdin" => self.write_stdin(&request.args),            "child_process.close_stdin" => self.close_stdin(&request.args),            "child_process.kill" => self.kill(&request.args),            "fs.writeFileSync" => self.write_file(host_cwd, &request.args),            other => Err(format!("unsupported sync RPC method: {other}")),        }    }     fn spawn(&mut self, host_cwd: &Path, args: &[Value]) -> Result<Value, String> {        let request = parse_test_child_process_spawn_request(args)?;         let child_id = {            self.next_child_id += 1;            format!("child-{}", self.next_child_id)        };         let mut command = if request.options.shell {            let mut command = Command::new("/bin/sh");            command.arg("-c").arg(&request.command);            command.args(&request.args);            command        } else {            let mut command = Command::new(self.map_guest_path(host_cwd, &request.command));            command.args(                request                    .args                    .iter()                    .map(|arg| self.map_guest_path(host_cwd, arg)),            );            command        };        if let Some(argv0) = request.options.argv0.as_deref() {            command.arg0(argv0);        }         let child_cwd = request            .options            .cwd            .as_deref()            .map(|cwd| std::path::PathBuf::from(self.map_guest_path(host_cwd, cwd)))            .unwrap_or_else(|| host_cwd.to_path_buf());         command            .current_dir(child_cwd)            .stdin(Stdio::piped())            .stdout(Stdio::piped())            .stderr(Stdio::piped())            .env_clear()            .envs(&request.options.env)            .envs(&request.options.internal_bootstrap_env);         let mut child = command            .spawn()            .map_err(|error| format!("spawn {} failed: {error}", request.command))?;         let stdin = child.stdin.take();        let stdout = child            .stdout            .take()            .ok_or_else(|| String::from("spawned child stdout pipe missing"))?;        let stderr = child            .stderr            .take()            .ok_or_else(|| String::from("spawned child stderr pipe missing"))?;        let (output_sender, output_events) = mpsc::channel();        spawn_output_reader(stdout, output_sender.clone(), true);        spawn_output_reader(stderr, output_sender, false);         let pid = child.id();        self.children.insert(            child_id.clone(),            HostChildRecord {                child,                stdin,                output_events,                pending_events: VecDeque::new(),                exit_status: None,                open_streams: 2,            },        );         Ok(json!({            "childId": child_id,            "pid": pid,            "command": request.command,            "args": request.args,        }))    }     fn poll(&mut self, args: &[Value]) -> Result<Value, String> {        let child_id = args            .first()            .and_then(Value::as_str)            .ok_or_else(|| String::from("child_process.poll missing child id"))?;        let wait_ms = args.get(1).and_then(Value::as_u64).unwrap_or_default();        let child = self            .children            .get_mut(child_id)            .ok_or_else(|| format!("unknown child process {child_id}"))?;         let deadline = std::time::Instant::now() + Duration::from_millis(wait_ms);        loop {            drain_child_output(child);            if let Some(event) = child.pending_events.pop_front() {                return Ok(event);            }             if child.exit_status.is_none() {                if let Some(status) = child                    .child                    .try_wait()                    .map_err(|error| format!("try_wait {child_id} failed: {error}"))?                {                    child.exit_status = Some((                        status.code(),                        status.signal().map(|signal| match signal {                            9 => String::from("SIGKILL"),                            15 => String::from("SIGTERM"),                            other => format!("SIG{other}"),                        }),                    ));                }            }             if let Some((exit_code, signal)) = child.exit_status.as_ref() {                if child.pending_events.is_empty() && child.open_streams == 0 {                    let exit_code = *exit_code;                    let signal = signal.clone();                    self.children.remove(child_id);                    return Ok(json!({                        "type": "exit",                        "exitCode": exit_code,                        "signal": signal,                    }));                }            }             if std::time::Instant::now() >= deadline {                return Ok(Value::Null);            }             thread::sleep(Duration::from_millis(5));        }    }     /// Translate the host-child harness's durable output state into the    /// evented stream protocol used by the production sidecar. The JavaScript    /// child-process shim no longer issues recurring `child_process.poll`    /// bridge calls, so this standalone conformance harness must emulate the    /// sidecar's readiness pump instead of waiting for a guest poll request.    fn drain_stream_events(&mut self) -> Result<Vec<(&'static str, Value)>, String> {        const MAX_EVENTS_PER_TURN: usize = 256;         let child_ids: Vec<String> = self.children.keys().cloned().collect();        let mut stream_events = Vec::new();        for child_id in child_ids {            while stream_events.len() < MAX_EVENTS_PER_TURN {                let event = self.poll(&[Value::String(child_id.clone()), json!(0)])?;                let Some(event_type) = event.get("type").and_then(Value::as_str) else {                    break;                };                match event_type {                    "stdout" | "stderr" => stream_events.push((                        if event_type == "stdout" {                            "child_stdout"                        } else {                            "child_stderr"                        },                        json!({                            "sessionId": child_id,                            "data": event.get("data").cloned().unwrap_or(Value::Null),                        }),                    )),                    "exit" => {                        stream_events.push((                            "child_exit",                            json!({                                "sessionId": child_id,                                "code": event                                    .get("exitCode")                                    .and_then(Value::as_i64)                                    .unwrap_or(1),                                "signal": Value::Null,                            }),                        ));                        break;                    }                    other => return Err(format!("unknown child event type {other}")),                }            }            if stream_events.len() >= MAX_EVENTS_PER_TURN {                break;            }        }        Ok(stream_events)    }     fn spawn_sync(&mut self, host_cwd: &Path, args: &[Value]) -> Result<Value, String> {        let (request, max_buffer, input) = parse_test_child_process_spawn_sync_request(args)?;        let mut command = if request.options.shell {            let mut command = Command::new("/bin/sh");            command.arg("-c").arg(&request.command);            command.args(&request.args);            command        } else {            let mut command = Command::new(self.map_guest_path(host_cwd, &request.command));            command.args(                request                    .args                    .iter()                    .map(|arg| self.map_guest_path(host_cwd, arg)),            );            command        };        if let Some(argv0) = request.options.argv0.as_deref() {            command.arg0(argv0);        }         let child_cwd = request            .options            .cwd            .as_deref()            .map(|cwd| std::path::PathBuf::from(self.map_guest_path(host_cwd, cwd)))            .unwrap_or_else(|| host_cwd.to_path_buf());         command            .current_dir(child_cwd)            .stdin(Stdio::piped())            .stdout(Stdio::piped())            .stderr(Stdio::piped())            .env_clear()            .envs(&request.options.env)            .envs(&request.options.internal_bootstrap_env);         let mut child = command            .spawn()            .map_err(|error| format!("spawnSync {} failed: {error}", request.command))?;        if let Some(input) = input.as_deref() {            let stdin = child                .stdin                .as_mut()                .ok_or_else(|| String::from("spawnSync child stdin pipe missing"))?;            stdin                .write_all(input)                .map_err(|error| format!("write spawnSync stdin failed: {error}"))?;        }        child.stdin.take();         let mut timed_out = false;        if let Some(timeout_ms) = request.options.timeout {            let deadline = Instant::now() + Duration::from_millis(timeout_ms);            loop {                match child                    .try_wait()                    .map_err(|error| format!("try_wait for {} failed: {error}", request.command))?                {                    Some(_) => break,                    None if !timed_out && Instant::now() >= deadline => {                        let signal = request                            .options                            .kill_signal                            .clone()                            .unwrap_or_else(|| String::from("SIGTERM"));                        let status = Command::new("kill")                            .arg(format!(                                "-{}",                                signal.strip_prefix("SIG").unwrap_or(&signal)                            ))                            .arg(child.id().to_string())                            .status()                            .map_err(|error| {                                format!("send {signal} to {} failed: {error}", request.command)                            })?;                        if !status.success() {                            return Err(format!(                                "send {signal} to {} failed with {status}",                                request.command                            ));                        }                        timed_out = true;                    }                    None => thread::sleep(Duration::from_millis(5)),                }            }        }         let output = child            .wait_with_output()            .map_err(|error| format!("wait_with_output for {} failed: {error}", request.command))?;         let stdout = String::from_utf8_lossy(&output.stdout).into_owned();        let stderr = String::from_utf8_lossy(&output.stderr).into_owned();        let max_buffer = max_buffer.unwrap_or(1024 * 1024);        let max_buffer_exceeded =            output.stdout.len() > max_buffer || output.stderr.len() > max_buffer;        let signal = output.status.signal().map(|signal| match signal {            9 => String::from("SIGKILL"),            15 => String::from("SIGTERM"),            other => format!("SIG{other}"),        });         Ok(json!({            "stdout": stdout,            "stderr": stderr,            "code": output.status.code(),            "signal": signal,            "timedOut": timed_out,            "maxBufferExceeded": max_buffer_exceeded,        }))    }     fn write_stdin(&mut self, args: &[Value]) -> Result<Value, String> {        let child_id = args            .first()            .and_then(Value::as_str)            .ok_or_else(|| String::from("child_process.write_stdin missing child id"))?;        let chunk = decode_guest_bytes(            args.get(1)                .ok_or_else(|| String::from("child_process.write_stdin missing chunk"))?,        )?;        let child = self            .children            .get_mut(child_id)            .ok_or_else(|| format!("unknown child process {child_id}"))?;        if let Some(stdin) = child.stdin.as_mut() {            stdin

Lines 1–500 of 7117.