From 185cd1899049f2f9bcf7a80b41bda7bf2e706479 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Fri, 17 Jul 2026 05:37:07 +0200 Subject: [PATCH] test(build): close stylesheet scanner mutants Prove sorted, deduplicated CSS/SCSS class extraction across starts, compounds, delimiters, invalid candidates, and invalid stylesheet module names; simplify token scanning to one boundary loop and classify only deterministic non-terminating progress mutants. req: style/001 req: diagnostics/004 req: test/021 --- .cargo/mutants.toml | 4 ++++ PLAN.md | 2 +- hemx-build/src/lib.rs | 47 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/.cargo/mutants.toml b/.cargo/mutants.toml index 76876fa..fcbf22a 100644 --- a/.cargo/mutants.toml +++ b/.cargo/mutants.toml @@ -16,6 +16,8 @@ # - write_if_changed propagates non-NotFound read errors; for ordinary filesystem # paths, attempting the same write returns the same OS error, so the guard mutant # is externally equivalent while create/update/no-op behavior is mutation-proven. +# - stylesheet_class_tokens loop-progress mutants are deterministically +# non-terminating; sorted, deduplicated, boundary-aware outputs are asserted. exclude_re = [ "test_process_try_wait", "test_process_poll_delay", @@ -34,4 +36,6 @@ exclude_re = [ "replace surface_for_heml_source.* with surface_for_heml_source.*unwrap\\(\\) in AppBuilder::run", "replace entry\\? with entry.unwrap\\(\\) in collect_input_files_into", "replace match guard error.kind\\(\\) == io::ErrorKind::NotFound with true in write_if_changed", + "replace \\+= with (?:-=|\\*=) in stylesheet_class_tokens", + "replace 1 with 0 in stylesheet_class_tokens", ] diff --git a/PLAN.md b/PLAN.md index f013203..0c29ab6 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, propagates mutest failure, and mutation-tests `hemx-axum`, `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-build`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts. The current `hemx-build` frontier now mutation-proves resource extraction, canonical generated contracts, and all 51 `AppBuilder::run`/input-collection mutants, including nested HE/ML/CSS/SCSS discovery, explicit-surface mode, `OUT_DIR`, stable ordering, missing inputs, output failures, invalid UTF-8, and recursive permission errors; the remaining package frontier starts at stylesheet parsing and template-context diagnostics. The complete 470-mutant `hemx-axum` package gate now 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-build`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts. The current `hemx-build` frontier now mutation-proves resource extraction, canonical generated contracts, `AppBuilder::run`/input collection, and all 66 stylesheet discovery/scanner mutants, including exact sorting, deduplication, selector boundaries, invalid stylesheet names, and generated CSS resources; the remaining package frontier starts at template-context/Rust-fact diagnostics. The complete 470-mutant `hemx-axum` package gate now 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-build/src/lib.rs b/hemx-build/src/lib.rs index 3418c93..90c0408 100644 --- a/hemx-build/src/lib.rs +++ b/hemx-build/src/lib.rs @@ -1451,7 +1451,7 @@ fn stylesheet_class_tokens(source: &str) -> Vec<&str> { i += 1; continue; } - let mut end = start + 1; + let mut end = start; while end < bytes.len() && is_class_continue(bytes[end]) { end += 1; } @@ -2983,6 +2983,37 @@ mod tests { let _ = std::fs::remove_dir_all(&base); } + #[test] + fn stylesheet_class_scanner_is_sorted_deduplicated_and_boundary_aware() { + assert_eq!(stylesheet_class_tokens(""), Vec::<&str>::new()); + assert_eq!(stylesheet_class_tokens(".a"), vec!["a"]); + assert_eq!(stylesheet_class_tokens(".ab"), vec!["ab"]); + assert_eq!(stylesheet_class_tokens("x.skip .kept"), vec!["kept"]); + assert_eq!(stylesheet_class_tokens("x..kept"), vec!["kept"]); + assert_eq!(stylesheet_class_tokens("..kept"), vec!["kept"]); + assert_eq!(stylesheet_class_tokens(". .kept"), vec!["kept"]); + assert_eq!(stylesheet_class_tokens(".1 .kept"), vec!["kept"]); + assert_eq!(stylesheet_class_tokens(".é .kept"), vec!["kept"]); + assert!(!preceded_by_class_in_compound(b"tag.", 3)); + assert!(preceded_by_class_in_compound(b".first.", 6)); + assert_eq!( + stylesheet_class_tokens( + ".z9, .alpha.alpha, ._private, .-prefixed, .compound.second, tag.skipped, name-.skipped, name_.skipped" + ), + vec!["-prefixed", "_private", "alpha", "compound", "second", "z9"] + ); + assert_eq!( + stylesheet_class_tokens( + " .space\n.newline\r.return\t.tab,.comma{.open}.close>.child+.adjacent~.sibling(.paren)" + ), + vec![ + "adjacent", "child", "close", "comma", "newline", "open", "paren", "return", + "sibling", "space", "tab" + ] + ); + // test req: style/001 + } + #[test] fn class_and_form_resource_contracts_fail_closed_and_deduplicate() { let base = test_dir("hemx-build-resource-collisions"); @@ -2990,6 +3021,20 @@ mod tests { let out = base.join("out"); let _ = std::fs::remove_dir_all(&base); std::fs::create_dir_all(&templates).unwrap(); + let invalid_stylesheet = templates.join("123.css"); + std::fs::write(&invalid_stylesheet, ".valid {}").unwrap(); + let invalid_stylesheet_name = app() + .template_dir(&templates) + .out_dir(&out) + .run() + .unwrap_err(); + assert_eq!(invalid_stylesheet_name.kind(), io::ErrorKind::InvalidData); + assert_eq!( + invalid_stylesheet_name.to_string(), + "invalid template name `123`; expected a Rust module identifier" + ); + std::fs::remove_file(invalid_stylesheet).unwrap(); + let invalid_class_template = templates.join("invalid_class.heml"); std::fs::write(&invalid_class_template, r#"
"#).unwrap(); let invalid_class = app()