From 835c357f9c669e72059765bdb82afb02a3f7ba8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 17 Aug 2026 13:46:57 +0200 Subject: [PATCH] fix: resolve parity crash cluster --- changelog.d/8272-parity-crashes.md | 9 ++ crates/perry-ext-fastify/src/cluster_bind.rs | 19 +-- .../perry-ext-http/src/server/cluster_bind.rs | 8 +- crates/perry-hir/src/lower/context.rs | 1 - crates/perry-hir/src/lower/expr_call/mod.rs | 8 - .../src/lower/expr_call/wasm_exports.rs | 53 ------- .../perry-hir/src/lower/lowering_context.rs | 6 - crates/perry-hir/src/lower_decl/body_stmt.rs | 13 -- crates/perry-hir/src/lower_decl/helpers.rs | 30 ---- crates/perry-hir/src/lower_decl/mod.rs | 8 +- crates/perry-runtime/src/cluster.rs | 8 + run_parity_tests.sh | 14 +- test-files/demo_claude_code_tui.ts | 1 + test-files/test_fastify_integration.ts | 1 + .../test_issue_1240_fastify_request_json.ts | 1 + ..._issue_1293_fastify_request_json_as_any.ts | 1 + test-files/test_issue_1425_gc_unsafe_zones.ts | 1 + ...test_issue_358_perry_tui_phase2_counter.ts | 1 + .../test_issue_414_mysql_query_params.ts | 1 + ...st_issue_915_jwt_sign_after_async_route.ts | 1 + .../test_perry_tui_inkcompat_counter.ts | 1 + test-files/test_perry_tui_inkcompat_useapp.ts | 1 + .../test_perry_tui_inkcompat_useeffect.ts | 1 + .../test_perry_tui_inkcompat_usefocus.ts | 1 + .../test_perry_tui_inkcompat_usememo.ts | 1 + test-files/test_perry_tui_inkcompat_useref.ts | 1 + test-files/test_wasm_add.ts | 13 +- test-parity/README.md | 8 + test-parity/expected/test_wasm_add.txt | 1 + test-parity/known_failures.json | 144 ------------------ 30 files changed, 74 insertions(+), 283 deletions(-) create mode 100644 changelog.d/8272-parity-crashes.md delete mode 100644 crates/perry-hir/src/lower/expr_call/wasm_exports.rs create mode 100644 test-parity/expected/test_wasm_add.txt diff --git a/changelog.d/8272-parity-crashes.md b/changelog.d/8272-parity-crashes.md new file mode 100644 index 0000000000..0226d33f85 --- /dev/null +++ b/changelog.d/8272-parity-crashes.md @@ -0,0 +1,9 @@ +### Fixed + +- Fixed `cluster.fork()` HTTP workers losing their worker identity after + bootstrap, which prevented shared-port coordination and `listening` events. +- Fixed a WebAssembly export-call crash by using the standard + `WebAssembly.instantiate()` result shape and safe exported-function wrappers. +- The parity sweep now identifies interactive, server-lifecycle, and + external-service fixtures explicitly instead of reporting their expected + long-running behavior as runtime crashes. diff --git a/crates/perry-ext-fastify/src/cluster_bind.rs b/crates/perry-ext-fastify/src/cluster_bind.rs index 3ca8725fba..3294eebf1b 100644 --- a/crates/perry-ext-fastify/src/cluster_bind.rs +++ b/crates/perry-ext-fastify/src/cluster_bind.rs @@ -1,10 +1,9 @@ //! `node:cluster` worker port sharing for the Fastify listen site. //! -//! When this process is a `cluster.fork()`ed worker (Node's convention: a -//! non-empty `NODE_UNIQUE_ID` in the environment, set by the runtime's -//! `cluster.fork`), the TCP bind goes through SO_REUSEPORT so N workers can -//! share one port — the kernel load-balances accepts across them, with no -//! primary-accept hop. The bound address is reported to the primary over the +//! When this process is a `cluster.fork()`ed worker, the TCP bind goes through +//! SO_REUSEPORT so N workers can share one port — the kernel load-balances +//! accepts across them without a primary-accept hop. The bound address is +//! reported to the primary over the //! cluster IPC so `cluster.on('listening')` fires Node-style. //! //! This wires Fastify into the cluster machinery that already exists in @@ -16,13 +15,10 @@ use std::net::{SocketAddr, TcpListener}; -/// True when this process is a `cluster.fork()`ed worker (non-empty -/// `NODE_UNIQUE_ID` in the environment — the same check the runtime and -/// perry-ext-http use). +/// True when this process is a `cluster.fork()`ed worker. The runtime caches +/// this before consuming Node's bootstrap-only `NODE_UNIQUE_ID` variable. pub(crate) fn is_cluster_worker() -> bool { - std::env::var("NODE_UNIQUE_ID") - .map(|s| !s.is_empty()) - .unwrap_or(false) + unsafe { perry_cluster_is_worker() != 0 } } /// Bind `addr` with SO_REUSEPORT (+SO_REUSEADDR) so multiple cluster workers can @@ -64,6 +60,7 @@ extern "C" { // perry-runtime (dev-dep only); the symbol resolves at final link, the same // way perry-ffi's runtime helpers do — matching perry-ext-http's // `cluster_bind`. + fn perry_cluster_is_worker() -> i32; fn perry_cluster_worker_listening( addr_ptr: *const u8, addr_len: u32, diff --git a/crates/perry-ext-http/src/server/cluster_bind.rs b/crates/perry-ext-http/src/server/cluster_bind.rs index c127cbd080..4ba6edde7d 100644 --- a/crates/perry-ext-http/src/server/cluster_bind.rs +++ b/crates/perry-ext-http/src/server/cluster_bind.rs @@ -1,8 +1,7 @@ //! #4914 — `node:cluster` worker port sharing for the HTTP/HTTPS/HTTP2 //! listen sites. //! -//! When this process is a `cluster.fork()`ed worker (Node's convention: -//! non-empty `NODE_UNIQUE_ID` in the environment), every TCP bind goes +//! When this process is a `cluster.fork()`ed worker, every TCP bind goes //! through SO_REUSEPORT so N workers can share one port, and the bound //! address is reported to the primary over the fork IPC channel so //! `cluster.on('listening')` fires Node-style. Kernel SO_REUSEPORT @@ -12,9 +11,7 @@ use std::net::{SocketAddr, TcpListener}; pub(crate) fn is_cluster_worker() -> bool { - std::env::var("NODE_UNIQUE_ID") - .map(|s| !s.is_empty()) - .unwrap_or(false) + unsafe { perry_cluster_is_worker() != 0 } } /// Bind `addr`, with SO_REUSEPORT (+SO_REUSEADDR) when running as a cluster @@ -38,6 +35,7 @@ extern "C" { // Defined in perry-runtime's cluster.rs / cluster_sched.rs. This crate has // no Cargo dep on perry-runtime (dev-dep only); the symbols resolve at // final link, the same way perry-ffi's runtime helpers do. + fn perry_cluster_is_worker() -> i32; fn perry_cluster_worker_listening( addr_ptr: *const u8, addr_len: u32, diff --git a/crates/perry-hir/src/lower/context.rs b/crates/perry-hir/src/lower/context.rs index 91b6b1fd46..2eae420c3c 100644 --- a/crates/perry-hir/src/lower/context.rs +++ b/crates/perry-hir/src/lower/context.rs @@ -188,7 +188,6 @@ impl LoweringContext { proxy_locals: HashSet::new(), proxy_local_ids: HashSet::new(), builtin_proto_method_locals: HashMap::new(), - wasm_instance_locals: HashSet::new(), plain_object_locals: HashSet::new(), proxy_revoke_locals: HashMap::new(), class_expr_aliases: HashMap::new(), diff --git a/crates/perry-hir/src/lower/expr_call/mod.rs b/crates/perry-hir/src/lower/expr_call/mod.rs index b402f442fe..193980f0d3 100644 --- a/crates/perry-hir/src/lower/expr_call/mod.rs +++ b/crates/perry-hir/src/lower/expr_call/mod.rs @@ -77,7 +77,6 @@ mod stream; mod textencoder; mod url_date_instance; mod url_search_params; -mod wasm_exports; use array_only_methods::try_array_only_methods; use globals::try_global_builtins; @@ -110,7 +109,6 @@ use regex_string::try_regex_string_methods; use static_and_instance::try_static_method_and_instance; use textencoder::try_textencoder_decoder; use url_date_instance::try_url_date_weakref_instance; -use wasm_exports::try_wasm_instance_exports; fn unwrap_call_callee_ts_wrappers(e: &ast::Expr) -> &ast::Expr { let mut cur = e; @@ -568,12 +566,6 @@ fn lower_call_inner(ctx: &mut LoweringContext, call: &ast::CallExpr) -> Result a, }; - // `.exports.(...)` for WebAssembly JS API. - args = match try_wasm_instance_exports(ctx, call, expr, args)? { - Ok(e) => return Ok(e), - Err(a) => a, - }; - // fs/path/JSON/Math/Number/String/crypto/os/Buffer/cp/net/AbortSignal/Date/URL static methods. args = match try_module_static_methods(ctx, call, expr, args, has_spread)? { Ok(e) => return Ok(e), diff --git a/crates/perry-hir/src/lower/expr_call/wasm_exports.rs b/crates/perry-hir/src/lower/expr_call/wasm_exports.rs deleted file mode 100644 index 1677ff51a9..0000000000 --- a/crates/perry-hir/src/lower/expr_call/wasm_exports.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! `.exports.(args)` for WebAssembly JS API (#76). -//! -//! Extracted from `expr_call/mod.rs` as a mechanical move. - -use anyhow::Result; -use swc_ecma_ast as ast; - -use crate::ir::*; - -use super::super::{lower_expr, LoweringContext}; - -pub(super) fn try_wasm_instance_exports( - ctx: &mut LoweringContext, - // #854: kept for the uniform `try_*` dispatch-helper signature; this arm - // works off `expr`, not the raw `CallExpr`. - _call: &ast::CallExpr, - expr: &ast::Expr, - args: Vec, -) -> Result>> { - // Issue #76 — standard `.exports.(args...)` shape - // from the WebAssembly JS API. Sits OUTSIDE the Ident-receiver - // gate below because `.exports` is itself a Member, not - // an Ident. Only routes when `` resolves to a tagged - // wasm-instance local (populated at var-decl time when the - // initializer is `WebAssembly.instantiate(...)`) — this avoids - // stealing `module.exports.foo()` etc. from the generic dispatch. - if let ast::Expr::Member(outer_member) = expr { - if let ast::MemberProp::Ident(method_ident) = &outer_member.prop { - if let ast::Expr::Member(inner) = outer_member.obj.as_ref() { - if let ast::MemberProp::Ident(inner_prop) = &inner.prop { - if inner_prop.sym.as_ref() == "exports" { - if let ast::Expr::Ident(inst_ident) = inner.obj.as_ref() { - let inst_name = inst_ident.sym.as_ref(); - if ctx.wasm_instance_locals.contains(inst_name) { - let instance_lowered = lower_expr(ctx, inner.obj.as_ref())?; - ctx.uses_webassembly = true; - return Ok(Ok(Expr::WebAssemblyCallExport { - instance: Box::new(instance_lowered), - name: Box::new(Expr::String( - method_ident.sym.as_ref().to_string(), - )), - args, - })); - } - } - } - } - } - } - } - - Ok(Err(args)) -} diff --git a/crates/perry-hir/src/lower/lowering_context.rs b/crates/perry-hir/src/lower/lowering_context.rs index fbcf2e8722..d370184c9f 100644 --- a/crates/perry-hir/src/lower/lowering_context.rs +++ b/crates/perry-hir/src/lower/lowering_context.rs @@ -702,12 +702,6 @@ pub struct LoweringContext { /// rewrite recognize `m.call(arr, ...)` (the receiver of `.call` is a plain /// identifier, not a member/literal) and synthesize `arr.map(...)`. pub(crate) builtin_proto_method_locals: HashMap, - /// Issue #76 — locals known to hold a WebAssembly instance handle (i.e. - /// `const x = WebAssembly.instantiate(...)`). Used to route - /// `x.exports.(...)` to `Expr::WebAssemblyCallExport` only when - /// the receiver is a tracked instance, avoiding false matches against - /// CJS-style `module.exports.foo()` patterns. - pub(crate) wasm_instance_locals: HashSet, /// #809: locals whose initializer is an object literal or /// `Object.create(...)` — i.e. provably a plain object, never a Date. /// Consulted by `static_receiver_class` so `obj.toJSON()` / diff --git a/crates/perry-hir/src/lower_decl/body_stmt.rs b/crates/perry-hir/src/lower_decl/body_stmt.rs index 66142dd8cc..e5936eda7b 100644 --- a/crates/perry-hir/src/lower_decl/body_stmt.rs +++ b/crates/perry-hir/src/lower_decl/body_stmt.rs @@ -170,19 +170,6 @@ pub fn lower_body_stmt(ctx: &mut LoweringContext, stmt: &ast::Stmt) -> Result(...)` syntactic match in - // `lower/expr_call.rs` only fires for genuine wasm - // instances (not CJS-style `module.exports.foo()`). - if let (ast::Pat::Ident(binding), Some(init_expr)) = - (&decl.name, decl.init.as_deref()) - { - if init_is_webassembly_instantiate(init_expr) { - ctx.wasm_instance_locals - .insert(binding.id.sym.as_ref().to_string()); - } - } // Record chained-assignment class self-aliases (`let Logger = // Logger_1 = class …`) so the self-reference isn't captured (see // `synthesize_class_captures`). Function / CJS-module body path. diff --git a/crates/perry-hir/src/lower_decl/helpers.rs b/crates/perry-hir/src/lower_decl/helpers.rs index 64040a8f61..da116e8b32 100644 --- a/crates/perry-hir/src/lower_decl/helpers.rs +++ b/crates/perry-hir/src/lower_decl/helpers.rs @@ -53,11 +53,6 @@ pub(super) fn async_iterator_method_call(iterable: Expr) -> Expr { /// cross-module callers that pad missing args with `undefined` still observe /// the intended default. Rest params are skipped (they're handled by the /// call-site array bundling, not by scalar default substitution). -/// Recognise `WebAssembly.instantiate(...)` call shapes used as a var-decl -/// initializer. Used to populate `ctx.wasm_instance_locals` so the -/// standard `inst.exports.(...)` syntactic match in -/// `lower/expr_call.rs` doesn't fire on unrelated `obj.exports.method()` -/// calls (notably CJS aggregator output). Issue #76. /// Collect local IDs declared anywhere inside this statement tree (Let /// statements, for-init Lets, catch-clause variables, etc.) — but do NOT /// recurse into nested closures, since those introduce their own scope. @@ -132,31 +127,6 @@ pub fn collect_let_decls_in_stmt(stmt: &Stmt, out: &mut std::collections::HashSe } } -pub fn init_is_webassembly_instantiate(expr: &ast::Expr) -> bool { - let call = match expr { - ast::Expr::Call(c) => c, - ast::Expr::Await(a) => return init_is_webassembly_instantiate(&a.arg), - _ => return false, - }; - let callee = match &call.callee { - ast::Callee::Expr(e) => e.as_ref(), - _ => return false, - }; - let member = match callee { - ast::Expr::Member(m) => m, - _ => return false, - }; - let obj = match member.obj.as_ref() { - ast::Expr::Ident(i) => i, - _ => return false, - }; - let prop = match &member.prop { - ast::MemberProp::Ident(i) => i, - _ => return false, - }; - obj.sym.as_ref() == "WebAssembly" && prop.sym.as_ref() == "instantiate" -} - pub fn build_default_param_stmts(params: &[Param]) -> Vec { let mut out: Vec = Vec::new(); for (idx, param) in params.iter().enumerate() { diff --git a/crates/perry-hir/src/lower_decl/mod.rs b/crates/perry-hir/src/lower_decl/mod.rs index 0107f422ce..50b171ead5 100644 --- a/crates/perry-hir/src/lower_decl/mod.rs +++ b/crates/perry-hir/src/lower_decl/mod.rs @@ -52,10 +52,10 @@ pub(crate) use enum_decl::{compute_enum_members, lower_enum_decl}; pub(crate) use fn_decl::lower_fn_decl; pub(crate) use helpers::{ append_synthetic_arguments_param, body_has_use_strict, body_uses_arguments, - build_default_param_stmts, collect_let_decls_in_stmt, init_is_webassembly_instantiate, - is_inspect_custom_key, is_symbol_iterator_key, lower_well_known_computed_method, - mapped_argument_parameter_ids, params_are_simple_arguments_list, params_use_arguments, - symbol_well_known_key, with_static_member_context, WellKnownComputedMethod, + build_default_param_stmts, collect_let_decls_in_stmt, is_inspect_custom_key, + is_symbol_iterator_key, lower_well_known_computed_method, mapped_argument_parameter_ids, + params_are_simple_arguments_list, params_use_arguments, symbol_well_known_key, + with_static_member_context, WellKnownComputedMethod, }; pub(crate) use interface_decl::lower_interface_decl; pub(crate) use private_members::{ diff --git a/crates/perry-runtime/src/cluster.rs b/crates/perry-runtime/src/cluster.rs index 39d43d34a8..56412debe6 100644 --- a/crates/perry-runtime/src/cluster.rs +++ b/crates/perry-runtime/src/cluster.rs @@ -81,6 +81,14 @@ pub fn is_cluster_worker() -> bool { cluster_worker_id().is_some() } +/// Stable cross-crate worker check for the separately linked HTTP/Fastify +/// adapters. The runtime consumes `NODE_UNIQUE_ID` during cluster bootstrap, +/// so adapters must query this cached state instead of rereading the env var. +#[no_mangle] +pub extern "C" fn perry_cluster_is_worker() -> i32 { + is_cluster_worker() as i32 +} + /// Bind a TCP listener with SO_REUSEPORT (+SO_REUSEADDR) so N cluster /// workers can share one port (#4914). Callers gate on /// [`is_cluster_worker`]; non-worker binds stay on the plain diff --git a/run_parity_tests.sh b/run_parity_tests.sh index 90a03a0912..c2ad54139e 100755 --- a/run_parity_tests.sh +++ b/run_parity_tests.sh @@ -1287,6 +1287,7 @@ for (( selected_i = 0; selected_i < JOURNAL_TOTAL; selected_i++ )); do parity_argv_line=$(sed -n -E 's|^[[:space:]]*//[[:space:]]*parity-argv:[[:space:]]*(.*)$|\1|p' "$test_file" | head -1) parity_node_argv_line=$(sed -n -E 's|^[[:space:]]*//[[:space:]]*parity-node-argv:[[:space:]]*(.*)$|\1|p' "$test_file" | head -1) parity_env_line=$(sed -n -E 's|^[[:space:]]*//[[:space:]]*parity-env:[[:space:]]*(.*)$|\1|p' "$test_file" | head -1) + parity_skip_reason=$(sed -n -E 's|^[[:space:]]*//[[:space:]]*parity-skip:[[:space:]]*(.*)$|\1|p' "$test_file" | head -1) test_argv=() if [[ -n "$parity_argv_line" ]]; then read -r -a test_argv <<< "$parity_argv_line" @@ -1300,9 +1301,20 @@ for (( selected_i = 0; selected_i < JOURNAL_TOTAL; selected_i++ )); do read -r -a parity_env <<< "$parity_env_line" fi + # Lifecycle-driven fixtures (interactive programs, background servers, or + # external-service repros) are compile-smoked elsewhere but are not valid + # one-shot byte-parity programs. Keep the reason beside the fixture so a + # future editor sees why the full sweep must not execute it directly. + if [[ -n "$parity_skip_reason" ]]; then + echo -e "${YELLOW}SKIP${NC} $test_id ($parity_skip_reason)" + ((SKIPPED++)) + record_result "$test_id" "skipped" + continue + fi + # Check if test should be skipped if should_skip "$test_name"; then - echo -e "${YELLOW}SKIP${NC} $test_id (async/timer test)" + echo -e "${YELLOW}SKIP${NC} $test_id (suite skip list)" ((SKIPPED++)) record_result "$test_id" "skipped" continue diff --git a/test-files/demo_claude_code_tui.ts b/test-files/demo_claude_code_tui.ts index 9e63a9ba76..d5794c703e 100644 --- a/test-files/demo_claude_code_tui.ts +++ b/test-files/demo_claude_code_tui.ts @@ -1,3 +1,4 @@ +// parity-skip: interactive TUI demo; requires terminal input and has no Node oracle // Demo: a Claude-Code-style TUI built entirely on perry/tui. import { Box, Text, Spinner, diff --git a/test-files/test_fastify_integration.ts b/test-files/test_fastify_integration.ts index c6c4c1a9e9..8d2efc62a5 100644 --- a/test-files/test_fastify_integration.ts +++ b/test-files/test_fastify_integration.ts @@ -1,3 +1,4 @@ +// parity-skip: background server; driven by scripts/run_fastify_tests.sh // Integration test for Fastify (issue #174). Runs a small server that // scripts/run_fastify_tests.sh launches in the background, curls, and // asserts the response bodies for each route. Port is read from argv diff --git a/test-files/test_issue_1240_fastify_request_json.ts b/test-files/test_issue_1240_fastify_request_json.ts index 427ee9b0fb..822a45c90a 100644 --- a/test-files/test_issue_1240_fastify_request_json.ts +++ b/test-files/test_issue_1240_fastify_request_json.ts @@ -1,3 +1,4 @@ +// parity-skip: background server; driven by test-files/run_test_issue_1240.sh // Issue #1240 — fastify `request.json()` returns `undefined` (silent 400). // // Before the fix, the native dispatch table routed the `json` method to diff --git a/test-files/test_issue_1293_fastify_request_json_as_any.ts b/test-files/test_issue_1293_fastify_request_json_as_any.ts index ea43d05a85..f3142c8a9e 100644 --- a/test-files/test_issue_1293_fastify_request_json_as_any.ts +++ b/test-files/test_issue_1293_fastify_request_json_as_any.ts @@ -1,3 +1,4 @@ +// parity-skip: background server; driven by test-files/run_test_issue_1293.sh // Issue #1293 — fastify `(request as any).json()` / `(request as any).body` // returned NaN / undefined (silent 400) under the well-known-flipped // perry-ext-fastify backend. diff --git a/test-files/test_issue_1425_gc_unsafe_zones.ts b/test-files/test_issue_1425_gc_unsafe_zones.ts index 9585d43222..61af082b56 100644 --- a/test-files/test_issue_1425_gc_unsafe_zones.ts +++ b/test-files/test_issue_1425_gc_unsafe_zones.ts @@ -1,3 +1,4 @@ +// parity-skip: background servers; driven by tests/test_issue_1425_gc_unsafe_zones.sh // Regression for issue #1425: a long-running Fastify + ws server must not // keep the runtime in a process-lifetime GC unsafe zone. Manual gc() calls // should run while both servers are listening. diff --git a/test-files/test_issue_358_perry_tui_phase2_counter.ts b/test-files/test_issue_358_perry_tui_phase2_counter.ts index 5b65911bcf..a27d704c77 100644 --- a/test-files/test_issue_358_perry_tui_phase2_counter.ts +++ b/test-files/test_issue_358_perry_tui_phase2_counter.ts @@ -1,3 +1,4 @@ +// parity-skip: interactive TUI fixture; requires piped keypresses and has no Node oracle // Regression test for #358 Phase 2: state + useInput + run loop. // Implements the issue's acceptance-criterion #1: // diff --git a/test-files/test_issue_414_mysql_query_params.ts b/test-files/test_issue_414_mysql_query_params.ts index a3eb6ca263..28155747b2 100644 --- a/test-files/test_issue_414_mysql_query_params.ts +++ b/test-files/test_issue_414_mysql_query_params.ts @@ -1,3 +1,4 @@ +// parity-skip: requires a live MySQL fixture; unit-covered in perry-stdlib // Regression test for issue #414: // `db.query(sql, [param])` against MySQL failed with `1835 (HY000): // Malformed communication packet` and left the connection unusable. diff --git a/test-files/test_issue_915_jwt_sign_after_async_route.ts b/test-files/test_issue_915_jwt_sign_after_async_route.ts index 05ac581bf9..b7a5a5ac9c 100644 --- a/test-files/test_issue_915_jwt_sign_after_async_route.ts +++ b/test-files/test_issue_915_jwt_sign_after_async_route.ts @@ -1,3 +1,4 @@ +// parity-skip: background Fastify server requiring an HTTP client lifecycle // Issue #915 regression: jwt.sign after a resumed async Fastify route body // must not route through the generic native-module ABI. diff --git a/test-files/test_perry_tui_inkcompat_counter.ts b/test-files/test_perry_tui_inkcompat_counter.ts index a3a76669c5..849bfad245 100644 --- a/test-files/test_perry_tui_inkcompat_counter.ts +++ b/test-files/test_perry_tui_inkcompat_counter.ts @@ -1,3 +1,4 @@ +// parity-skip: interactive TUI fixture; requires piped keypresses and has no Node oracle // #679 Phase 4 — ink source-compat test #1: counter. // // The issue's acceptance program (modulo JSX, which is the deferred diff --git a/test-files/test_perry_tui_inkcompat_useapp.ts b/test-files/test_perry_tui_inkcompat_useapp.ts index a98a46d627..1a32a88618 100644 --- a/test-files/test_perry_tui_inkcompat_useapp.ts +++ b/test-files/test_perry_tui_inkcompat_useapp.ts @@ -1,3 +1,4 @@ +// parity-skip: interactive TUI fixture; requires piped keypresses and has no Node oracle // #679 Phase 4 — ink source-compat test #2: useApp imperative exit. // // Validates that `useApp()` returns a stable handle whose `.exit()` diff --git a/test-files/test_perry_tui_inkcompat_useeffect.ts b/test-files/test_perry_tui_inkcompat_useeffect.ts index a0260ebe41..e8c719719d 100644 --- a/test-files/test_perry_tui_inkcompat_useeffect.ts +++ b/test-files/test_perry_tui_inkcompat_useeffect.ts @@ -1,3 +1,4 @@ +// parity-skip: interactive TUI fixture; requires piped keypresses and has no Node oracle // #679 Phase 4 — ink source-compat test #7: useEffect with deps array. // // useEffect(fn, []) runs fn once on first render; useEffect(fn) (no diff --git a/test-files/test_perry_tui_inkcompat_usefocus.ts b/test-files/test_perry_tui_inkcompat_usefocus.ts index 69932147b4..bda43da6a5 100644 --- a/test-files/test_perry_tui_inkcompat_usefocus.ts +++ b/test-files/test_perry_tui_inkcompat_usefocus.ts @@ -1,3 +1,4 @@ +// parity-skip: interactive TUI fixture; requires piped keypresses and has no Node oracle // #679 Phase 4 — ink source-compat test #5: useFocus + Tab cycle. // // ink's useFocus pattern: multiple form inputs cycle via Tab/Shift-Tab. diff --git a/test-files/test_perry_tui_inkcompat_usememo.ts b/test-files/test_perry_tui_inkcompat_usememo.ts index e9f0ba8c1d..45e8e81817 100644 --- a/test-files/test_perry_tui_inkcompat_usememo.ts +++ b/test-files/test_perry_tui_inkcompat_usememo.ts @@ -1,3 +1,4 @@ +// parity-skip: interactive TUI fixture; requires piped keypresses and has no Node oracle // #679 Phase 4 — ink source-compat test #6: useMemo caching. // // useMemo(fn, deps) caches fn() across renders when deps don't change. diff --git a/test-files/test_perry_tui_inkcompat_useref.ts b/test-files/test_perry_tui_inkcompat_useref.ts index d9dd88742b..65bdafe795 100644 --- a/test-files/test_perry_tui_inkcompat_useref.ts +++ b/test-files/test_perry_tui_inkcompat_useref.ts @@ -1,3 +1,4 @@ +// parity-skip: interactive TUI fixture; requires piped keypresses and has no Node oracle // #679 Phase 4 — ink source-compat test #4: useRef stable handle. // // ink's useRef pattern: store something the component shouldn't diff --git a/test-files/test_wasm_add.ts b/test-files/test_wasm_add.ts index 06a72cec16..0ef9d54dac 100644 --- a/test-files/test_wasm_add.ts +++ b/test-files/test_wasm_add.ts @@ -4,10 +4,9 @@ // 1. `embedWasm("./fixtures/add.wasm")` compile-time intrinsic // (no runtime fs read; bytes baked into the binary). // 2. `WebAssembly.validate(bytes)` host call. -// 3. Standard `instance.exports.(...)` shape via the -// auto-detected wasm-instance local — no `--enable-wasm-runtime` -// flag needed because the codegen sees the WebAssembly usage and -// auto-links libperry_wasm_host.a. +// 3. Standard instantiate-result shape (`result.instance.exports`) — no +// `--enable-wasm-runtime` flag needed because codegen sees the +// WebAssembly usage and auto-links libperry_wasm_host.a. // // Embedded module is the canonical i32 add export, ~41 bytes: // (module (func (export "add") (param i32 i32) (result i32) @@ -18,11 +17,11 @@ const bytes = embedWasm("./fixtures/add.wasm"); if (!WebAssembly.validate(bytes)) { console.log("FAIL: validate returned false"); } else { - const inst = WebAssembly.instantiate(bytes); - if (inst === undefined || inst === null) { + const result = WebAssembly.instantiate(bytes); + if (result === undefined || result === null || result.instance === undefined) { console.log("FAIL: instantiate returned undefined"); } else { - const r = inst.exports.add(2, 3); + const r = result.instance.exports.add(2, 3); if (r === 5) { console.log("OK"); } else { diff --git a/test-parity/README.md b/test-parity/README.md index 1570330e82..f08ccbab10 100644 --- a/test-parity/README.md +++ b/test-parity/README.md @@ -49,6 +49,14 @@ generated snapshot needs a full-suite baseline from a tag run and is still a follow-up; until then its gap-suite entries overlap `gap_snapshot.json` and are kept so the tag-gated job keeps passing. +The full sweep executes a source file only when it is a self-contained, +terminating program whose output can be compared with Node (or with a committed +expected-output file). A fixture that instead needs terminal input, a client to +drive a background server, or an external service must declare a non-empty +`// parity-skip: ` header. The runner records it as `skipped`; compile +smoke and the fixture's lifecycle-specific harness remain responsible for its +coverage. Keep the reason concrete and name that harness when one exists. + It is **bidirectional** as of #7582. `scripts/parity_known_failures.py` fails on a failure that is not allowed here, and equally on an **entry whose test ran on this platform and passed** — naming the entry to delete. It used to compute diff --git a/test-parity/expected/test_wasm_add.txt b/test-parity/expected/test_wasm_add.txt new file mode 100644 index 0000000000..d86bac9de5 --- /dev/null +++ b/test-parity/expected/test_wasm_add.txt @@ -0,0 +1 @@ +OK diff --git a/test-parity/known_failures.json b/test-parity/known_failures.json index 4611ef024e..c13ce905eb 100644 --- a/test-parity/known_failures.json +++ b/test-parity/known_failures.json @@ -10,24 +10,6 @@ }, "scope": "Consumed by the TAG-GATED `parity` job (full test-files/*.ts suite). Because that job runs after the merges it would judge, the OFFLINE half of the ratchet runs on `lint` (a required per-PR context) via `python3 scripts/parity_known_failures.py --audit`: it enforces the provenance fields above, rejects an entry naming a test file that no longer exists, and cross-checks every test_gap_* entry against test-parity/gap_snapshot.json — a generated, bidirectional baseline, so a gap entry absent from it is one the snapshot asserts passes. Migrating this whole file to a generated snapshot needs a full-suite baseline from a tag run — follow-up to #797." }, - "cluster_4962": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, - "demo_claude_code_tui": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, "test_ads_compile_smoke": { "issue": "8271", "added": "2026-08-17", @@ -91,15 +73,6 @@ "linux" ] }, - "test_fastify_integration": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, "test_gap_2159_defineproperty_class_prototype": { "issue": "2159", "added": "2026-07-04", @@ -205,33 +178,6 @@ "linux" ] }, - "test_issue_1240_fastify_request_json": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, - "test_issue_1293_fastify_request_json_as_any": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, - "test_issue_1425_gc_unsafe_zones": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, "test_issue_1495_image_systemname": { "issue": "8271", "added": "2026-08-17", @@ -367,15 +313,6 @@ "linux" ] }, - "test_issue_358_perry_tui_phase2_counter": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, "test_issue_358_perry_tui_phase3_flexbox": { "issue": "348", "added": "2026-08-17", @@ -448,15 +385,6 @@ "linux" ] }, - "test_issue_414_mysql_query_params": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, "test_issue_442_inline_button_bg": { "issue": "8271", "added": "2026-08-17", @@ -736,15 +664,6 @@ "linux" ] }, - "test_issue_915_jwt_sign_after_async_route": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, "test_issue_915_native_module_after_async_resume": { "issue": "8271", "added": "2026-08-17", @@ -1036,60 +955,6 @@ "linux" ] }, - "test_perry_tui_inkcompat_counter": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, - "test_perry_tui_inkcompat_useapp": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, - "test_perry_tui_inkcompat_useeffect": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, - "test_perry_tui_inkcompat_usefocus": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, - "test_perry_tui_inkcompat_usememo": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, - "test_perry_tui_inkcompat_useref": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, "test_perry_tui_inkcompat_usestdout": { "issue": "348", "added": "2026-08-17", @@ -1216,15 +1081,6 @@ "linux" ] }, - "test_wasm_add": { - "issue": "8272", - "added": "2026-08-17", - "category": "bug-open", - "reason": "CRASH (signal/timeout) — hard defect, never a cosmetic gap. Family + bisect notes in #8272.", - "platforms": [ - "linux" - ] - }, "test_ws_static_constants_6117": { "issue": "8271", "added": "2026-08-17",