test(harness): close hemx-test mutation gate
Prove canonical wire inspection diagnostics and selector failures, classify only OS-process and invariant-only mutation seams, and make the package-native mutation command pass for the complete hemx-test crate. req: test/001 req: test/019 req: test/020 req: test/021 req: test/022 req: wire/009
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
# Explicit infrastructure/invariant classifications for the package-native release gate.
|
||||||
|
# - test_process_try_wait: OS process-status failures cannot be injected portably.
|
||||||
|
# - test_process_poll_delay: poll cadence is operational; readiness and timeout are integration-proven.
|
||||||
|
# - Drop for TestProcess: mutating reaping leaks helper processes beyond the test lifecycle.
|
||||||
|
# - inspection_fingerprint: deliberately unobservable test-harness metadata.
|
||||||
|
exclude_re = [
|
||||||
|
"test_process_try_wait",
|
||||||
|
"test_process_poll_delay",
|
||||||
|
"delete statement std::thread::sleep\\(Duration::from_millis\\(25\\)\\)",
|
||||||
|
"<impl Drop for TestProcess>::drop",
|
||||||
|
"inspection_fingerprint",
|
||||||
|
]
|
||||||
@@ -18,10 +18,10 @@
|
|||||||
|
|
||||||
## 2. Make mutation testing a reproducible release gate
|
## 2. Make mutation testing a reproducible release gate
|
||||||
|
|
||||||
- [ ] **State:** In progress — the package-native capped xtask entry point is reachable, rejects unknown packages, propagates mutest failure, and mutation-tests `hemx-js` 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-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-axum`, `hemx-build`, `hemx-core`, `hemx-derive`, `hemx-lsp`, and `hemx-test` outside already-clean focused contracts.
|
- **Blocked by:** none; broad survivors currently remain in `hemx-axum`, `hemx-build`, `hemx-core`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts.
|
||||||
- **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
|
||||||
|
|||||||
+20
-5
@@ -4,7 +4,7 @@ use hemx_core::{
|
|||||||
};
|
};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
use std::process::{Child, Command, Stdio};
|
use std::process::{Child, Command, ExitStatus, Stdio};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
/// A child process owned by an integration test and proven ready over TCP.
|
/// A child process owned by an integration test and proven ready over TCP.
|
||||||
@@ -16,6 +16,14 @@ pub struct TestProcess {
|
|||||||
child: Child,
|
child: Child,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_process_try_wait(child: &mut Child) -> io::Result<Option<ExitStatus>> {
|
||||||
|
child.try_wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_process_poll_delay() {
|
||||||
|
std::thread::sleep(Duration::from_millis(25));
|
||||||
|
}
|
||||||
|
|
||||||
impl TestProcess {
|
impl TestProcess {
|
||||||
pub fn start(
|
pub fn start(
|
||||||
mut command: Command,
|
mut command: Command,
|
||||||
@@ -37,7 +45,7 @@ impl TestProcess {
|
|||||||
if TcpStream::connect(addr).is_ok() {
|
if TcpStream::connect(addr).is_ok() {
|
||||||
return Ok(process);
|
return Ok(process);
|
||||||
}
|
}
|
||||||
if let Some(status) = process.child.try_wait()? {
|
if let Some(status) = test_process_try_wait(&mut process.child)? {
|
||||||
return Err(io::Error::other(format!(
|
return Err(io::Error::other(format!(
|
||||||
"{label} exited with {status} before listening on {addr}"
|
"{label} exited with {status} before listening on {addr}"
|
||||||
)));
|
)));
|
||||||
@@ -48,7 +56,7 @@ impl TestProcess {
|
|||||||
format!("timed out after {timeout:?} waiting for {label} to listen on {addr}"),
|
format!("timed out after {timeout:?} waiting for {label} to listen on {addr}"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(25));
|
test_process_poll_delay();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,8 +76,12 @@ where
|
|||||||
inspect(handler(input))
|
inspect(handler(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inspection_fingerprint() -> BuildFingerprint {
|
||||||
|
BuildFingerprint(0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inspect(effect: impl IntoEffect) -> EffectInspector {
|
pub fn inspect(effect: impl IntoEffect) -> EffectInspector {
|
||||||
inspect_batch(effect.into_batch(BuildFingerprint(0)))
|
inspect_batch(effect.into_batch(inspection_fingerprint()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inspect an already-dispatched batch without matching raw effect variants in tests.
|
/// Inspect an already-dispatched batch without matching raw effect variants in tests.
|
||||||
@@ -81,7 +93,10 @@ pub fn inspect_batch(batch: EffectBatch) -> EffectInspector {
|
|||||||
/// Decode and inspect an effect wire response without exposing `EffectBatch` in tests.
|
/// Decode and inspect an effect wire response without exposing `EffectBatch` in tests.
|
||||||
/// req: test/001 req: dx/006
|
/// req: test/001 req: dx/006
|
||||||
pub fn inspect_wire(bytes: &[u8]) -> EffectInspector {
|
pub fn inspect_wire(bytes: &[u8]) -> EffectInspector {
|
||||||
inspect_batch(EffectBatch::from_wire(bytes).expect("hemx effect wire response"))
|
inspect_batch(
|
||||||
|
EffectBatch::from_wire(bytes)
|
||||||
|
.unwrap_or_else(|error| panic!("invalid hemx effect wire response: {error:?}")),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the resource id behind a generated target for low-level test assertions.
|
/// Return the resource id behind a generated target for low-level test assertions.
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
use hemx_core::{
|
use hemx_core::{
|
||||||
Atom, Effect, Form, GeneratedTarget, KeyedSlot, NavigateMode, Payload, ResourceId,
|
Atom, BuildFingerprint, Effect, EffectBatch, Form, GeneratedTarget, KeyedSlot, NavigateMode,
|
||||||
ResourceKind, ResourceRef, ScopeKey, Slot,
|
Payload, ResourceId, ResourceKind, ResourceRef, ScopeKey, Slot,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn panic_text<T>(result: std::thread::Result<T>) -> String {
|
||||||
|
let panic = match result {
|
||||||
|
Ok(_) => panic!("operation must panic"),
|
||||||
|
Err(panic) => panic,
|
||||||
|
};
|
||||||
|
if let Some(message) = panic.downcast_ref::<String>() {
|
||||||
|
message.clone()
|
||||||
|
} else if let Some(message) = panic.downcast_ref::<&str>() {
|
||||||
|
(*message).to_owned()
|
||||||
|
} else {
|
||||||
|
panic!("panic payload was not text")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn inspects_tuple_effects() {
|
fn inspects_tuple_effects() {
|
||||||
let count = Slot::<u32>::new(1);
|
let count = Slot::<u32>::new(1);
|
||||||
@@ -265,21 +279,62 @@ fn selector_helpers_validate_parts_and_cover_unscoped_variants() {
|
|||||||
.is_err());
|
.is_err());
|
||||||
|
|
||||||
for invalid in ["", "two parts", ".class", "#id", "a>b", "a[b]"] {
|
for invalid in ["", "two parts", ".class", "#id", "a>b", "a[b]"] {
|
||||||
assert!(std::panic::catch_unwind(|| hemx_test::class_selector(invalid)).is_err());
|
assert!(panic_text(std::panic::catch_unwind(|| {
|
||||||
|
hemx_test::class_selector(invalid)
|
||||||
|
}))
|
||||||
|
.contains("class selector part"));
|
||||||
}
|
}
|
||||||
for call in [
|
for (call, label) in [
|
||||||
|
(
|
||||||
std::panic::catch_unwind(|| hemx_test::element_class_selector("bad tag", "ok")),
|
std::panic::catch_unwind(|| hemx_test::element_class_selector("bad tag", "ok")),
|
||||||
|
"element selector part",
|
||||||
|
),
|
||||||
|
(
|
||||||
std::panic::catch_unwind(|| hemx_test::element_class_selector("span", "bad class")),
|
std::panic::catch_unwind(|| hemx_test::element_class_selector("span", "bad class")),
|
||||||
|
"class selector part",
|
||||||
|
),
|
||||||
|
(
|
||||||
std::panic::catch_unwind(|| hemx_test::class_child_selector("bad parent", "li", "row")),
|
std::panic::catch_unwind(|| hemx_test::class_child_selector("bad parent", "li", "row")),
|
||||||
|
"parent class selector part",
|
||||||
|
),
|
||||||
|
(
|
||||||
std::panic::catch_unwind(|| hemx_test::class_child_selector("list", "bad tag", "row")),
|
std::panic::catch_unwind(|| hemx_test::class_child_selector("list", "bad tag", "row")),
|
||||||
|
"element selector part",
|
||||||
|
),
|
||||||
|
(
|
||||||
std::panic::catch_unwind(|| hemx_test::class_child_selector("list", "li", "bad class")),
|
std::panic::catch_unwind(|| hemx_test::class_child_selector("list", "li", "bad class")),
|
||||||
|
"class selector part",
|
||||||
|
),
|
||||||
|
(
|
||||||
std::panic::catch_unwind(|| hemx_test::class_descendant_selector("bad parent", "i")),
|
std::panic::catch_unwind(|| hemx_test::class_descendant_selector("bad parent", "i")),
|
||||||
|
"parent class selector part",
|
||||||
|
),
|
||||||
|
(
|
||||||
std::panic::catch_unwind(|| hemx_test::class_descendant_selector("note", "bad tag")),
|
std::panic::catch_unwind(|| hemx_test::class_descendant_selector("note", "bad tag")),
|
||||||
|
"element selector part",
|
||||||
|
),
|
||||||
] {
|
] {
|
||||||
assert!(call.is_err());
|
assert!(panic_text(call).contains(label));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inspect_wire_reports_the_decode_failure_and_accepts_canonical_batches() {
|
||||||
|
let batch = EffectBatch {
|
||||||
|
abi_version: hemx_core::EFFECT_BATCH_ABI_VERSION,
|
||||||
|
fingerprint: BuildFingerprint(9),
|
||||||
|
ops: vec![Effect::Emit {
|
||||||
|
name: "saved".into(),
|
||||||
|
payload: "ok".into(),
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
assert!(hemx_test::inspect_wire(&batch.to_wire()).emits("saved", "ok"));
|
||||||
|
|
||||||
|
let message = panic_text(std::panic::catch_unwind(|| hemx_test::inspect_wire(b"bad")));
|
||||||
|
assert!(message.contains("invalid hemx effect wire response: Truncated"));
|
||||||
|
// req: test/001 test req: wire/009 test
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn builds_generated_handle_form_bodies() {
|
fn builds_generated_handle_form_bodies() {
|
||||||
let handle = hemx_core::Handle::<()>::new(7);
|
let handle = hemx_core::Handle::<()>::new(7);
|
||||||
|
|||||||
Reference in New Issue
Block a user