Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions .github/workflows/rust-codegen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,49 @@ name: Rust codegen
# fn signatures, wrong trait bounds, generic drift. The
# `tests/compile_spec.rs` cancel-mode end-to-end probe runs in
# `Check & Test`; this suite is the breadth check across the core
# example corpus, run nightly so the per-example `cargo check`
# (~30-60 s each) doesn't burden every PR.
# example corpus, run only where Rust codegen can change so the
# per-example `cargo check` (~30-60 s each) doesn't burden every PR.
#
# Sister workflow to `proof.yml`. Both consolidate heavy toolchain
# / build work into one nightly run instead of adding latency to
# every compiler-touching PR.
# Sister workflow to `proof.yml`. It runs nightly against main and on
# pull requests that touch the Rust backend or what feeds it: the Rust
# emitter, shared codegen, MIR and its ownership passes, the native
# providers and capability contracts, the Aver runtime crate, yield
# lowering, and the self-hosted sources. Other PRs skip it, so ordinary
# compiler changes do not pay for the per-example `cargo check`. Neither
# job is a required check; their names are distinct from every `CI` job.

on:
schedule:
# 03:00 UTC daily — runs against the tip of main, same cadence
# as fuzz nightly + proof nightly. Three independent workflows
# each reading main, no cross-artefact deps.
- cron: '0 3 * * *'
pull_request:
branches: [main]
paths:
- '.github/workflows/rust-codegen.yml'
- 'src/codegen/rust/**'
- 'src/codegen/common.rs'
- 'src/codegen/mod.rs'
- 'src/codegen/builtin*.rs'
- 'src/ir/mir/**'
- 'src/ir/alias.rs'
- 'src/ir/escape.rs'
- 'src/ir/last_use.rs'
- 'src/yield_lowering/**'
- 'src/provider/**'
- 'stdlib/capabilities/**'
- 'aver-rt/**'
- 'self_hosted/**'
- 'tests/rust_codegen_regression.rs'
workflow_dispatch:

concurrency:
# A new push to the same PR supersedes the running canary; nightly and
# dispatched runs keep their own group and are never cancelled.
group: rust-codegen-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
CARGO_TERM_COLOR: always

Expand Down
107 changes: 92 additions & 15 deletions tests/wasm_host_work_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,102 @@ fn wasm_workers_transport_unit_tasks_and_unit_results() {
);
}

#[test]
fn javascript_workers_run_the_coordinator_and_progress_independently() {
let node = std::env::var_os("AVER_NODE").unwrap_or_else(|| "node".into());
let version = Command::new(&node)
.arg("--version")
.output()
.expect("Node is required for the public JS Work host test (set AVER_NODE)");
let major: u32 = String::from_utf8_lossy(&version.stdout)
/// Oldest Node whose V8 runs the emitted module: WasmGC and tail calls are
/// on by default from Node 22.
const MIN_NODE_MAJOR: u32 = 22;

fn node_major(node: &std::ffi::OsStr) -> Option<u32> {
let output = Command::new(node).arg("--version").output().ok()?;
if !output.status.success() {
return None;
}
String::from_utf8_lossy(&output.stdout)
.trim()
.trim_start_matches('v')
.split('.')
.next()
.unwrap_or("")
.next()?
.parse()
.unwrap_or(0);
assert!(
major >= 22,
"the JS host test needs Node 22+ with WasmGC and tail calls; set AVER_NODE"
);
.ok()
}

/// The Node that runs the JS host test and its major version.
///
/// `AVER_NODE` wins when set, and a too-old value is reported rather than
/// silently replaced by another install. Otherwise `node` on `PATH` is used when it is new enough,
/// then the newest suitable install under `~/.nvm/versions/node`. The
/// error names every candidate so a skip or failure says what was tried.
fn suitable_node() -> Result<(std::ffi::OsString, u32), String> {
if let Some(node) = std::env::var_os("AVER_NODE") {
return match node_major(&node) {
Some(major) if major >= MIN_NODE_MAJOR => Ok((node, major)),
Some(major) => Err(format!(
"AVER_NODE={} is Node {major}; Node {MIN_NODE_MAJOR}+ is required",
node.to_string_lossy()
)),
None => Err(format!(
"AVER_NODE={} did not run `--version`",
node.to_string_lossy()
)),
};
}
let mut tried = Vec::new();
let path_node = std::ffi::OsString::from("node");
match node_major(&path_node) {
Some(major) if major >= MIN_NODE_MAJOR => return Ok((path_node, major)),
Some(major) => tried.push(format!("`node` on PATH is Node {major}")),
None => tried.push("no `node` on PATH".to_string()),
}
if let Some(home) = std::env::var_os("HOME") {
let nvm = std::path::PathBuf::from(home).join(".nvm/versions/node");
let mut installs: Vec<(u32, std::path::PathBuf)> = std::fs::read_dir(&nvm)
.into_iter()
.flatten()
.filter_map(|entry| entry.ok())
.filter_map(|entry| {
let name = entry.file_name();
let major = name
.to_str()?
.trim_start_matches('v')
.split('.')
.next()?
.parse()
.ok()?;
Some((major, entry.path().join("bin/node")))
})
.filter(|(major, bin)| *major >= MIN_NODE_MAJOR && bin.is_file())
.collect();
installs.sort();
if let Some((_, bin)) = installs.pop() {
let node = bin.into_os_string();
if let Some(major) = node_major(&node) {
return Ok((node, major));
}
}
tried.push(format!("no Node {MIN_NODE_MAJOR}+ under {}", nvm.display()));
}
Err(tried.join("; "))
}

#[test]
fn javascript_workers_run_the_coordinator_and_progress_independently() {
let (node, major) = match suitable_node() {
Ok(found) => found,
// CI installs a suitable Node for this lane, so a missing one there
// is a broken lane, not a reason to skip.
Err(why) if std::env::var_os("CI").is_some() => {
panic!(
"the JS Work host test needs Node {MIN_NODE_MAJOR}+ with WasmGC and tail calls: {why}"
)
}
Err(why) => {
eprintln!(
"SKIP javascript_workers_run_the_coordinator_and_progress_independently: \
needs Node {MIN_NODE_MAJOR}+ with WasmGC and tail calls ({why}); \
set AVER_NODE to a suitable node"
);
return;
}
};
let out = tempfile::tempdir().expect("wasm artifacts");
for name in [
"work_jobs_parallel",
Expand Down
Loading