#:property PublishAot=false using System.Diagnostics; using System.Formats.Tar; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Linq; // Installed-guest CI slice. --validate never invokes macOS, dotnet build/test, or a VM. return await NativeGuest.Execute(args); static class NativeGuest { const string SdkVersion = "10.0.401"; const string SdkSha512 = "33401b4a2da8554e3306db6072ea8569d9fcc608509c271e0aa4b39e7cc432da3631f14e7e1e2445d67d72550d18ce44a8bbd2382a756867ad2edab6b1c963c0"; const int ExpectedTests = 577; const int MaximumLogBytes = 8 * 1024 * 1024; static readonly JsonSerializerOptions JsonOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }; static readonly string[] RequiredNativeTests = [ "MeetingAssistant.Tests.MacOsMeetingAudioSourceTests.NativeAudioCaptureIsPackagedAsSignedMacOsAppForPersistentPrivacyGrant", "MeetingAssistant.Tests.MacOsMeetingIntegrationTests.MacOsCapabilityEndpointReportsEnabledRealProviders", "MeetingAssistant.Tests.MacOsMeetingIntegrationTests.NativeHelperAdvertisesCalendarPromptAndScreenshotFeatures", "MeetingAssistant.Tests.MacOsMeetingIntegrationTests.NativeHelperCropsPngUsingOcrPixelCoordinates", "MeetingAssistant.Tests.MacOsMeetingIntegrationTests.CalendarClientFallsBackToCalendarAutomationWhenEventKitIsDenied" ]; static readonly string[] NativeNames = [ "MeetingAssistantAudioCapture.app/Contents/MacOS/macos-meeting-audio-capture", "macos-desktop-controls", "macos-meeting-integrations", "macos-meeting-assistant-launcher" ]; public static async Task Execute(string[] args) { if (args.Length == 0 || args.SequenceEqual(["--help"])) { Console.WriteLine("dotnet run --file tools/ci/MacOsNativeGuest.cs -- --validate [--archive --source-commit ] | --run --state /Volumes/installstate --work /private/var/tmp/meeting-assistant-native-"); return 0; } try { if (args.SequenceEqual(["--validate"]) || args.Length == 5 && args[0] == "--validate" && args[1] == "--archive" && args[3] == "--source-commit") { ValidateContracts(); if (args.Length == 5) { if (!Hex(args[4], 40)) throw new ArgumentException("Source commit must be the full lowercase Git SHA."); using var archive = File.OpenRead(Path.GetFullPath(args[2])); ValidateArchive(archive, args[4]); } Console.WriteLine("Guest payload, safe Git tar, fresh TRX and native receipt contracts passed; no native execution occurred."); return 0; } if (args.Length != 5 || args[0] != "--run" || args[1] != "--state" || args[3] != "--work") throw new ArgumentException("Choose --validate or --run --state --work ."); return await Run(Path.GetFullPath(args[2]), Path.GetFullPath(args[4])); } catch (Exception exception) { Console.Error.WriteLine(exception.Message); return 1; } } static async Task Run(string state, string work) { if (!OperatingSystem.IsMacOS() || RuntimeInformation.ProcessArchitecture != Architecture.X64) throw new InvalidOperationException("--run is restricted to the installed macOS x86_64 guest."); RequireNoLinks(state); RequireNoLinks(work); var manifestPath = Path.Combine(state, "payload.json"); RequireNoLinks(manifestPath); var payload = ReadPayload(File.ReadAllText(manifestPath)); if (work != "/private/var/tmp/meeting-assistant-native-" + payload.RunToken || !Directory.Exists(work)) throw new InvalidOperationException("Guest work directory does not match this run's owned APFS location."); var owner = Path.Combine(work, "run.owner"); RequireNoLinks(owner); if (File.ReadAllText(owner).TrimEnd('\r', '\n') != payload.RunToken) throw new InvalidOperationException("Guest work directory has a different run owner."); var started = DateTimeOffset.UtcNow; var summary = new TestSummary(0, 0, 0, 0, 0, []); var native = new List(); var osVersion = ""; var architecture = ""; var actualSdk = ""; var trxSha256 = ""; var audioCodeSignExit = -1; var success = false; var reason = ""; var logs = Path.Combine(state, "guest-logs"); var results = Path.Combine(state, "test-results"); var source = Path.Combine(work, "source"); var dotnet = Path.Combine(work, "dotnet", "dotnet"); // Guest checks/restore/build/tests: 25 minutes within the host's 172-minute total. using var deadline = new CancellationTokenSource(TimeSpan.FromMinutes(25)); using var signal = PosixSignalRegistration.Create(PosixSignal.SIGTERM, context => { context.Cancel = true; deadline.Cancel(); }); ConsoleCancelEventHandler cancelHandler = (_, context) => { context.Cancel = true; deadline.Cancel(); }; Console.CancelKeyPress += cancelHandler; void Phase(string phase, string stage) => Save(Path.Combine(state, "guest-phase.json"), new { token = payload.RunToken, phase, stage, updatedUtc = DateTimeOffset.UtcNow }); async Task Cmd(string executable, string[] arguments, string label, bool requireSuccess = true) => await Command(executable, arguments, source, work, logs, label, deadline.Token, requireSuccess); try { RequireAbsent(Path.Combine(state, "full-result.json")); RequireAbsent(logs); RequireAbsent(results); RequireAbsent(source); Directory.CreateDirectory(logs); Directory.CreateDirectory(results); foreach (var directory in new[] { "home", "packages", "tmp" }) { RequireNoLinks(Path.Combine(work, directory)); Directory.CreateDirectory(Path.Combine(work, directory)); } Phase("tests-running", "installed-guest-checks"); osVersion = (await Cmd("/usr/bin/sw_vers", ["-productVersion"], "os-version")).Output.Trim(); architecture = (await Cmd("/usr/bin/uname", ["-m"], "architecture")).Output.Trim(); var uid = (await Cmd("/usr/bin/id", ["-u"], "uid")).Output.Trim(); RequirePlatform(osVersion, architecture, uid); foreach (var volume in new[] { ("/", "system-volume"), (work, "work-volume") }) RequireApfs((await Cmd("/usr/sbin/diskutil", ["info", "-plist", volume.Item1], volume.Item2)).Output); await Cmd("/usr/bin/xcode-select", ["-p"], "clt-location"); await Cmd("/usr/sbin/pkgutil", ["--pkg-info", "com.apple.pkg.CLTools_Executables"], "clt-package"); await Cmd("/usr/bin/xcrun", ["swiftc", "--version"], "swift-version"); await Cmd("/usr/bin/xcrun", ["--sdk", "macosx", "--show-sdk-version"], "apple-sdk-version"); await Cmd("/usr/bin/xcrun", ["--sdk", "macosx", "--show-sdk-path"], "apple-sdk-path"); RequireNoLinks(dotnet); actualSdk = (await Cmd(dotnet, ["--version"], "sdk-version")).Output.Trim(); var sdkInfo = (await Cmd(dotnet, ["--info"], "sdk-info")).Output; if (actualSdk != SdkVersion || !Regex.IsMatch(sdkInfo, @"(?m)^\s*Architecture:\s*x64\s*$")) throw new InvalidOperationException("Guest .NET SDK is not the pinned 10.0.401/x64 toolchain."); Phase("tests-running", "payload-verification"); var archive = Path.Combine(state, "source.tar"); var sdkArchive = Path.Combine(state, "sdk.tar.gz"); RequireNoLinks(archive); RequireNoLinks(sdkArchive); if (await HashFile(archive, false, deadline.Token) != payload.ArchiveSha256 || await HashFile(sdkArchive, true, deadline.Token) != payload.SdkSha512) throw new InvalidOperationException("Guest payload hash does not match the pinned manifest."); using (var stream = File.OpenRead(archive)) ValidateArchive(stream, payload.SourceCommit); // All members were checked before native tar can write anything; the destination is new. Directory.CreateDirectory(source); await Cmd("/usr/bin/tar", ["-xf", archive, "-C", source], "source-extraction"); var project = Path.Combine(source, "MeetingAssistant.Tests", "MeetingAssistant.Tests.csproj"); if (!File.Exists(project)) throw new InvalidOperationException("Source archive lacks the test project."); var nativeRoot = Path.Combine(source, "MeetingAssistant", "bin", "Release", "net10.0", "Native"); RequireAbsent(nativeRoot); Phase("tests-running", "restore"); await Cmd(dotnet, ["restore", project, "-p:EnableWindowsTargeting=true", "-p:TargetFramework=net10.0"], "restore"); Phase("tests-running", "build"); var buildStarted = DateTimeOffset.UtcNow; await Cmd(dotnet, ["build", project, "--no-restore", "-f", "net10.0", "-c", "Release", "-p:EnableWindowsTargeting=true"], "build"); Phase("tests-running", "native-artifacts"); foreach (var name in NativeNames) { var path = Path.Combine(nativeRoot, name); RequireNoLinks(path); RequireFreshFile(path, buildStarted); var fileOutput = (await Cmd("/usr/bin/file", ["-b", path], "native-file-" + native.Count)).Output; var arch = (await Cmd("/usr/bin/xcrun", ["lipo", "-archs", path], "native-architecture-" + native.Count)).Output.Trim(); RequireNativeArtifact(fileOutput, arch); native.Add(new(name, await HashFile(path, false, deadline.Token), arch)); } var signing = await Cmd("/usr/bin/codesign", ["--verify", "--deep", "--strict", Path.Combine(nativeRoot, "MeetingAssistantAudioCapture.app")], "audio-code-sign", false); audioCodeSignExit = signing.ExitCode; if (audioCodeSignExit != 0) throw new InvalidOperationException("Fresh audio app did not pass strict code-signature verification."); Phase("tests-running", "test"); var testStarted = DateTimeOffset.UtcNow; await Cmd(dotnet, ["test", project, "--no-build", "--no-restore", "-f", "net10.0", "-c", "Release", "-p:EnableWindowsTargeting=true", "--logger", "trx;LogFileName=native.trx", "--results-directory", results], "test"); var trxPath = Path.Combine(results, "native.trx"); RequireNoLinks(trxPath); RequireFreshFile(trxPath, testStarted, 16 * 1024 * 1024); var trxBytes = File.ReadAllBytes(trxPath); summary = ValidateTrx(trxBytes, payload.ExpectedTests, testStarted); trxSha256 = Convert.ToHexStringLower(SHA256.HashData(trxBytes)); success = true; } catch (Exception exception) { reason = exception is OperationCanceledException ? "The explicit 25-minute guest deadline or cancellation was reached." : exception.Message; if (reason.Length > 4096) reason = reason[..4096]; Console.Error.WriteLine(reason); } finally { Console.CancelKeyPress -= cancelHandler; var result = new FullResult(payload.RunToken, success, payload.SourceCommit, payload.ArchiveSha256, osVersion, architecture, actualSdk, payload.ExpectedTests, summary.Total, summary.Executed, summary.Passed, summary.Failed, summary.NotExecuted, summary.NativeTests, native.ToArray(), audioCodeSignExit, trxSha256, reason, started, DateTimeOffset.UtcNow); if (success) { try { ValidateResult(result, payload); } catch (InvalidOperationException exception) { success = false; result = result with { Success = false, Reason = exception.Message }; } } Save(Path.Combine(state, "full-result.json"), result); Phase(success ? "tests-passed" : "tests-failed", "complete"); } Console.WriteLine(success ? "Native macOS guest build, signed helpers and all 577 tests passed without skips." : "Native guest validation failed; full-result.json and bounded logs retain the evidence."); return success ? 0 : 1; } static Payload ReadPayload(string json) { using var document = JsonDocument.Parse(json); if (document.RootElement.ValueKind != JsonValueKind.Object || document.RootElement.EnumerateObject().Select(property => property.Name).Distinct(StringComparer.Ordinal).Count() != document.RootElement.EnumerateObject().Count()) throw new InvalidOperationException("Payload manifest must contain one unambiguous JSON object."); var payload = JsonSerializer.Deserialize(json, JsonOptions) ?? throw new InvalidOperationException("Missing guest payload manifest."); if (!Hex(payload.RunToken, 32) || !Hex(payload.SourceCommit, 40) || !Hex(payload.ArchiveSha256, 64) || payload.SdkVersion != SdkVersion || payload.SdkSha512 != SdkSha512 || payload.ExpectedTests != ExpectedTests) throw new InvalidOperationException("Guest payload identity, SDK pin or expected test count is invalid."); return payload; } static void ValidateArchive(Stream stream, string commit) { using var reader = new TarReader(stream, leaveOpen: true); var names = new HashSet(StringComparer.OrdinalIgnoreCase); var commits = new List(); long totalBytes = 0; while (reader.GetNextEntry() is { } entry) { if (entry is PaxGlobalExtendedAttributesTarEntry global) { if (global.GlobalExtendedAttributes.TryGetValue("comment", out var value)) commits.Add(value); if (global.GlobalExtendedAttributes.Keys.Any(key => key is "path" or "linkpath")) throw new InvalidOperationException("Global tar path overrides are not supported."); continue; } if (entry.EntryType is not (TarEntryType.RegularFile or TarEntryType.V7RegularFile or TarEntryType.Directory)) throw new InvalidOperationException("Source tar contains a link or unsupported entry type: " + entry.Name); var name = entry.Name.TrimEnd('/'); if (name.Length == 0 || name.StartsWith('/') || name.Contains('\\') || name.Contains('\0') || name.Split('/').Any(part => part.Length == 0 || part is "." or ".." or ".git" or "bin" or "obj") || !names.Add(name)) throw new InvalidOperationException("Source tar path is unsafe, repeated or contains generated output: " + entry.Name); totalBytes = checked(totalBytes + entry.Length); if (names.Count > 100_000 || totalBytes > 2L * 1024 * 1024 * 1024) throw new InvalidOperationException("Source tar exceeds its explicit entry/size budget."); } if (names.Count == 0 || commits.Count != 1 || commits[0] != commit || !names.Contains("MeetingAssistant.Tests/MeetingAssistant.Tests.csproj")) throw new InvalidOperationException("Source tar does not prove its exact Git commit and test project."); } static TestSummary ValidateTrx(string xml, int expected, DateTimeOffset testStarted) => ValidateTrx(Encoding.UTF8.GetBytes(xml), expected, testStarted); static TestSummary ValidateTrx(byte[] xml, int expected, DateTimeOffset testStarted) { var document = ParseXml(xml); var root = document.Root ?? throw new InvalidOperationException("Empty TRX."); XNamespace ns = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010"; if (root.Name != ns + "TestRun") throw new InvalidOperationException("Unexpected TRX namespace or root."); var times = root.Element(ns + "Times") ?? throw new InvalidOperationException("TRX lacks execution timestamps."); if (!DateTimeOffset.TryParse((string?)times.Attribute("start"), out var start) || !DateTimeOffset.TryParse((string?)times.Attribute("finish"), out var finish) || start < testStarted.AddSeconds(-2) || finish < start || finish > DateTimeOffset.UtcNow.AddSeconds(30)) throw new InvalidOperationException("TRX belongs to a stale or invalid test execution."); var resultSummary = root.Element(ns + "ResultSummary") ?? throw new InvalidOperationException("TRX lacks a final result summary."); if ((string?)resultSummary.Attribute("outcome") != "Completed") throw new InvalidOperationException("TRX test run did not complete."); var counters = resultSummary.Element(ns + "Counters") ?? throw new InvalidOperationException("TRX lacks final counters."); int Count(string name) => int.TryParse((string?)counters.Attribute(name), out var value) && value >= 0 ? value : throw new InvalidOperationException("TRX lacks a valid counter: " + name); var total = Count("total"); var executed = Count("executed"); var passed = Count("passed"); var failed = Count("failed"); var notExecuted = Count("notExecuted"); foreach (var name in new[] { "error", "timeout", "aborted", "inconclusive", "passedButRunAborted", "notRunnable", "disconnected", "inProgress", "pending" }) if (counters.Attribute(name) is not null && Count(name) != 0) throw new InvalidOperationException("TRX contains an incomplete or unsuccessful execution: " + name); if (expected != ExpectedTests || total != expected || executed != expected || passed != expected || failed != 0 || notExecuted != 0) throw new InvalidOperationException($"Native TRX must prove {expected} total/executed/passed tests, zero failures and zero skips; got {total}/{executed}/{passed}/{failed}/{notExecuted}."); var definitions = new Dictionary(StringComparer.Ordinal); foreach (var unit in root.Element(ns + "TestDefinitions")?.Elements(ns + "UnitTest") ?? []) { var method = unit.Element(ns + "TestMethod") ?? throw new InvalidOperationException("TRX test definition lacks its method identity."); var className = ((string?)method.Attribute("className") ?? "").Split(',')[0].Trim(); var methodName = (string?)method.Attribute("name") ?? ""; var id = (string?)unit.Attribute("id") ?? ""; if (id.Length == 0 || className.Length == 0 || methodName.Length == 0 || !definitions.TryAdd(id, methodName.StartsWith(className + ".", StringComparison.Ordinal) ? methodName : className + "." + methodName)) throw new InvalidOperationException("TRX contains an ambiguous test definition."); } var results = root.Element(ns + "Results")?.Elements(ns + "UnitTestResult").ToArray() ?? []; var executionIds = new HashSet(StringComparer.Ordinal); var testIds = new HashSet(StringComparer.Ordinal); var native = new List(); 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) || !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 (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()); } static void ValidateResult(FullResult result, Payload payload) { if (result.Token != payload.RunToken || !result.Success || result.SourceCommit != payload.SourceCommit || result.ArchiveSha256 != payload.ArchiveSha256 || !Version.TryParse(result.OsVersion, out var version) || version.Major < 14 || result.Architecture != "x86_64" || result.SdkVersion != SdkVersion || result.ExpectedTests != ExpectedTests || result.Total != ExpectedTests || result.Executed != ExpectedTests || result.Passed != ExpectedTests || result.Failed != 0 || result.NotExecuted != 0 || !result.NativeTests.Order().SequenceEqual(RequiredNativeTests.Order()) || result.AudioCodeSignExit != 0 || !Hex(result.TrxSha256, 64) || result.NativeArtifacts.Length != NativeNames.Length || !result.NativeArtifacts.Select(artifact => artifact.Name).Order().SequenceEqual(NativeNames.Order()) || result.NativeArtifacts.Any(artifact => artifact.Architecture != "x86_64" || !Hex(artifact.Sha256, 64)) || result.CompletedUtc < result.StartedUtc || result.CompletedUtc - result.StartedUtc > TimeSpan.FromMinutes(25).Add(TimeSpan.FromSeconds(15)) || result.CompletedUtc > DateTimeOffset.UtcNow.AddSeconds(30) || result.CompletedUtc < DateTimeOffset.UtcNow.AddMinutes(-1) || result.Reason.Length != 0) throw new InvalidOperationException("Native guest receipt does not prove this source, toolchain, signed artifacts and all expected tests."); } static void RequirePlatform(string version, string architecture, string uid) { if (!Version.TryParse(version, out var parsed) || parsed.Major < 14 || architecture != "x86_64" || uid != "0") throw new InvalidOperationException("Native build requires installed macOS 14+/x86_64 running as root."); } static void RequireApfs(string xml) { var elements = ParseXml(xml).Root?.Element("dict")?.Elements().ToArray() ?? []; var type = elements.Select((element, index) => (element, index)).FirstOrDefault(pair => pair.element.Name == "key" && pair.element.Value == "FilesystemType"); if (type.element is null || type.index + 1 >= elements.Length || elements[type.index + 1].Name != "string" || elements[type.index + 1].Value != "apfs") throw new InvalidOperationException("Native build must run from the installed APFS system and owned APFS work volume."); } static void RequireNativeArtifact(string fileOutput, string architecture) { if (!fileOutput.Contains("Mach-O", StringComparison.Ordinal) || !fileOutput.Contains("x86_64", StringComparison.Ordinal) || architecture != "x86_64") throw new InvalidOperationException("Native helper is not a Mach-O executable containing exactly x86_64."); } static void RequireAbsent(string path) { if (Path.Exists(path) || GetAttributesSafe(path)?.HasFlag(FileAttributes.ReparsePoint) == true) throw new InvalidOperationException("Fresh guest execution refuses pre-existing output: " + path); } static void RequireFreshFile(string path, DateTimeOffset started, long maximumBytes = long.MaxValue) { RequireNoLinks(path); var file = new FileInfo(path); if (!file.Exists || file.Length == 0 || file.Length > maximumBytes || file.LastWriteTimeUtc < started.UtcDateTime.AddSeconds(-2)) throw new InvalidOperationException("Expected a fresh, nonempty, bounded output from this execution: " + path); } static void RequireNoLinks(string path) { for (var current = Path.GetFullPath(path); current is not null; current = Path.GetDirectoryName(current)) if (GetAttributesSafe(current)?.HasFlag(FileAttributes.ReparsePoint) == true) throw new InvalidOperationException("Guest ownership/extraction refuses a symbolic link: " + current); } static FileAttributes? GetAttributesSafe(string path) { try { return File.GetAttributes(path); } catch (FileNotFoundException) { return null; } catch (DirectoryNotFoundException) { return null; } } static XDocument ParseXml(string xml) => ParseXml(Encoding.UTF8.GetBytes(xml)); static XDocument ParseXml(byte[] xml) { using var stream = new MemoryStream(xml, writable: false); using var reader = XmlReader.Create(stream, new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, XmlResolver = null, MaxCharactersInDocument = 16 * 1024 * 1024 }); return XDocument.Load(reader); } static bool Hex(string? value, int length) => value is not null && Regex.IsMatch(value, "^[0-9a-f]{" + length + "}$"); static async Task HashFile(string path, bool sha512, CancellationToken cancellation) { using var stream = File.OpenRead(path); var hash = sha512 ? await SHA512.HashDataAsync(stream, cancellation) : await SHA256.HashDataAsync(stream, cancellation); return Convert.ToHexStringLower(hash); } static void Save(string path, object value) { RequireNoLinks(path); var temporary = path + ".tmp"; RequireNoLinks(temporary); File.WriteAllText(temporary, JsonSerializer.Serialize(value, JsonOptions), new UTF8Encoding(false)); File.Move(temporary, path, overwrite: true); } static async Task Command(string executable, string[] arguments, string source, string work, string logs, string label, CancellationToken cancellation, bool requireSuccess) { Console.WriteLine("[native-guest] " + label); using var commandCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellation); var token = commandCancellation.Token; var start = new ProcessStartInfo(executable) { WorkingDirectory = Directory.Exists(source) ? source : work, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false }; foreach (var argument in arguments) start.ArgumentList.Add(argument); start.Environment.Clear(); foreach (var pair in new Dictionary { ["PATH"] = Path.Combine(work, "dotnet") + ":/usr/bin:/bin:/usr/sbin:/sbin", ["TMPDIR"] = Path.Combine(work, "tmp"), ["DOTNET_ROOT"] = Path.Combine(work, "dotnet"), ["DOTNET_CLI_HOME"] = Path.Combine(work, "home"), ["NUGET_PACKAGES"] = Path.Combine(work, "packages"), ["TZ"] = "Europe/Berlin", ["LANG"] = "en_US.UTF-8", ["LC_ALL"] = "en_US.UTF-8", ["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1", ["DOTNET_NOLOGO"] = "1", ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1", ["MSBUILDDISABLENODEREUSE"] = "1" }) start.Environment[pair.Key] = pair.Value; using var process = Process.Start(start) ?? throw new InvalidOperationException("Cannot start guest command: " + label); long capturedBytes = 0; async Task Read(StreamReader reader, string stream) { var captured = new StringBuilder(); var buffer = new char[8192]; using var log = new StreamWriter(Path.Combine(logs, label + "." + stream + ".log"), false, new UTF8Encoding(false)); while (true) { var count = await reader.ReadAsync(buffer.AsMemory(), token); if (count == 0) break; if (Interlocked.Add(ref capturedBytes, Encoding.UTF8.GetByteCount(buffer.AsSpan(0, count))) > MaximumLogBytes) { commandCancellation.Cancel(); throw new InvalidOperationException(label + " exceeded its combined 8-MiB output budget."); } captured.Append(buffer, 0, count); await log.WriteAsync(buffer.AsMemory(0, count), token); await log.FlushAsync(token); } return captured.ToString(); } var stdout = Read(process.StandardOutput, "stdout"); var stderr = Read(process.StandardError, "stderr"); try { await Task.WhenAll(stdout, stderr, process.WaitForExitAsync(token)); var result = new CommandResult(process.ExitCode, await stdout, await stderr); if (requireSuccess && result.ExitCode != 0) throw new InvalidOperationException($"{label} exited {result.ExitCode}: {result.Error[..Math.Min(result.Error.Length, 1500)]}"); return result; } catch { try { if (!process.HasExited) process.Kill(entireProcessTree: true); } catch (InvalidOperationException) { } using var killDeadline = new CancellationTokenSource(TimeSpan.FromSeconds(5)); try { await process.WaitForExitAsync(killDeadline.Token); } catch (OperationCanceledException) { } throw; } } static void ValidateContracts() { var payload = new Payload(new string('a', 32), new string('b', 40), new string('c', 64), SdkVersion, SdkSha512, ExpectedTests); var json = JsonSerializer.Serialize(payload, JsonOptions); ReadPayload(json); Reject(() => ReadPayload(json.Replace(SdkVersion, "10.0.100", StringComparison.Ordinal))); Reject(() => ReadPayload(json.Replace(SdkSha512, new string('d', 128), StringComparison.Ordinal))); Reject(() => ReadPayload(json.Replace("577", "572", StringComparison.Ordinal))); Reject(() => ReadPayload(json.Replace(payload.RunToken, "stale", StringComparison.Ordinal))); Reject(() => ReadPayload(json.Replace(payload.ArchiveSha256, "invalid", StringComparison.Ordinal))); RequirePlatform("14.6.1", "x86_64", "0"); Reject(() => RequirePlatform("13.6.1", "x86_64", "0")); Reject(() => RequirePlatform("14.6.1", "arm64", "0")); Reject(() => RequirePlatform("14.6.1", "x86_64", "501")); RequireApfs("FilesystemTypeapfs"); Reject(() => RequireApfs("FilesystemTypehfs")); RequireNativeArtifact("Mach-O 64-bit executable x86_64", "x86_64"); Reject(() => RequireNativeArtifact("Mach-O universal binary x86_64 arm64", "x86_64 arm64")); Reject(() => RequireNativeArtifact("ELF 64-bit executable x86_64", "x86_64")); using (var archive = FixtureArchive(payload.SourceCommit)) ValidateArchive(archive, payload.SourceCommit); foreach (var path in new[] { "../escape", "/absolute", "safe/../escape", "safe\\escape", "MeetingAssistant/bin/stale" }) Reject(() => { using var archive = FixtureArchive(payload.SourceCommit, path); ValidateArchive(archive, payload.SourceCommit); }); Reject(() => { using var archive = FixtureArchive(payload.SourceCommit, "link", true); ValidateArchive(archive, payload.SourceCommit); }); Reject(() => { using var archive = FixtureArchive(new string('d', 40)); ValidateArchive(archive, payload.SourceCommit); }); var started = DateTimeOffset.UtcNow.AddMinutes(-1); var fixture = FixtureTrx(started); var summary = ValidateTrx(fixture.ToString(), ExpectedTests, started); var plainTrx = Encoding.UTF8.GetBytes(fixture.ToString()); var bomTrx = new byte[] { 0xef, 0xbb, 0xbf }.Concat(plainTrx).ToArray(); ValidateTrx(bomTrx, ExpectedTests, started); if (SHA256.HashData(plainTrx).SequenceEqual(SHA256.HashData(bomTrx))) throw new InvalidOperationException("TRX raw-byte hashing discarded its BOM."); Reject(() => ValidateTrx(fixture.ToString(), ExpectedTests, started.AddMinutes(2))); XNamespace ns = fixture.Root!.Name.Namespace; var skipped = new XDocument(fixture); skipped.Descendants(ns + "UnitTestResult").First().SetAttributeValue("outcome", "NotExecuted"); Reject(() => ValidateTrx(skipped.ToString(), ExpectedTests, started)); var missingNative = new XDocument(fixture); missingNative.Descendants(ns + "TestMethod").First().SetAttributeValue("name", "ManagedReplacement"); Reject(() => ValidateTrx(missingNative.ToString(), ExpectedTests, started)); 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)); var aborted = new XDocument(fixture); aborted.Descendants(ns + "ResultSummary").Single().SetAttributeValue("outcome", "Aborted"); Reject(() => ValidateTrx(aborted.ToString(), ExpectedTests, started)); var artifacts = NativeNames.Select(name => new NativeArtifact(name, new string('d', 64), "x86_64")).ToArray(); var result = new FullResult(payload.RunToken, true, payload.SourceCommit, payload.ArchiveSha256, "14.6.1", "x86_64", SdkVersion, ExpectedTests, summary.Total, summary.Executed, summary.Passed, summary.Failed, summary.NotExecuted, summary.NativeTests, artifacts, 0, new string('e', 64), "", started, DateTimeOffset.UtcNow); ValidateResult(result, payload); foreach (var invalid in new[] { result with { Token = new string('f', 32) }, result with { Success = false }, result with { SourceCommit = new string('f', 40) }, result with { ArchiveSha256 = new string('f', 64) }, result with { NotExecuted = 5 }, result with { AudioCodeSignExit = 1 }, result with { NativeTests = RequiredNativeTests[..4] }, result with { NativeArtifacts = artifacts[..3] }, result with { NativeArtifacts = [artifacts[0] with { Architecture = "arm64" }, .. artifacts[1..]] }, result with { TrxSha256 = "" }, result with { SdkVersion = "10.0.100" }, result with { OsVersion = "13.6.1" }, result with { StartedUtc = started.AddHours(-1) }, result with { StartedUtc = started.AddHours(-1), CompletedUtc = started.AddHours(-1).AddSeconds(1) } }) Reject(() => ValidateResult(invalid, payload)); var temporary = Path.Combine(OperatingSystem.IsMacOS() ? "/private/tmp" : Path.GetTempPath(), "meeting-assistant-guest-validation-" + Guid.NewGuid().ToString("N")); try { RequireAbsent(temporary); Directory.CreateDirectory(temporary); Reject(() => RequireAbsent(temporary)); var file = Path.Combine(temporary, "fresh.trx"); File.WriteAllText(file, "fixture"); RequireFreshFile(file, started); Reject(() => RequireFreshFile(file, started, 1)); File.SetLastWriteTimeUtc(file, started.UtcDateTime.AddMinutes(-5)); Reject(() => RequireFreshFile(file, started)); File.Delete(file); } finally { if (Directory.Exists(temporary)) Directory.Delete(temporary, recursive: true); } } static MemoryStream FixtureArchive(string commit, string? extra = null, bool link = false) { var stream = new MemoryStream(); using (var writer = new TarWriter(stream, TarEntryFormat.Pax, leaveOpen: true)) { writer.WriteEntry(new PaxGlobalExtendedAttributesTarEntry(new Dictionary { ["comment"] = commit })); writer.WriteEntry(new PaxTarEntry(TarEntryType.RegularFile, "MeetingAssistant.Tests/MeetingAssistant.Tests.csproj") { DataStream = new MemoryStream(Encoding.UTF8.GetBytes("")) }); if (extra is not null) { var entry = new PaxTarEntry(link ? TarEntryType.SymbolicLink : TarEntryType.RegularFile, extra); if (link) entry.LinkName = "../outside"; else entry.DataStream = new MemoryStream([1]); writer.WriteEntry(entry); } } stream.Position = 0; return stream; } static XDocument FixtureTrx(DateTimeOffset started) { XNamespace ns = "http://microsoft.com/schemas/VisualStudio/TeamTest/2010"; var definitions = new XElement(ns + "TestDefinitions"); var results = new XElement(ns + "Results"); for (var index = 0; index < ExpectedTests; index++) { var identity = index < RequiredNativeTests.Length ? RequiredNativeTests[index] : "MeetingAssistant.Tests.ManagedTests.Test" + index; var separator = identity.LastIndexOf('.'); definitions.Add(new XElement(ns + "UnitTest", new XAttribute("id", "test-" + index), new XElement(ns + "TestMethod", new XAttribute("className", identity[..separator] + ", MeetingAssistant.Tests"), new XAttribute("name", identity[(separator + 1)..])))); results.Add(new XElement(ns + "UnitTestResult", new XAttribute("testId", "test-" + index), new XAttribute("executionId", "execution-" + index), new XAttribute("outcome", "Passed"))); } return new XDocument(new XElement(ns + "TestRun", new XElement(ns + "Times", new XAttribute("start", started.ToString("O")), new XAttribute("finish", started.AddSeconds(1).ToString("O"))), definitions, results, new XElement(ns + "ResultSummary", new XAttribute("outcome", "Completed"), new XElement(ns + "Counters", new XAttribute("total", ExpectedTests), new XAttribute("executed", ExpectedTests), new XAttribute("passed", ExpectedTests), new XAttribute("failed", 0), new XAttribute("notExecuted", 0))))); } static void Reject(Action action) { try { action(); } catch (InvalidOperationException) { return; } throw new InvalidOperationException("Contract validation accepted an invalid or stale fixture."); } sealed record Payload(string RunToken, string SourceCommit, string ArchiveSha256, string SdkVersion, string SdkSha512, int ExpectedTests); sealed record NativeArtifact(string Name, string Sha256, string Architecture); sealed record TestSummary(int Total, int Executed, int Passed, int Failed, int NotExecuted, string[] NativeTests); sealed record FullResult(string Token, bool Success, string SourceCommit, string ArchiveSha256, string OsVersion, string Architecture, string SdkVersion, int ExpectedTests, int Total, int Executed, int Passed, int Failed, int NotExecuted, string[] NativeTests, NativeArtifact[] NativeArtifacts, int AudioCodeSignExit, string TrxSha256, string Reason, DateTimeOffset StartedUtc, DateTimeOffset CompletedUtc); sealed record CommandResult(int ExitCode, string Output, string Error); }