From 78a0d91223220c6655dc3ed3fe6c4566f578e135 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Fri, 17 Jul 2026 14:27:35 +0200 Subject: [PATCH] test(derive): close fourth mutation shard Prove generated form decoding for required, optional, and repeated controls plus bare, qualified, mismatched, and empty generic container boundaries. req: form/004 req: diagnostics/003 req: test/022 req: test/023 --- PLAN.md | 2 +- hemx-derive/src/lib.rs | 59 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/PLAN.md b/PLAN.md index 3f22036..9d40f5f 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 `3/8` are clean. Shard `3/8` passed with 61 mutants (52 caught, 9 unviable) after exact missing-symbol fallback plus accumulated missing-field, singular/multiple, required/optional, and malformed form-contract diagnostics. Shards `4/8` through `8/8` remain. 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-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 `4/8` are clean. Shard `4/8` passed with 61 mutants (49 caught, 12 unviable) after exact required/optional/multiple form-decoding code generation plus bare/qualified/mismatched/empty generic container boundaries. Shards `5/8` through `8/8` remain. 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 1f45345..1ca4e59 100644 --- a/hemx-derive/src/lib.rs +++ b/hemx-derive/src/lib.rs @@ -1023,13 +1023,13 @@ fn compile_error(message: &str) -> TokenStream { mod tests { use super::{ add_app_registry_helper, add_component_register_helper, client_handler_has_inputs, - component_handler_names, expand_handler_function, form_contract_errors, form_fields, - form_impl_generics, form_model_type, form_resource_id, handle_params, handle_requires_form, - handler_form_model_type, handler_placement, 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, syms_contains_handle, - HandlerPlacement, + component_handler_names, expand_handler_function, form_contract_errors, form_decode_fields, + form_fields, form_impl_generics, form_model_type, form_resource_id, generic_inner_type, + handle_params, handle_requires_form, handler_form_model_type, handler_placement, + 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, syms_contains_handle, HandlerPlacement, }; use quote::{quote, ToTokens}; use syn::{parse_quote, ItemFn, Type}; @@ -1297,6 +1297,51 @@ mod tests { "hemx form `profile` field `required_name` is required in HTML and must not be Option<_>", ] ); + + let decode_struct: syn::ItemStruct = parse_quote!( + struct Profile { + first: Option, + tags: Vec, + required_name: String, + } + ); + let decoded = form_decode_fields(&path, "profile", &decode_struct) + .into_iter() + .map(|tokens| tokens.to_string()) + .collect::>(); + assert_eq!(decoded.len(), 3); + assert!(decoded[0].contains("Some (__hemx_value) => Some")); + assert!(decoded[0].contains("None => None")); + assert!(decoded[1].contains("collect :: < Result < Vec < _ > , _ >> () ?")); + assert!(decoded[2].contains("missing form field")); + + let option: Type = parse_quote!(Option); + let qualified_vec: Type = parse_quote!(std::vec::Vec); + let plain: Type = parse_quote!(String); + assert_eq!( + generic_inner_type(&option, "Option") + .unwrap() + .to_token_stream() + .to_string(), + "String" + ); + assert_eq!( + generic_inner_type(&qualified_vec, "Vec") + .unwrap() + .to_token_stream() + .to_string(), + "u64" + ); + assert!(generic_inner_type(&option, "Vec").is_none()); + assert!(generic_inner_type(&plain, "String").is_none()); + let empty_path = Type::Path(syn::TypePath { + qself: None, + path: syn::Path { + leading_colon: None, + segments: Default::default(), + }, + }); + assert!(generic_inner_type(&empty_path, "Vec").is_none()); std::fs::remove_file(path).unwrap(); // test req: form/004 req: diagnostics/003 }