mod common; use std::fs; use std::process::{Command, Stdio}; use std::time::{SystemTime, UNIX_EPOCH}; #[test] fn embedded_startup_uses_live_state_in_main_and_nested_workers() { let directory = tempfile::tempdir().unwrap(); let root = directory.path(); fs::write( root.join("state.js"), r#" import {env, process} from 'avril:std'; export async function state() { let value, denied; try { value = env.get('AVRIL_STARTUP_VALUE'); } catch (error) { value = error.name; } try { env.get('AVRIL_STARTUP_UNGRANTED'); } catch (error) { denied = error.name; } await new Promise(resolve => setTimeout(resolve, 1)); return { pid: process.pid, args: process.args, value, denied, origin: performance.timeOrigin, storage: typeof Storage !== 'undefined', name: globalThis.name ?? '', url: globalThis.location?.href ?? '', random: Array.from({length: 4}, () => Math.random()), uuid: crypto.randomUUID(), }; } export function child(name) { const worker = new Worker('./worker.js', {type: 'module', name}); return new Promise((resolve, reject) => { worker.onmessage = event => resolve(event.data); worker.onerror = event => reject(Error(event.message)); }); } "#, ) .unwrap(); fs::write( root.join("worker.js"), r#" import {env} from 'avril:std'; import {state, child} from './state.js'; const result = await state(); if (name === 'branch') { env.set('AVRIL_STARTUP_VALUE', 'nested value'); result.nested = await child('leaf'); } postMessage(result); close(); "#, ) .unwrap(); fs::write( root.join("main.js"), r#" import {env, pledge} from 'avril:std'; import {state, child} from './state.js'; const result = await state(); result.worker = await child('branch'); if (env.get('AVRIL_STARTUP_VALUE') !== result.value) throw Error('Shared environment'); pledge({env: false}); result.narrowed = await child('denied'); console.log(JSON.stringify(result)); "#, ) .unwrap(); let worker_url = url::Url::from_file_path(root.join("worker.js")) .unwrap() .to_string(); let mut random_sequences = std::collections::HashSet::new(); let mut uuids = std::collections::HashSet::new(); for label in ["first process", "second process"] { let started = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_millis(); let child = Command::new(env!("CARGO_BIN_EXE_avril")) .current_dir(root) .env("AVRIL_STARTUP_VALUE", label) .args([ "run", "-0", "--allow-js-exec=.", "--allow-env=AVRIL_STARTUP_VALUE", "main.js", label, ]) .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .unwrap(); let pid = child.id(); let output = common::wait(child); assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); let result: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); for (state, name, value) in [ (&result, "", label), (&result["worker"], "branch", label), (&result["worker"]["nested"], "leaf", "nested value"), (&result["narrowed"], "denied", "PermissionDenied"), ] { assert!(random_sequences.insert(state["random"].to_string())); assert!(uuids.insert(state["uuid"].as_str().unwrap().to_owned())); assert_eq!(state["pid"], pid); assert_eq!(state["args"], serde_json::json!([label])); assert_eq!(state["value"], value); assert_eq!(state["denied"], "PermissionDenied"); assert_eq!(state["name"], name); assert_eq!(state["storage"], name.is_empty()); assert_eq!(state["url"], if name.is_empty() { "" } else { &worker_url }); assert!(state["origin"].as_f64().unwrap() >= started as f64 - 1000.0); } } }