From 7633c414e8b272c796bdd175e402bad1c31eff2a Mon Sep 17 00:00:00 2001 From: Andrew DiZenzo Date: Fri, 29 May 2026 23:28:22 +0000 Subject: [PATCH 1/5] Add crypto randomFill to API manifest --- crates/perry-api-manifest/src/entries.rs | 4 +++- crates/perry-api-manifest/src/lib.rs | 13 +++++++++++++ docs/api/perry.d.ts | 4 +++- docs/src/api/reference.md | 3 ++- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/perry-api-manifest/src/entries.rs b/crates/perry-api-manifest/src/entries.rs index e09e0e1b8..044e24744 100644 --- a/crates/perry-api-manifest/src/entries.rs +++ b/crates/perry-api-manifest/src/entries.rs @@ -2007,10 +2007,12 @@ pub static API_MANIFEST: &[ApiEntry] = &[ method("crypto", "sha256", false, None), method("crypto", "md5", false, None), method("crypto", "getRandomValues", false, None), - // crypto.randomFillSync(buffer, offset?, size?) — fills the + // crypto.randomFill(buffer[, offset][, size], callback) / + // randomFillSync(buffer, offset?, size?) — fills the // typed-array / Buffer with cryptographically strong random // bytes in-place and returns the same object. Required by // axios (Uint32Array) for ID generation. + method("crypto", "randomFill", false, None), method("crypto", "randomFillSync", false, None), method("crypto", "createHash", false, None), method("crypto", "createSign", false, None), diff --git a/crates/perry-api-manifest/src/lib.rs b/crates/perry-api-manifest/src/lib.rs index b9913db66..ff3d77a4c 100644 --- a/crates/perry-api-manifest/src/lib.rs +++ b/crates/perry-api-manifest/src/lib.rs @@ -283,6 +283,19 @@ mod tests { assert!(matches!(entry.returns, TypeSpec::Bool)); } + #[test] + fn crypto_random_fill_is_manifest_method() { + let entry = module_has_symbol("node:crypto", "randomFill") + .expect("crypto.randomFill should be in the manifest"); + assert!(matches!( + entry.kind, + ApiKind::Method { + has_receiver: false, + class_filter: None + } + )); + } + #[test] fn known_modules_consistent_with_manifest() { // Every entry's module must appear in NATIVE_MODULES. diff --git a/docs/api/perry.d.ts b/docs/api/perry.d.ts index 7c70c5667..04f98149b 100644 --- a/docs/api/perry.d.ts +++ b/docs/api/perry.d.ts @@ -1,6 +1,6 @@ // Auto-generated from Perry's API manifest (#465). Do not edit by hand. // Source: perry-api-manifest::API_MANIFEST -// Coverage: 1546 entries across 85 modules +// Coverage: 1547 entries across 85 modules type PerryU32 = number & { readonly __perryU32?: never }; type PerryU64 = number & { readonly __perryU64?: never }; @@ -460,6 +460,8 @@ declare module "crypto" { /** stdlib */ export function randomBytes(...args: any[]): any; /** stdlib */ + export function randomFill(...args: any[]): any; + /** stdlib */ export function randomFillSync(...args: any[]): any; /** stdlib */ export function randomInt(...args: any[]): any; diff --git a/docs/src/api/reference.md b/docs/src/api/reference.md index 185612f19..cb7977bdb 100644 --- a/docs/src/api/reference.md +++ b/docs/src/api/reference.md @@ -2,7 +2,7 @@ This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target. -Total: 1546 entries across 85 modules. +Total: 1547 entries across 85 modules. ## Modules @@ -440,6 +440,7 @@ Total: 1546 entries across 85 modules. - `publicDecrypt` — module - `publicEncrypt` — module - `randomBytes` — module +- `randomFill` — module - `randomFillSync` — module - `randomInt` — module - `randomInt` — module From 1ea84a35f9e9b2ca64e63069fcaeb6102f98f800 Mon Sep 17 00:00:00 2001 From: Andrew DiZenzo Date: Sat, 30 May 2026 02:19:42 +0000 Subject: [PATCH 2/5] ci: prune cargo test binaries between packages --- .github/workflows/test.yml | 44 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7dafb59e8..c4dfd0fdf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -189,18 +189,40 @@ jobs: RUST_TEST_THREADS=1 cargo test -p perry-runtime # The remaining workspace includes large `perry` / `perry-stdlib` # test binaries. Keep Cargo build jobs serialized so the runner - # does not link several of those large test binaries at once. + # does not link several of those large test binaries at once, then + # run packages one at a time and prune linked test executables so + # target/debug/deps does not exhaust the runner disk mid-job. export CARGO_BUILD_JOBS=1 - cargo test --workspace \ - --exclude perry-runtime \ - --exclude perry-ui-macos \ - --exclude perry-ui-ios \ - --exclude perry-ui-visionos \ - --exclude perry-ui-tvos \ - --exclude perry-ui-watchos \ - --exclude perry-ui-gtk4 \ - --exclude perry-ui-android \ - --exclude perry-ui-windows + workspace_packages="$( + cargo metadata --no-deps --format-version 1 | python3 -c ' + import json + import sys + + excluded = { + "perry-runtime", + "perry-ui-macos", + "perry-ui-ios", + "perry-ui-visionos", + "perry-ui-tvos", + "perry-ui-watchos", + "perry-ui-gtk4", + "perry-ui-android", + "perry-ui-windows", + } + metadata = json.load(sys.stdin) + workspace_members = set(metadata["workspace_members"]) + for package in metadata["packages"]: + if package["id"] in workspace_members and package["name"] not in excluded: + print(package["name"]) + ' + )" + + for package in $workspace_packages; do + echo "::group::cargo test -p $package" + cargo test -p "$package" + echo "::endgroup::" + find target/debug/deps -maxdepth 1 -type f -perm -111 ! -name '*.so' -delete + done # --------------------------------------------------------------------------- # Compiler-output regression gate From 970fcf876be01740aa89526b082f69f703521c98 Mon Sep 17 00:00:00 2001 From: Andrew DiZenzo Date: Sat, 30 May 2026 02:45:46 +0000 Subject: [PATCH 3/5] fix(runtime): suppress ws stubs for extension tests --- crates/perry-ext-fastify/Cargo.toml | 2 +- crates/perry-ext-http-server/Cargo.toml | 2 +- crates/perry-ext-http/Cargo.toml | 2 +- crates/perry-ext-ws/Cargo.toml | 2 +- crates/perry-runtime/Cargo.toml | 4 ++++ crates/perry-runtime/src/stdlib_stubs.rs | 14 ++++++++++---- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/crates/perry-ext-fastify/Cargo.toml b/crates/perry-ext-fastify/Cargo.toml index 850066275..dda33e3aa 100644 --- a/crates/perry-ext-fastify/Cargo.toml +++ b/crates/perry-ext-fastify/Cargo.toml @@ -28,4 +28,4 @@ lazy_static = "1.5" [dev-dependencies] perry-ffi = { workspace = true, features = ["runtime-link"] } -perry-runtime.workspace = true +perry-runtime = { workspace = true, features = ["external-ws-symbols"] } diff --git a/crates/perry-ext-http-server/Cargo.toml b/crates/perry-ext-http-server/Cargo.toml index 837955909..a3a20b4dd 100644 --- a/crates/perry-ext-http-server/Cargo.toml +++ b/crates/perry-ext-http-server/Cargo.toml @@ -30,4 +30,4 @@ tokio-tungstenite = { workspace = true } [dev-dependencies] perry-ffi = { workspace = true, features = ["runtime-link"] } -perry-runtime.workspace = true +perry-runtime = { workspace = true, features = ["external-ws-symbols"] } diff --git a/crates/perry-ext-http/Cargo.toml b/crates/perry-ext-http/Cargo.toml index 9ce5a20a1..d89c512db 100644 --- a/crates/perry-ext-http/Cargo.toml +++ b/crates/perry-ext-http/Cargo.toml @@ -23,7 +23,7 @@ perry-ext-http-server = { path = "../perry-ext-http-server" } # directly. Cargo feature unification keeps the stdlib feature on when # both stdlib and this crate link the same perry-runtime — no duplicate # symbols, no behaviour change for the default `full` build. -perry-runtime.workspace = true +perry-runtime = { workspace = true, features = ["external-ws-symbols"] } reqwest = { version = "0.12", features = ["json", "rustls-tls", "http2"], default-features = false } tokio = { workspace = true } serde_json = "1" diff --git a/crates/perry-ext-ws/Cargo.toml b/crates/perry-ext-ws/Cargo.toml index 9f52b5610..0bdbe0a0a 100644 --- a/crates/perry-ext-ws/Cargo.toml +++ b/crates/perry-ext-ws/Cargo.toml @@ -21,4 +21,4 @@ lazy_static = "1.5" [dev-dependencies] perry-ffi = { workspace = true, features = ["runtime-link"] } -perry-runtime.workspace = true +perry-runtime = { workspace = true, features = ["external-ws-symbols"] } diff --git a/crates/perry-runtime/Cargo.toml b/crates/perry-runtime/Cargo.toml index ccc7b1cdf..2771da9e3 100644 --- a/crates/perry-runtime/Cargo.toml +++ b/crates/perry-runtime/Cargo.toml @@ -19,6 +19,10 @@ default = ["full"] full = ["dep:hostname", "dep:dirs"] # When perry-stdlib is the consumer, exclude no-op stubs to avoid duplicate symbols stdlib = [] +# Extension-crate test binaries that link the real perry-ext-ws symbols still +# need runtime-link for perry-ffi helpers, but must not also export runtime-only +# js_ws_* stubs. +external-ws-symbols = [] # Enable geisterhand in-process fuzzer (callback registry + dispatch queue) geisterhand = [] # iOS/tvOS game loop: provides main() that spawns user code on game thread + UIApplicationMain diff --git a/crates/perry-runtime/src/stdlib_stubs.rs b/crates/perry-runtime/src/stdlib_stubs.rs index e862b08ab..13bece00d 100644 --- a/crates/perry-runtime/src/stdlib_stubs.rs +++ b/crates/perry-runtime/src/stdlib_stubs.rs @@ -13,11 +13,13 @@ //! call in runtime-only mode prints `[perry] warning: ...` once per //! symbol per process — see issue #464 and `src/stub_diag.rs`. -use crate::promise::Promise; -use crate::string::StringHeader; use crate::stub_diag::perry_stub_warn; -use std::ptr; +#[cfg(not(any( + target_os = "ios", + target_os = "android", + feature = "external-ws-symbols" +)))] const WS_REASON: &str = "WebSocket symbol from perry-stdlib not linked into this binary (runtime-only build)"; const READLINE_REASON: &str = @@ -31,7 +33,11 @@ const STDLIB_DISPATCH_REASON: &str = // WebSocket implementation using tungstenite+rustls. These stubs must NOT // be compiled for either platform, otherwise the real implementations will // be shadowed by the no-op stubs. -#[cfg(not(any(target_os = "ios", target_os = "android")))] +#[cfg(not(any( + target_os = "ios", + target_os = "android", + feature = "external-ws-symbols" +)))] mod ws_stubs { use super::{perry_stub_warn, WS_REASON}; use crate::promise::Promise; From 0edec82615dd5cdd190f922d146ac26c39164a10 Mon Sep 17 00:00:00 2001 From: Andrew DiZenzo Date: Sat, 30 May 2026 03:15:03 +0000 Subject: [PATCH 4/5] ci: skip doc fixture crate in cargo test loop --- .github/workflows/coverage.yml | 3 +++ .github/workflows/test.yml | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index d45b64de8..f1f8bd61b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -54,6 +54,7 @@ jobs: # this is a visibility job, not a gate. run: | cargo llvm-cov --workspace --no-fail-fast \ + --exclude perry-doc-fixture-my-bindings \ --exclude perry-ui-macos \ --exclude perry-ui-ios \ --exclude perry-ui-visionos \ @@ -64,6 +65,7 @@ jobs: --exclude perry-ui-windows \ --html --output-dir target/llvm-cov-html cargo llvm-cov report --no-fail-fast \ + --exclude perry-doc-fixture-my-bindings \ --exclude perry-ui-macos \ --exclude perry-ui-ios \ --exclude perry-ui-visionos \ @@ -74,6 +76,7 @@ jobs: --exclude perry-ui-windows \ --lcov --output-path target/lcov.info cargo llvm-cov report --no-fail-fast \ + --exclude perry-doc-fixture-my-bindings \ --exclude perry-ui-macos \ --exclude perry-ui-ios \ --exclude perry-ui-visionos \ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c4dfd0fdf..3997f1635 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -208,6 +208,7 @@ jobs: "perry-ui-gtk4", "perry-ui-android", "perry-ui-windows", + "perry-doc-fixture-my-bindings", } metadata = json.load(sys.stdin) workspace_members = set(metadata["workspace_members"]) From d7cbebb241308811ef4918aa17dafd8410ab84ad Mon Sep 17 00:00:00 2001 From: Andrew DiZenzo Date: Sat, 30 May 2026 03:24:30 +0000 Subject: [PATCH 5/5] test(codegen): serialize ext registry provider tests --- crates/perry-codegen/src/ext_registry.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/perry-codegen/src/ext_registry.rs b/crates/perry-codegen/src/ext_registry.rs index 845a1bcde..a66339fd2 100644 --- a/crates/perry-codegen/src/ext_registry.rs +++ b/crates/perry-codegen/src/ext_registry.rs @@ -317,13 +317,17 @@ pub fn take_used_providers() -> HashSet { mod tests { use super::*; + static PROVIDER_TEST_LOCK: Mutex<()> = Mutex::new(()); + // `USED_PROVIDERS` is a process-wide static; other tests in the same - // process may concurrently insert into it via `LlBlock::call`. We - // therefore check membership rather than exact set equality. The - // non-registered-FFI check uses a deliberately unique symbol name - // that no other test will ever insert. + // process may concurrently insert into it via `LlBlock::call`, and these + // module tests drain it. Serialize the explicit drain/record assertions so + // one test cannot steal another test's providers. #[test] fn registry_dispatch_routes_to_correct_owner() { + let _guard = PROVIDER_TEST_LOCK + .lock() + .expect("provider test lock poisoned"); // Drain anything left over from prior tests. let _ = take_used_providers(); @@ -380,6 +384,9 @@ mod tests { /// `Undefined symbols: _js_node_http_create_server_with_options`. #[test] fn emitted_create_server_symbol_routes_to_http() { + let _guard = PROVIDER_TEST_LOCK + .lock() + .expect("provider test lock poisoned"); let _ = take_used_providers(); record_ffi_call("js_node_http_create_server_with_options"); let got = take_used_providers();