Establish controlled red-suite baseline
This commit is contained in:
14
README.md
14
README.md
@@ -12,12 +12,13 @@ the main assembly's packet/message/asset/primitive wire-data, core
|
|||||||
runtime/networking, avatar-facing manager, world/social/service manager, RLV,
|
runtime/networking, avatar-facing manager, world/social/service manager, RLV,
|
||||||
LSL tools, Utilities, Vivox, and WebRTC slices now have complete callable,
|
LSL tools, Utilities, Vivox, and WebRTC slices now have complete callable,
|
||||||
failure-only signatures. The independent downstream fixture compiles every
|
failure-only signatures. The independent downstream fixture compiles every
|
||||||
cataloged type and member with zero exclusions; semantic Rust test translations
|
cataloged type and member with zero exclusions, and all 1,289 NUnit invocations
|
||||||
are the next stage.
|
now have reviewed Rust parity cases. Production behavior implementation is the
|
||||||
|
next stage.
|
||||||
|
|
||||||
Pending NUnit cases are inventory only, not generated fake tests. A case counts
|
A case counts as translated only when an explicit Rust test body calls the
|
||||||
as translated only when an explicit Rust test body calls the mapped API and
|
mapped API and retains the upstream identity and body hash. The parity ledger
|
||||||
retains the upstream identity and body hash.
|
now contains zero pending or unreviewed cases.
|
||||||
|
|
||||||
The source snapshot, compatibility rules, dependency research, and ordered
|
The source snapshot, compatibility rules, dependency research, and ordered
|
||||||
implementation plan are in [RUSTREWRITE.md](RUSTREWRITE.md).
|
implementation plan are in [RUSTREWRITE.md](RUSTREWRITE.md).
|
||||||
@@ -37,6 +38,7 @@ cargo test --workspace --no-run
|
|||||||
python3 tools/generate_rust_mapping.py --check
|
python3 tools/generate_rust_mapping.py --check
|
||||||
python3 tools/generate_api_shims.py --check
|
python3 tools/generate_api_shims.py --check
|
||||||
python3 tools/check_test_parity.py
|
python3 tools/check_test_parity.py
|
||||||
|
python3 tools/audit_red_suite.py
|
||||||
```
|
```
|
||||||
|
|
||||||
`tests/upstream-tests.json` is the machine-readable NUnit parity catalog.
|
`tests/upstream-tests.json` is the machine-readable NUnit parity catalog.
|
||||||
@@ -46,3 +48,5 @@ verifies the catalog against the pinned adjacent LibreMetaverse checkout without
|
|||||||
overwriting those files.
|
overwriting those files.
|
||||||
|
|
||||||
Running `cargo test --workspace` is intentionally red during the shim stage.
|
Running `cargo test --workspace` is intentionally red during the shim stage.
|
||||||
|
The controlled audit aggregates every expected failure by standardized C#
|
||||||
|
member ID and rejects unrelated fixture, assertion, compile, or symbol errors.
|
||||||
|
|||||||
@@ -364,6 +364,32 @@ The test-suite gate is complete only when:
|
|||||||
- no production method contains real behavior beyond what is required to make
|
- no production method contains real behavior beyond what is required to make
|
||||||
signatures and constants compile.
|
signatures and constants compile.
|
||||||
|
|
||||||
|
### 3.3 Fixed controlled-red baseline
|
||||||
|
|
||||||
|
Milestone 03 closes against the pinned 1,289-invocation catalog, not the older
|
||||||
|
1,295 source-text estimate. The six-case difference is intentional: five
|
||||||
|
attributes were commented out and one parameterless `[Test]` marker duplicated
|
||||||
|
a parameterized `[TestCase]` method. Reintroducing those entries would create
|
||||||
|
tests that NUnit never runs.
|
||||||
|
|
||||||
|
`tests/red-suite-baseline.json` fixes the machine-checked ledger at 1,266
|
||||||
|
ordinary translations, 19 live-grid translations, four benchmarks, and zero
|
||||||
|
pending, drifted, duplicate, missing, or unreviewed cases. Run the controlled
|
||||||
|
audit with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 tools/audit_red_suite.py
|
||||||
|
```
|
||||||
|
|
||||||
|
With all three live-grid credentials present, the baseline executes every case:
|
||||||
|
22 tests pass, 1,287 fail at 131 standardized C# member IDs, and none are
|
||||||
|
ignored. Twenty passes are gate/support tests. The only two passing parity cases
|
||||||
|
verify the required `BAKED_TEXTURE_COUNT` constant and composable validation
|
||||||
|
flags; they do not represent implemented production behavior. Without complete
|
||||||
|
credentials, the same audit conditionally ignores exactly the 19 live-grid
|
||||||
|
cases and requires the remaining 1,268 parity failures to retain standardized
|
||||||
|
member IDs. Production crates remain the failure-only milestone-02 shims.
|
||||||
|
|
||||||
## 4. Validated Rust dependency map
|
## 4. Validated Rust dependency map
|
||||||
|
|
||||||
Versions below were queried from crates.io on 2026-08-08 with `cargo search`
|
Versions below were queried from crates.io on 2026-08-08 with `cargo search`
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ fn estate_packet(blocked: &[UUID], trusted: &[UUID], allowed: &[UUID]) -> Estate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn block_on_with<F: Future>(future: F, mut after_pending: impl FnMut()) -> F::Output {
|
fn block_on_with<F: Future>(future: F, mut after_first_poll: impl FnMut()) -> F::Output {
|
||||||
struct ThreadWake(std::thread::Thread);
|
struct ThreadWake(std::thread::Thread);
|
||||||
impl Wake for ThreadWake {
|
impl Wake for ThreadWake {
|
||||||
fn wake(self: Arc<Self>) {
|
fn wake(self: Arc<Self>) {
|
||||||
@@ -93,7 +93,7 @@ fn block_on_with<F: Future>(future: F, mut after_pending: impl FnMut()) -> F::Ou
|
|||||||
Poll::Ready(value) => return value,
|
Poll::Ready(value) => return value,
|
||||||
Poll::Pending if !invoked => {
|
Poll::Pending if !invoked => {
|
||||||
invoked = true;
|
invoked = true;
|
||||||
after_pending();
|
after_first_poll();
|
||||||
}
|
}
|
||||||
Poll::Pending => std::thread::park_timeout(Duration::from_millis(10)),
|
Poll::Pending => std::thread::park_timeout(Duration::from_millis(10)),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ use libremetaverse::{AppearanceManager, AvatarManager, Error};
|
|||||||
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AppearanceManager_Null_Throws::test a3c872805bc924b5a1a9441d5c9eb61ed6d6e9477e4bb411131359290b144ab4 translated
|
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AppearanceManager_Null_Throws::test a3c872805bc924b5a1a9441d5c9eb61ed6d6e9477e4bb411131359290b144ab4 translated
|
||||||
#[test]
|
#[test]
|
||||||
fn appearance_manager_null_throws() {
|
fn appearance_manager_null_throws() {
|
||||||
assert!(matches!(
|
assert_eq!(
|
||||||
AppearanceManager::new(None),
|
AppearanceManager::new(None).err(),
|
||||||
Err(Error::ArgumentNull)
|
Some(Error::ArgumentNull)
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AvatarManager_Null_Throws::test 54f23cbf0ba87b0525c2914e78d75808e93fd1c460fc0bec4d15f09575b1431c translated
|
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AvatarManager_Null_Throws::test 54f23cbf0ba87b0525c2914e78d75808e93fd1c460fc0bec4d15f09575b1431c translated
|
||||||
#[test]
|
#[test]
|
||||||
fn avatar_manager_null_throws() {
|
fn avatar_manager_null_throws() {
|
||||||
assert!(matches!(AvatarManager::new(None), Err(Error::ArgumentNull)));
|
assert_eq!(AvatarManager::new(None).err(), Some(Error::ArgumentNull));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ use libremetaverse::{AgentManager, Error, GridManager, ObjectManager};
|
|||||||
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.GridManager_Null_Throws::test e972a95f334b0f46948cda34c27124c28c44faf4372d0e598d7dc60050b49be4 translated
|
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.GridManager_Null_Throws::test e972a95f334b0f46948cda34c27124c28c44faf4372d0e598d7dc60050b49be4 translated
|
||||||
#[test]
|
#[test]
|
||||||
fn grid_manager_null_throws() {
|
fn grid_manager_null_throws() {
|
||||||
assert!(matches!(GridManager::new(None), Err(Error::ArgumentNull)));
|
assert_eq!(GridManager::new(None).err(), Some(Error::ArgumentNull));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.ObjectManager_Null_Throws::test 8a11feba0edfb35e5dd99f681182e8f5a5cf8a3f738211b45a683a8a2ad45ddb translated
|
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.ObjectManager_Null_Throws::test 8a11feba0edfb35e5dd99f681182e8f5a5cf8a3f738211b45a683a8a2ad45ddb translated
|
||||||
#[test]
|
#[test]
|
||||||
fn object_manager_null_throws() {
|
fn object_manager_null_throws() {
|
||||||
assert!(matches!(ObjectManager::new(None), Err(Error::ArgumentNull)));
|
assert_eq!(ObjectManager::new(None).err(), Some(Error::ArgumentNull));
|
||||||
}
|
}
|
||||||
|
|
||||||
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AgentManager_Null_Throws::test d86e41b6cdd03063685aa330c31b9a274c1d400b9a461abf3065fe185fe0bbf0 translated
|
// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.AgentManager_Null_Throws::test d86e41b6cdd03063685aa330c31b9a274c1d400b9a461abf3065fe185fe0bbf0 translated
|
||||||
#[test]
|
#[test]
|
||||||
fn agent_manager_null_throws() {
|
fn agent_manager_null_throws() {
|
||||||
assert!(matches!(AgentManager::new(None), Err(Error::ArgumentNull)));
|
assert_eq!(AgentManager::new(None).err(), Some(Error::ArgumentNull));
|
||||||
}
|
}
|
||||||
|
|||||||
18
tests/red-suite-baseline.json
Normal file
18
tests/red-suite-baseline.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"upstream_commit": "2aa70bb68513b39795da5d13c88f31b86e85a3ba",
|
||||||
|
"reviewed_cases": 1289,
|
||||||
|
"parity_report": {
|
||||||
|
"pending": 0,
|
||||||
|
"translated": 1266,
|
||||||
|
"ignored_live": 19,
|
||||||
|
"benchmark": 4,
|
||||||
|
"drifted": 0,
|
||||||
|
"unreviewed": 0
|
||||||
|
},
|
||||||
|
"allowed_parity_passes": [
|
||||||
|
"LibreMetaverse.Tests/AppearanceManagerTests.cs::AppearanceManagerTests.BAKED_TEXTURE_COUNT_Is11::test",
|
||||||
|
"LibreMetaverse.Tests/MarketplaceFolderClassifierTests.cs::MarketplaceFolderClassifierTests.ValidateListing_ValidFlags_AreBitmaskComposable::test"
|
||||||
|
],
|
||||||
|
"support_passes": 20
|
||||||
|
}
|
||||||
@@ -3054,7 +3054,7 @@
|
|||||||
"body_sha256": "e972a95f334b0f46948cda34c27124c28c44faf4372d0e598d7dc60050b49be4",
|
"body_sha256": "e972a95f334b0f46948cda34c27124c28c44faf4372d0e598d7dc60050b49be4",
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"fixture_dependencies": [],
|
"fixture_dependencies": [],
|
||||||
"rust_body_sha256": "7ced2dbdadce6cc8654767c7f17f491b428f21c3d397f3236fd46e686ca3f219",
|
"rust_body_sha256": "440233e304c7561b384d23798d815d10806cb624558cc847475b2f517b5275c4",
|
||||||
"status": "translated",
|
"status": "translated",
|
||||||
"rust_file": "tests/compat/tests/world_constructor_semantics.rs",
|
"rust_file": "tests/compat/tests/world_constructor_semantics.rs",
|
||||||
"rust_line": 7,
|
"rust_line": 7,
|
||||||
@@ -3092,7 +3092,7 @@
|
|||||||
"body_sha256": "a3c872805bc924b5a1a9441d5c9eb61ed6d6e9477e4bb411131359290b144ab4",
|
"body_sha256": "a3c872805bc924b5a1a9441d5c9eb61ed6d6e9477e4bb411131359290b144ab4",
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"fixture_dependencies": [],
|
"fixture_dependencies": [],
|
||||||
"rust_body_sha256": "5d9e748f126a3ff03db848d71258287441dd520c3f2a5bfadb5921f3e7fba7d0",
|
"rust_body_sha256": "65ab61a0cc83ccef52575370de46c61fa91a93246938d2e7a5df41b2547c6e41",
|
||||||
"status": "translated",
|
"status": "translated",
|
||||||
"rust_file": "tests/compat/tests/social_constructor_semantics.rs",
|
"rust_file": "tests/compat/tests/social_constructor_semantics.rs",
|
||||||
"rust_line": 7,
|
"rust_line": 7,
|
||||||
@@ -3111,7 +3111,7 @@
|
|||||||
"body_sha256": "8a11feba0edfb35e5dd99f681182e8f5a5cf8a3f738211b45a683a8a2ad45ddb",
|
"body_sha256": "8a11feba0edfb35e5dd99f681182e8f5a5cf8a3f738211b45a683a8a2ad45ddb",
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"fixture_dependencies": [],
|
"fixture_dependencies": [],
|
||||||
"rust_body_sha256": "c6849d0a524859f5436a8e0a78a9baf75a761a8edaa801a3eed09c4057c7eb96",
|
"rust_body_sha256": "551175a56c31bfee564b3f408c6a9868d56e0b6763e36cf71c0cf372760ee575",
|
||||||
"status": "translated",
|
"status": "translated",
|
||||||
"rust_file": "tests/compat/tests/world_constructor_semantics.rs",
|
"rust_file": "tests/compat/tests/world_constructor_semantics.rs",
|
||||||
"rust_line": 13,
|
"rust_line": 13,
|
||||||
@@ -3130,7 +3130,7 @@
|
|||||||
"body_sha256": "d86e41b6cdd03063685aa330c31b9a274c1d400b9a461abf3065fe185fe0bbf0",
|
"body_sha256": "d86e41b6cdd03063685aa330c31b9a274c1d400b9a461abf3065fe185fe0bbf0",
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"fixture_dependencies": [],
|
"fixture_dependencies": [],
|
||||||
"rust_body_sha256": "4c6198a60bbdee2fe94096d579ebff31b8b38048982d3aea25e0c0bb041ba70c",
|
"rust_body_sha256": "0ccd306db6d19296ce8793dc7b6712601f79c5c7cd6f013171102f581589b065",
|
||||||
"status": "translated",
|
"status": "translated",
|
||||||
"rust_file": "tests/compat/tests/world_constructor_semantics.rs",
|
"rust_file": "tests/compat/tests/world_constructor_semantics.rs",
|
||||||
"rust_line": 19,
|
"rust_line": 19,
|
||||||
@@ -3149,7 +3149,7 @@
|
|||||||
"body_sha256": "54f23cbf0ba87b0525c2914e78d75808e93fd1c460fc0bec4d15f09575b1431c",
|
"body_sha256": "54f23cbf0ba87b0525c2914e78d75808e93fd1c460fc0bec4d15f09575b1431c",
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"fixture_dependencies": [],
|
"fixture_dependencies": [],
|
||||||
"rust_body_sha256": "fed746879e584f0c75b19f08009234588905204e5954d7a5f61d14c40664bef1",
|
"rust_body_sha256": "1307f3ee0990260c80fa9ec1b693231d5c3e1f94c293fab7bcca4d5c5140ce55",
|
||||||
"status": "translated",
|
"status": "translated",
|
||||||
"rust_file": "tests/compat/tests/social_constructor_semantics.rs",
|
"rust_file": "tests/compat/tests/social_constructor_semantics.rs",
|
||||||
"rust_line": 16,
|
"rust_line": 16,
|
||||||
|
|||||||
207
tools/audit_red_suite.py
Normal file
207
tools/audit_red_suite.py
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Run and verify MetaCrate's controlled red test-suite baseline."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from collections import Counter, defaultdict, deque
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
CATALOG = ROOT / "tests" / "upstream-tests.json"
|
||||||
|
BASELINE = ROOT / "tests" / "red-suite-baseline.json"
|
||||||
|
LIVE_KEYS = ("GRID_USER", "GRID_PASSWORD", "GRID_LOGIN_URL")
|
||||||
|
SUMMARY_RE = re.compile(
|
||||||
|
r"test result: (?:ok|FAILED)\. (\d+) passed; (\d+) failed; "
|
||||||
|
r"(\d+) ignored; (\d+) measured; (\d+) filtered out"
|
||||||
|
)
|
||||||
|
RESULT_RE = re.compile(
|
||||||
|
r"^test (.+) \.\.\. (ok|FAILED|ignored)(?:, .*)?$", re.MULTILINE
|
||||||
|
)
|
||||||
|
PANIC_RE = re.compile(r"(?:^|\n)thread '([^']+)'(?: \(\d+\))? panicked at ")
|
||||||
|
MEMBER_RE = re.compile(
|
||||||
|
r'unimplemented C# API member: ([^\n]+)|NotImplemented \{ csharp_member: "([^"]+)" \}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class LogAudit:
|
||||||
|
passed: int
|
||||||
|
failed: int
|
||||||
|
ignored: int
|
||||||
|
measured: int
|
||||||
|
filtered: int
|
||||||
|
passed_names: tuple[str, ...]
|
||||||
|
failed_names: tuple[str, ...]
|
||||||
|
ignored_names: tuple[str, ...]
|
||||||
|
members: Counter[str]
|
||||||
|
nonstandard_failures: tuple[str, ...]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_log(text: str) -> LogAudit:
|
||||||
|
"""Parse libtest output and associate each failed test with its member ID."""
|
||||||
|
summaries = [tuple(map(int, match)) for match in SUMMARY_RE.findall(text)]
|
||||||
|
if not summaries:
|
||||||
|
raise ValueError("controlled suite log contains no libtest summaries")
|
||||||
|
totals = tuple(sum(row[index] for row in summaries) for index in range(5))
|
||||||
|
results = RESULT_RE.findall(text)
|
||||||
|
names = {
|
||||||
|
state: tuple(name for name, result in results if result == state)
|
||||||
|
for state in ("ok", "FAILED", "ignored")
|
||||||
|
}
|
||||||
|
|
||||||
|
panic_members: dict[str, deque[tuple[str, ...]]] = defaultdict(deque)
|
||||||
|
starts = list(PANIC_RE.finditer(text))
|
||||||
|
for index, start in enumerate(starts):
|
||||||
|
end = starts[index + 1].start() if index + 1 < len(starts) else len(text)
|
||||||
|
chunk = text[start.end() : end]
|
||||||
|
members = tuple(first or second for first, second in MEMBER_RE.findall(chunk))
|
||||||
|
panic_members[start.group(1)].append(members)
|
||||||
|
|
||||||
|
member_counts: Counter[str] = Counter()
|
||||||
|
nonstandard: list[str] = []
|
||||||
|
for name in names["FAILED"]:
|
||||||
|
members = panic_members[name].popleft() if panic_members[name] else ()
|
||||||
|
if members:
|
||||||
|
member_counts[members[0]] += 1
|
||||||
|
else:
|
||||||
|
nonstandard.append(name)
|
||||||
|
|
||||||
|
if totals[0] != len(names["ok"]) or totals[1] != len(names["FAILED"]):
|
||||||
|
raise ValueError("libtest summaries do not match parsed pass/fail result lines")
|
||||||
|
return LogAudit(
|
||||||
|
*totals,
|
||||||
|
names["ok"],
|
||||||
|
names["FAILED"],
|
||||||
|
names["ignored"],
|
||||||
|
member_counts,
|
||||||
|
tuple(nonstandard),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def dotenv_values() -> dict[str, str]:
|
||||||
|
values: dict[str, str] = {}
|
||||||
|
path = ROOT / ".env"
|
||||||
|
if not path.exists():
|
||||||
|
return values
|
||||||
|
for raw_line in path.read_text().splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
line = line.removeprefix("export ")
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
values[key.strip()] = value.strip().strip("'\"")
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def has_live_credentials() -> bool:
|
||||||
|
dotenv = dotenv_values()
|
||||||
|
return all((os.environ.get(key) or dotenv.get(key, "")).strip() for key in LIVE_KEYS)
|
||||||
|
|
||||||
|
|
||||||
|
def pending_calls() -> list[str]:
|
||||||
|
pending: list[str] = []
|
||||||
|
roots = (ROOT / "tests" / "compat" / "tests", ROOT / "crates")
|
||||||
|
for path in sorted(file for root in roots for file in root.rglob("*.rs")):
|
||||||
|
if re.search(r"\bpending\s*\(", path.read_text()):
|
||||||
|
pending.append(path.relative_to(ROOT).as_posix())
|
||||||
|
return pending
|
||||||
|
|
||||||
|
|
||||||
|
def verify(log: LogAudit, live: bool) -> None:
|
||||||
|
catalog = json.loads(CATALOG.read_text())
|
||||||
|
baseline = json.loads(BASELINE.read_text())
|
||||||
|
report = catalog["report"]
|
||||||
|
expected_cases = baseline["reviewed_cases"]
|
||||||
|
if catalog["upstream_commit"] != baseline["upstream_commit"]:
|
||||||
|
raise ValueError("red-suite baseline targets a different upstream commit")
|
||||||
|
if catalog["expected_cases"] != expected_cases or sum(report.values()) != expected_cases:
|
||||||
|
raise ValueError("parity catalog does not contain the fixed reviewed case count")
|
||||||
|
expected_report = baseline["parity_report"]
|
||||||
|
if report != expected_report:
|
||||||
|
raise ValueError(f"parity report changed: expected {expected_report}, found {report}")
|
||||||
|
pending = pending_calls()
|
||||||
|
if pending:
|
||||||
|
raise ValueError("pending( remains in Rust test sources: " + ", ".join(pending))
|
||||||
|
|
||||||
|
by_test = defaultdict(list)
|
||||||
|
for case in catalog["tests"]:
|
||||||
|
by_test[case["rust_test"]].append(case["id"])
|
||||||
|
passed_cases = sorted(
|
||||||
|
case_id for name in log.passed_names for case_id in by_test.get(name, [])
|
||||||
|
)
|
||||||
|
allowed_passes = sorted(baseline["allowed_parity_passes"])
|
||||||
|
if passed_cases != allowed_passes:
|
||||||
|
raise ValueError(
|
||||||
|
f"parity pass baseline changed: expected {allowed_passes}, found {passed_cases}"
|
||||||
|
)
|
||||||
|
support_passes = log.passed - len(passed_cases)
|
||||||
|
if support_passes != baseline["support_passes"]:
|
||||||
|
raise ValueError(
|
||||||
|
f"support pass baseline changed: expected {baseline['support_passes']}, "
|
||||||
|
f"found {support_passes}"
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_ignored = 0 if live else report["ignored_live"]
|
||||||
|
expected_failed = expected_cases - len(allowed_passes) - expected_ignored
|
||||||
|
if (log.failed, log.ignored) != (expected_failed, expected_ignored):
|
||||||
|
raise ValueError(
|
||||||
|
"controlled totals changed: "
|
||||||
|
f"expected failed={expected_failed}, ignored={expected_ignored}; "
|
||||||
|
f"found failed={log.failed}, ignored={log.ignored}"
|
||||||
|
)
|
||||||
|
if log.nonstandard_failures:
|
||||||
|
raise ValueError(
|
||||||
|
"failures without standardized member IDs: "
|
||||||
|
+ ", ".join(log.nonstandard_failures)
|
||||||
|
)
|
||||||
|
if sum(log.members.values()) != log.failed:
|
||||||
|
raise ValueError("standardized member aggregation does not cover every failure")
|
||||||
|
|
||||||
|
|
||||||
|
def run_suite() -> tuple[int, str]:
|
||||||
|
command = [
|
||||||
|
"cargo",
|
||||||
|
"test",
|
||||||
|
"--workspace",
|
||||||
|
"--no-fail-fast",
|
||||||
|
"--",
|
||||||
|
"--nocapture",
|
||||||
|
"--test-threads=1",
|
||||||
|
]
|
||||||
|
completed = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, check=False)
|
||||||
|
return completed.returncode, completed.stdout + completed.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--log", type=Path, help="audit an existing captured cargo-test log")
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.log:
|
||||||
|
return_code, text = 101, args.log.read_text()
|
||||||
|
else:
|
||||||
|
return_code, text = run_suite()
|
||||||
|
if return_code != 101:
|
||||||
|
raise SystemExit(f"controlled suite returned {return_code}, expected Cargo test failure 101")
|
||||||
|
live = has_live_credentials()
|
||||||
|
audit = parse_log(text)
|
||||||
|
verify(audit, live)
|
||||||
|
print(
|
||||||
|
f"controlled red suite is current: passed={audit.passed}, failed={audit.failed}, "
|
||||||
|
f"ignored={audit.ignored}, live_credentials={str(live).lower()}, "
|
||||||
|
f"standardized_members={len(audit.members)}"
|
||||||
|
)
|
||||||
|
for member, count in audit.members.most_common():
|
||||||
|
print(f"{count:4} {member}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
39
tools/test_audit_red_suite.py
Normal file
39
tools/test_audit_red_suite.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from audit_red_suite import parse_log
|
||||||
|
|
||||||
|
|
||||||
|
class AuditRedSuiteTests(unittest.TestCase):
|
||||||
|
def test_associates_standardized_boundaries_with_failed_tests(self) -> None:
|
||||||
|
audit = parse_log(
|
||||||
|
"""
|
||||||
|
thread 'direct' panicked at generated.rs:1:1:
|
||||||
|
unimplemented C# API member: M:Example.Direct
|
||||||
|
test direct ... FAILED
|
||||||
|
thread 'typed' panicked at test.rs:1:1:
|
||||||
|
assertion failed: left == right
|
||||||
|
left: Some(NotImplemented { csharp_member: "M:Example.Typed" })
|
||||||
|
right: Some(ArgumentNull)
|
||||||
|
test typed ... FAILED
|
||||||
|
test support ... ok
|
||||||
|
test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
self.assertEqual(audit.members["M:Example.Direct"], 1)
|
||||||
|
self.assertEqual(audit.members["M:Example.Typed"], 1)
|
||||||
|
self.assertEqual(audit.nonstandard_failures, ())
|
||||||
|
|
||||||
|
def test_reports_nonstandard_failure(self) -> None:
|
||||||
|
audit = parse_log(
|
||||||
|
"""
|
||||||
|
thread 'broken' panicked at test.rs:1:1:
|
||||||
|
assertion failed
|
||||||
|
test broken ... FAILED
|
||||||
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
self.assertEqual(audit.nonstandard_failures, ("broken",))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user