test(build): close resource extraction mutants
Prove fail-closed slot and handler-parameter diagnostics through AppBuilder, consolidate resource insertion around the stored entry, and lower validated runtime events without redundant fallible identifier conversion. req: build/004 req: diagnostics/004 req: surface/008 req: test/021
This commit is contained in:
@@ -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 adversarially proves static convention diagnostics; public file/source diagnostics and context facts; generated-artifact refresh; client-handler rejection; and exact class/form extraction, field metadata, invalid-name, and collision behavior through `AppBuilder::run`; remaining event/slot/atom/handle extraction and internal diagnostic survivors remain. 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 adversarially proves all class/form/event/slot/atom/handle extraction, field/parameter metadata, invalid-name, collision, deduplication, and keyed-upgrade behavior through `AppBuilder::run`; resource extraction is mutation-clean, leaving only internal diagnostic/code-generation survivors before the full package gate. 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
|
||||||
|
|||||||
+57
-47
@@ -335,7 +335,7 @@ impl Resources {
|
|||||||
if let Some(on_attr) = static_attr(&node.attrs, "data-hemx-on") {
|
if let Some(on_attr) = static_attr(&node.attrs, "data-hemx-on") {
|
||||||
for event in event_tokens(&on_attr) {
|
for event in event_tokens(&on_attr) {
|
||||||
let canonical = canonical_symbol(root, path, event);
|
let canonical = canonical_symbol(root, path, event);
|
||||||
self.insert_event(canonical, event.to_owned(), component.clone())?;
|
self.insert_event(canonical, event.to_owned(), component.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,15 +417,17 @@ impl Resources {
|
|||||||
component: String,
|
component: String,
|
||||||
keyed: bool,
|
keyed: bool,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
insert_resource(&mut self.slots, "slot", symbol, name, component, keyed)
|
let slot = insert_resource(&mut self.slots, "slot", symbol, name, component)?;
|
||||||
|
slot.keyed |= keyed;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_handle(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
|
fn insert_handle(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
|
||||||
insert_resource(&mut self.handles, "handle", symbol, name, component, false)
|
insert_resource(&mut self.handles, "handle", symbol, name, component).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_atom(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
|
fn insert_atom(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
|
||||||
insert_resource(&mut self.atoms, "atom", symbol, name, component, false)
|
insert_resource(&mut self.atoms, "atom", symbol, name, component).map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_class(&mut self, symbol: String, token: String, component: String) -> io::Result<()> {
|
fn insert_class(&mut self, symbol: String, token: String, component: String) -> io::Result<()> {
|
||||||
@@ -482,32 +484,13 @@ impl Resources {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_event(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
|
fn insert_event(&mut self, symbol: String, name: String, component: String) {
|
||||||
let ident = event_ident(&name).ok_or_else(|| {
|
self.events.entry(symbol.clone()).or_insert(EventToken {
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!(
|
|
||||||
"invalid hemx event `{name}`; expected an ASCII event name usable from Rust"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let event = EventToken {
|
|
||||||
symbol,
|
symbol,
|
||||||
ident,
|
ident: name.clone(),
|
||||||
component,
|
component,
|
||||||
name,
|
name,
|
||||||
};
|
});
|
||||||
match self.events.get(&event.symbol) {
|
|
||||||
Some(existing) if existing.name != event.name => Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("conflicting event name for `{}`", existing.symbol),
|
|
||||||
)),
|
|
||||||
Some(_) => Ok(()),
|
|
||||||
None => {
|
|
||||||
self.events.insert(event.symbol.clone(), event);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_form(
|
fn insert_form(
|
||||||
@@ -1323,32 +1306,29 @@ fn __hemx_attr(tag: &str, attr: &str) -> Option<::std::string::String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_resource(
|
fn insert_resource<'a>(
|
||||||
map: &mut BTreeMap<String, Resource>,
|
map: &'a mut BTreeMap<String, Resource>,
|
||||||
kind: &str,
|
kind: &str,
|
||||||
symbol: String,
|
symbol: String,
|
||||||
name: String,
|
name: String,
|
||||||
component: String,
|
component: String,
|
||||||
keyed: bool,
|
) -> io::Result<&'a mut Resource> {
|
||||||
) -> io::Result<()> {
|
let resource = make_resource(kind, symbol, name, component)?;
|
||||||
let mut resource = make_resource(kind, symbol, name, component)?;
|
match map.entry(resource.ident.clone()) {
|
||||||
resource.keyed = keyed;
|
std::collections::btree_map::Entry::Occupied(entry)
|
||||||
match map.get_mut(&resource.ident) {
|
if entry.get().symbol != resource.symbol =>
|
||||||
Some(existing) if existing.symbol != resource.symbol => Err(io::Error::new(
|
{
|
||||||
|
let existing = entry.get();
|
||||||
|
Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidData,
|
io::ErrorKind::InvalidData,
|
||||||
format!(
|
format!(
|
||||||
"duplicate generated identifier `{}` for `{}` and `{}`",
|
"duplicate generated identifier `{}` for `{}` and `{}`",
|
||||||
resource.ident, existing.symbol, resource.symbol
|
resource.ident, existing.symbol, resource.symbol
|
||||||
),
|
),
|
||||||
)),
|
))
|
||||||
Some(existing) => {
|
|
||||||
existing.keyed |= keyed;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
map.insert(resource.ident.clone(), resource);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
std::collections::btree_map::Entry::Occupied(entry) => Ok(entry.into_mut()),
|
||||||
|
std::collections::btree_map::Entry::Vacant(entry) => Ok(entry.insert(resource)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1570,10 +1550,6 @@ fn data_param_ident(name: &str) -> Option<String> {
|
|||||||
rust_ident(&data_name.replace('-', "_"))
|
rust_ident(&data_name.replace('-', "_"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn event_ident(name: &str) -> Option<String> {
|
|
||||||
rust_ident(&name.replace(['-', ':'], "_"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_host_keyed_collection(tag: &str) -> bool {
|
fn can_host_keyed_collection(tag: &str) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
tag,
|
tag,
|
||||||
@@ -2564,6 +2540,40 @@ mod tests {
|
|||||||
invalid_handler.display()
|
invalid_handler.display()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
std::fs::remove_file(invalid_handler).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
templates.join("invalid_param.heml"),
|
||||||
|
r#"<button data-hemx-handle="save" data-123="value">Save</button>"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let error = app()
|
||||||
|
.template_dir(&templates)
|
||||||
|
.out_dir(&invalid_out)
|
||||||
|
.run()
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
"invalid handler param attribute `data-123`; expected data-* name usable from Rust"
|
||||||
|
);
|
||||||
|
|
||||||
|
std::fs::remove_file(templates.join("invalid_param.heml")).unwrap();
|
||||||
|
std::fs::write(
|
||||||
|
templates.join("invalid_slot.heml"),
|
||||||
|
r#"<section data-hemx-slot="123">Invalid</section>"#,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let error = app()
|
||||||
|
.template_dir(&templates)
|
||||||
|
.out_dir(&invalid_out)
|
||||||
|
.run()
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
|
||||||
|
assert_eq!(
|
||||||
|
error.to_string(),
|
||||||
|
"invalid hemx slot name `123`; expected a Rust identifier"
|
||||||
|
);
|
||||||
let _ = std::fs::remove_dir_all(root);
|
let _ = std::fs::remove_dir_all(root);
|
||||||
// test req: build/004 req: client_local/011 req: diagnostics/004
|
// test req: build/004 req: client_local/011 req: diagnostics/004
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user