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
This commit is contained in:
slhx agent
2026-07-17 05:37:07 +02:00
parent 4058d05f20
commit 185cd18990
3 changed files with 51 additions and 2 deletions
+4
View File
@@ -16,6 +16,8 @@
# - write_if_changed propagates non-NotFound read errors; for ordinary filesystem # - 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 # 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. # 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 = [ exclude_re = [
"test_process_try_wait", "test_process_try_wait",
"test_process_poll_delay", "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 surface_for_heml_source.* with surface_for_heml_source.*unwrap\\(\\) in AppBuilder::run",
"replace entry\\? with entry.unwrap\\(\\) in collect_input_files_into", "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 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",
] ]
+1 -1
View File
@@ -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. - [ ] **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. - **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. - **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 - **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 ## 3. Elect and enforce the release license policy
+46 -1
View File
@@ -1451,7 +1451,7 @@ fn stylesheet_class_tokens(source: &str) -> Vec<&str> {
i += 1; i += 1;
continue; continue;
} }
let mut end = start + 1; let mut end = start;
while end < bytes.len() && is_class_continue(bytes[end]) { while end < bytes.len() && is_class_continue(bytes[end]) {
end += 1; end += 1;
} }
@@ -2983,6 +2983,37 @@ mod tests {
let _ = std::fs::remove_dir_all(&base); 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] #[test]
fn class_and_form_resource_contracts_fail_closed_and_deduplicate() { fn class_and_form_resource_contracts_fail_closed_and_deduplicate() {
let base = test_dir("hemx-build-resource-collisions"); let base = test_dir("hemx-build-resource-collisions");
@@ -2990,6 +3021,20 @@ mod tests {
let out = base.join("out"); let out = base.join("out");
let _ = std::fs::remove_dir_all(&base); let _ = std::fs::remove_dir_all(&base);
std::fs::create_dir_all(&templates).unwrap(); 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"); let invalid_class_template = templates.join("invalid_class.heml");
std::fs::write(&invalid_class_template, r#"<div class="123"></div>"#).unwrap(); std::fs::write(&invalid_class_template, r#"<div class="123"></div>"#).unwrap();
let invalid_class = app() let invalid_class = app()