ci: report native progress and require complete unique test evidence

This commit is contained in:
dh committed 2026-10-03 15:54:58 +02:00
1 parent 01596e1f52
commit 90d86af887
3 files changed
+66 -7

No files matched your search

+45 -2
View File
@@ -134,6 +134,7 @@ static class NativeDiagnostic
var phaseStarted = Stopwatch.StartNew();
var phase = "recovery";
var phaseBudget = TimeSpan.FromMinutes(40);
var heartbeat = Stopwatch.StartNew();
var permitted = false;
while (true)
{
@@ -185,6 +186,11 @@ static class NativeDiagnostic
}
var running = await Command("docker", ["inspect", "--format", "{{.State.Running}}", id], output, "container-running", deadline.Token);
if (running.Output.Trim() != "true") throw new InvalidOperationException("Guest container exited before a native readiness result.");
if (heartbeat.Elapsed >= TimeSpan.FromSeconds(60))
{
Console.WriteLine($"[native-diagnostic] phase={phase}; elapsed={phaseStarted.Elapsed.TotalMinutes:F1}/{phaseBudget.TotalMinutes:F0} minutes; container=running; readiness={(permitted ? "passed" : "pending")}");
heartbeat.Restart();
}
await Task.Delay(TimeSpan.FromSeconds(20), deadline.Token);
}
}
@@ -346,9 +352,18 @@ static class NativeDiagnostic
installer = ReplaceOnce(installer, installer[selectorStart..selectorEnd], selector);
installer = ReplaceOnce(installer, "MIN_TARGET_SIZE=$((16 * 1024 * 1024 * 1024))", "# Target policy is exclusively the own writable 64-GiB emulated disk.");
installer = ReplaceOnce(installer, "no writable installation disk of at least 16 GiB was found", "the run-owned writable 64-GiB installation disk was not proved");
installer = ReplaceOnce(installer, " local message=\"$1\"\n\n echo \"[log] ERROR: $message\"", " local message=\"$1\"\n\n mark_installation_failed || :\n echo \"[log] ERROR: $message\"");
installer = ReplaceOnce(installer, "if (( rc != 0 )); then\n", "if (( rc != 0 )); then\n mark_installation_failed || :\n");
installer = ReplaceAllExact(installer, "rm -f \"$STARTED\"", ": # Keep the owned erase guard on failure; never erase again.", 2);
installer = ReplaceOnce(installer, ": > \"$STARTED\" || fail \"failed to create installation guard\"", "( set -o noclobber; printf '%s:%s:%s\\n' \"$PROOF_TOKEN\" \"$(cat \"$STATE_DIR/source.commit\")\" \"$TARGET_DISK\" > \"$STARTED\" ) || fail \"failed to create the exclusive owned installation guard\"");
installer = ReplaceOnce(installer, "set -u\n", "set -u\nPROOF_TOKEN=\"" + token + "\"\n" + """
mark_installation_failed() {
local state="${STATE_DIR:-/Volumes/installstate}" temporary
[ "$(cat "$state/run.owner" 2>/dev/null)" = "$PROOF_TOKEN" ] || return 1
temporary="$state/guest-phase.install.$$.tmp"
printf '{"token":"%s","phase":"installation-failed"}\n' "$PROOF_TOKEN" > "$temporary" &&
/bin/mv -f "$temporary" "$state/guest-phase.json"
}
installer_parent=$$
# Installer watchdog: 80 minutes, also bounded by the host's 172-minute total.
(
@@ -356,7 +371,7 @@ static class NativeDiagnostic
sleep 4800 & sleeper=$!; wait "$sleeper"; kill -TERM "$installer_parent" 2>/dev/null || :
) & install_watchdog=$!
trap 'kill -TERM "$install_watchdog" 2>/dev/null || :; wait "$install_watchdog" 2>/dev/null || :' EXIT
trap 'kill "${STARTOSINSTALL_PID:-}" "${BOOTSTRAPPER_PID:-}" 2>/dev/null || :; printf "{\"token\":\"%s\",\"phase\":\"installation-failed\"}\n" "$PROOF_TOKEN" > /Volumes/installstate/guest-phase.json; exit 1' TERM INT
trap 'kill "${STARTOSINSTALL_PID:-}" "${BOOTSTRAPPER_PID:-}" 2>/dev/null || :; mark_installation_failed || :; exit 1' TERM INT
""" + "\n");
var firstboot = ReadPinned(source, "src/install/firstboot/launch.sh", "d6b29bb42ffe99edda6b3be3faf6009c4e0b34e5b8bba6eb0855cf24a0c24307");
firstboot = ReplaceOnce(firstboot, "log \"prebuilt account package installed successfully\"\n", "log \"prebuilt account package installed successfully\"\n" + """
@@ -392,12 +407,40 @@ static class NativeDiagnostic
foreach (var name in new[] { "macos-native-firstboot.sh", "macos-native-disk-guard.sh" })
await Command("bash", ["-n", Path.Combine("tools", "ci", name)], output, name + "-syntax", cancellation);
Save(Path.Combine(output, "full-source-hashes.json"), new Dictionary<string, string> { ["full-install.sh"] = Hash(Encoding.UTF8.GetBytes(installer)), ["full-firstboot.sh"] = Hash(Encoding.UTF8.GetBytes(firstboot)), ["full-state-source.sh"] = Hash(Encoding.UTF8.GetBytes(initialize)), ["MacOsNativeGuest.cs"] = Hash(File.ReadAllBytes("tools/ci/MacOsNativeGuest.cs")), ["macos-native-firstboot.sh"] = Hash(File.ReadAllBytes("tools/ci/macos-native-firstboot.sh")), ["macos-native-disk-guard.sh"] = Hash(File.ReadAllBytes("tools/ci/macos-native-disk-guard.sh")) });
if (!writeSource) return;
if (!writeSource)
{
await ValidateInstallerFailureReceipt(installer, output, token, cancellation);
return;
}
File.WriteAllText(Path.Combine(source, "src/install/recovery/full-install.sh"), installer, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/install/firstboot/launch.sh"), firstboot, new UTF8Encoding(false));
File.WriteAllText(Path.Combine(source, "src/install.sh"), initialize, new UTF8Encoding(false));
}
static async Task ValidateInstallerFailureReceipt(string installer, string output, string token, CancellationToken cancellation)
{
const string start = "mark_installation_failed() {";
const string end = "\n}\ninstaller_parent=$$";
var begin = installer.IndexOf(start, StringComparison.Ordinal);
var finish = begin < 0 ? -1 : installer.IndexOf(end, begin, StringComparison.Ordinal);
if (begin < 0 || finish < begin || installer.Split("mark_installation_failed || :", StringSplitOptions.None).Length != 4)
throw new InvalidOperationException("Pinned installer must publish its terminal failure phase from fail(), nonzero startosinstall and TERM/INT.");
var function = installer[begin..(finish + 2)];
var state = Path.Combine(output, "installer-failure-fixture");
Directory.CreateDirectory(state);
File.WriteAllText(Path.Combine(state, "run.owner"), token);
var command = "set -u\n" + function + "\nPROOF_TOKEN=\"$1\"; STATE_DIR=\"$2\"; mark_installation_failed";
await Command("bash", ["-c", command, "installer-failure-validation", token, state], output, "installer-terminal-failure", cancellation);
var receipt = File.ReadAllText(Path.Combine(state, "guest-phase.json"));
using var json = JsonDocument.Parse(receipt);
if (json.RootElement.GetProperty("token").GetString() != token || json.RootElement.GetProperty("phase").GetString() != "installation-failed" || Directory.GetFiles(state, "*.tmp").Length != 0)
throw new InvalidOperationException("Installer failed to publish a complete atomic terminal phase.");
File.WriteAllText(Path.Combine(state, "run.owner"), "foreign");
var foreign = await Command("bash", ["-c", command, "installer-failure-validation", token, state], output, "installer-foreign-failure", cancellation, requireSuccess: false);
if (foreign.ExitCode == 0 || File.ReadAllText(Path.Combine(state, "guest-phase.json")) != receipt)
throw new InvalidOperationException("Installer terminal phase overwrote foreign run-owned state.");
}
static async Task PermitInstallation(string id, string output, string token, string commit, string readiness, CancellationToken cancellation)
{
await Command("docker", ["inspect", id], output, "full-container-boundary", cancellation);
+19 -3
View File
@@ -277,17 +277,18 @@ static class NativeGuest
}
var results = root.Element(ns + "Results")?.Elements(ns + "UnitTestResult").ToArray() ?? [];
var executionIds = new HashSet<string>(StringComparer.Ordinal);
var testIds = new HashSet<string>(StringComparer.Ordinal);
var native = new List<string>();
foreach (var result in results)
{
var id = (string?)result.Attribute("testId") ?? "";
var executionId = (string?)result.Attribute("executionId") ?? "";
if ((string?)result.Attribute("outcome") != "Passed" || executionId.Length == 0 || !executionIds.Add(executionId) || !definitions.TryGetValue(id, out var identity))
if ((string?)result.Attribute("outcome") != "Passed" || executionId.Length == 0 || !executionIds.Add(executionId) || !testIds.Add(id) || !definitions.TryGetValue(id, out var identity))
throw new InvalidOperationException("TRX has a missing, duplicate or non-passed execution.");
if (RequiredNativeTests.Contains(identity, StringComparer.Ordinal)) native.Add(identity);
}
if (results.Length != expected || native.Count != RequiredNativeTests.Length || !native.Order().SequenceEqual(RequiredNativeTests.Order()))
throw new InvalidOperationException("TRX does not prove every expected execution and the five explicit native macOS tests.");
if (definitions.Count != expected || results.Length != expected || native.Count != RequiredNativeTests.Length || !native.Order().SequenceEqual(RequiredNativeTests.Order()))
throw new InvalidOperationException("TRX does not prove every expected test definition exactly once and the five explicit native macOS tests.");
return new(total, executed, passed, failed, notExecuted, native.ToArray());
}
@@ -461,6 +462,21 @@ static class NativeGuest
var duplicate = new XDocument(fixture);
duplicate.Descendants(ns + "UnitTestResult").Last().SetAttributeValue("executionId", "execution-0");
Reject(() => ValidateTrx(duplicate.ToString(), ExpectedTests, started));
var repeatedManaged = new XDocument(fixture);
repeatedManaged.Descendants(ns + "UnitTestResult").Last().SetAttributeValue("testId", "test-5");
Reject(() => ValidateTrx(repeatedManaged.ToString(), ExpectedTests, started));
var unexecutedDefinition = new XDocument(fixture);
var extraDefinition = new XElement(unexecutedDefinition.Descendants(ns + "UnitTest").Last());
extraDefinition.SetAttributeValue("id", "unexecuted-test");
unexecutedDefinition.Root!.Element(ns + "TestDefinitions")!.Add(extraDefinition);
Reject(() => ValidateTrx(unexecutedDefinition.ToString(), ExpectedTests, started));
var missingDefinition = new XDocument(fixture);
missingDefinition.Descendants(ns + "UnitTest").Last().Remove();
Reject(() => ValidateTrx(missingDefinition.ToString(), ExpectedTests, started));
var theoryRows = new XDocument(fixture);
foreach (var method in theoryRows.Descendants(ns + "TestMethod").Skip(RequiredNativeTests.Length).Take(2))
method.SetAttributeValue("name", "TheoryWithDistinctRowIds");
ValidateTrx(theoryRows.ToString(), ExpectedTests, started);
var counters = new XDocument(fixture);
counters.Descendants(ns + "Counters").Single().SetAttributeValue("passed", "572");
Reject(() => ValidateTrx(counters.ToString(), ExpectedTests, started));