From df9f8f16fb0f3693b5dc819f088474630d0a595f Mon Sep 17 00:00:00 2001 From: slhx agent Date: Fri, 17 Jul 2026 14:52:09 +0200 Subject: [PATCH] test(derive): close final mutation shard Fail closed on malformed symbol lines, prove sorted deduplicated component discovery and component filtering, exclude ordinary functions from handler registration, and emit diagnostics through quoted tokens without string reparsing. req: diagnostics/003 req: component/003 req: derive_handler/003 req: test/020 req: test/021 req: test/022 req: test/023 --- PLAN.md | 2 +- hemx-derive/src/lib.rs | 24 +++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/PLAN.md b/PLAN.md index 6caa7d3..9593558 100644 --- a/PLAN.md +++ b/PLAN.md @@ -21,7 +21,7 @@ - [ ] **State:** In progress — the package-native capped xtask entry point is reachable, rejects unknown packages and invalid shards, propagates mutest failure, and mutation-tests `hemx-axum`, `hemx-build`, `hemx-core`, `hemx-js`, and the full `hemx-test` package cleanly; full package closure remains. - **User value:** maintainers can run one bounded repository command and trust that meaningful Rust logic across every mutation-applicable library is either killed or explicitly justified. - **Build:** add a capped `hemx-xtask` mutation command that invokes `/opt/repositories/mutest`/`mutest` through package-native test targets rather than the broken workspace-wide example path; enumerate only current mutation-applicable library/proc-macro packages; finish adversarial tests or simplify code until every survivor is classified; keep equivalent, invariant-only, and infrastructure-inapplicable classifications inspectable and minimal; document the exact local release command in the existing readiness surface. -- **Blocked by:** none; broad survivors currently remain in `hemx-derive` and `hemx-lsp` outside already-clean focused contracts. The xtask mutation runner now creates shard output parents before invoking mutest, fixing first-use failure for newly sharded packages. `hemx-derive` public shards `1/8` through `7/8` are clean. Shard `7/8` passed with 60 mutants (45 caught, 15 unviable) after exact coverage of all state/async/result registration shapes and the no-generated-component repair diagnostic. Shard `8/8` remains. The mutation entry point accepts validated one-based `SHARD/TOTAL` operands, maps them to native zero-based shards, uses shard-specific output directories, grants repo-owned compiler probes a 120-second floor, and preserves the unsharded gate. All eight deterministic `hemx-build` shards now pass: 1,304 mutants total, 1,070 caught and 234 unviable, including final shard `8/8` with 159 mutants (139 caught, 20 unviable). The complete 470-mutant `hemx-axum` package gate passes with 262 caught and 208 unviable after public page/form/multipart/registry/response/runtime proofs and narrow classification of infallible header parsing and streamed multipart unwrap-equivalent mutants. +- **Blocked by:** none; broad survivors currently remain in `hemx-lsp` outside already-clean focused contracts. The xtask mutation runner now creates shard output parents before invoking mutest, fixing first-use failure for newly sharded packages. All eight deterministic `hemx-derive` shards pass: 480 mutants total, 393 caught and 87 unviable, including final shard `8/8` with 54 mutants (42 caught, 12 unviable) after fail-closed malformed symbol-line handling, exact sorted/deduplicated component discovery, component filtering, non-handler exclusion, and direct quoted compile diagnostics. The mutation entry point accepts validated one-based `SHARD/TOTAL` operands, maps them to native zero-based shards, uses shard-specific output directories, grants repo-owned compiler probes a 120-second floor, and preserves the unsharded gate. All eight deterministic `hemx-build` shards now pass: 1,304 mutants total, 1,070 caught and 234 unviable, including final shard `8/8` with 159 mutants (139 caught, 20 unviable). The complete 470-mutant `hemx-axum` package gate passes with 262 caught and 208 unviable after public page/form/multipart/registry/response/runtime proofs and narrow classification of infallible header parsing and streamed multipart unwrap-equivalent mutants. - **Proof:** the new xtask mutation command exits zero within its documented bound, covers each applicable package, emits no unexplained missed mutant, and a deliberate adjacent mutation makes it fail. `cargo run -p hemx-xtask -- test` remains green. req: test/020 req: test/021 ## 3. Elect and enforce the release license policy diff --git a/hemx-derive/src/lib.rs b/hemx-derive/src/lib.rs index 2b2a1e4..c7988ea 100644 --- a/hemx-derive/src/lib.rs +++ b/hemx-derive/src/lib.rs @@ -1007,9 +1007,7 @@ fn symbol_component(symbol: &str) -> Option<&str> { } fn compile_error(message: &str) -> TokenStream { - format!("compile_error!({message:?});") - .parse() - .expect("compile_error expansion is valid") + quote!(compile_error!(#message);).into() } #[cfg(test)] @@ -1023,7 +1021,8 @@ mod tests { handler_syms_path, has_form_param, has_non_unit_return, is_type_named, join_contract_errors, missing_component_handlers, missing_form_generated_files_message, missing_handle_params, missing_handler_generated_files_message, parser_type, - returns_result, rust_ident, syms_contains_handle, ComponentHandler, HandlerPlacement, + returns_result, rust_ident, symbol_component, syms_components, syms_contains_handle, + syms_handles, ComponentHandler, HandlerPlacement, }; use quote::{quote, ToTokens}; use syn::{parse_quote, ItemFn, Type}; @@ -1345,7 +1344,7 @@ mod tests { let path = std::env::temp_dir().join("hemx-derive-syms-test.syms"); std::fs::write( &path, - "hemx-syms-v1\nslot\ttemplates/a.heml::count\tcount\t1\nhandle\ttemplates/a.heml::create\tcreate\t2\nhandle_form\tcreate\tnew_todo\nhandle_param\tcreate\ttodo_id\n", + "hemx-syms-v1\nslot\ttemplates/a.heml::count\tcount\t1\nhandle\ttemplates/z.heml::archive\tarchive\t3\nhandle\ttemplates/a.heml::create\tcreate\t2\nhandle\ttemplates/a.heml::delete\tdelete\t4\nhandle\ttemplates/a.heml::create\tcreate-again\t5\nhandle\thas-no-component\tignored\t6\nhandle\nhandle_form\tcreate\tnew_todo\nhandle_param\tcreate\ttodo_id\n", ) .unwrap(); @@ -1355,6 +1354,19 @@ mod tests { assert!(!handle_requires_form(&path, "missing")); assert_eq!(handle_params(&path, "create"), vec!["todo_id"]); assert!(handle_params(&path, "missing").is_empty()); + assert_eq!( + syms_handles(&path, Some("a")), + vec!["create", "delete", "create-again"] + ); + assert_eq!( + syms_handles(&path, None), + vec!["archive", "create", "delete", "create-again", "ignored"] + ); + assert_eq!(syms_components(&path), vec!["a", "z"]); + assert_eq!(symbol_component("templates/a.heml::create"), Some("a")); + assert_eq!(symbol_component("a.heml::create"), Some("a")); + assert_eq!(symbol_component("a.html::create"), None); + assert_eq!(symbol_component("a.heml"), None); let _ = std::fs::remove_file(&path); assert!( @@ -1531,6 +1543,8 @@ mod tests { mod component { #[hemx::handler] fn create() -> impl hemx::IntoEffect { hemx::advanced::EffectBatch::default() } + + fn helper() {} } }; let (_, items) = module.content.expect("inline module");