From 56f330a34415156ef7644d5290cc81fd52485607 Mon Sep 17 00:00:00 2001 From: Danny Canter Date: Fri, 7 Nov 2025 02:05:06 -0800 Subject: [PATCH] Integration: Add stdio ingest test Add a test to ingest 500MiB of data across stdout/stderr. Useful to test how fast we can ingest stdio. The timing will always be a tad off as it times between start and wait returning, and there's quite a lot in the way.. but it's a good enough metric. --- Sources/Integration/ContainerTests.swift | 75 ++++++++++++++++++++++++ Sources/Integration/Suite.swift | 1 + 2 files changed, 76 insertions(+) diff --git a/Sources/Integration/ContainerTests.swift b/Sources/Integration/ContainerTests.swift index 36cd447b..89cb96a2 100644 --- a/Sources/Integration/ContainerTests.swift +++ b/Sources/Integration/ContainerTests.swift @@ -64,6 +64,18 @@ extension IntegrationSuite { } } + final class DiscardingWriter: @unchecked Sendable, Writer { + var count: Int = 0 + + func write(_ data: Data) throws { + count += data.count + } + + func close() throws { + return + } + } + final class BufferWriter: Writer { // `data` isn't used concurrently. nonisolated(unsafe) var data = Data() @@ -1027,4 +1039,67 @@ extension IntegrationSuite { try "hello".write(to: dir.appendingPathComponent("hi.txt"), atomically: true, encoding: .utf8) return dir } + + func testLargeStdioOutput() async throws { + let id = "test-large-stdout-stderr-output" + + let bs = try await bootstrap(id) + let container = try LinuxContainer(id, rootfs: bs.rootfs, vmm: bs.vmm) { config in + config.process.arguments = ["/bin/sleep", "1000"] + config.bootlog = bs.bootlog + } + + do { + try await container.create() + try await container.start() + + let stdoutBuffer = DiscardingWriter() + let stderrBuffer = DiscardingWriter() + + let exec = try await container.exec("large-output") { config in + config.arguments = [ + "sh", + "-c", + """ + dd if=/dev/zero bs=1M count=250 status=none && \ + dd if=/dev/zero bs=1M count=250 status=none >&2 + """, + ] + config.stdout = stdoutBuffer + config.stderr = stderrBuffer + } + + let started = CFAbsoluteTimeGetCurrent() + + try await exec.start() + let status = try await exec.wait() + + let lasted = CFAbsoluteTimeGetCurrent() - started + print("Test \(id) finished process ingesting stdio in \(lasted)") + + guard status.exitCode == 0 else { + throw IntegrationError.assert(msg: "exec process status \(status) != 0") + } + + try await exec.delete() + + let expectedSize = 250 * 1024 * 1024 + guard stdoutBuffer.count == expectedSize else { + throw IntegrationError.assert( + msg: "stdout size \(stdoutBuffer.count) != expected \(expectedSize)") + } + + guard stderrBuffer.count == expectedSize else { + throw IntegrationError.assert( + msg: "stderr size \(stderrBuffer.count) != expected \(expectedSize)") + } + + try await container.kill(SIGKILL) + try await container.wait() + try await container.stop() + } catch { + try? await container.stop() + throw error + } + } } diff --git a/Sources/Integration/Suite.swift b/Sources/Integration/Suite.swift index 23d22a8d..7dc6624a 100644 --- a/Sources/Integration/Suite.swift +++ b/Sources/Integration/Suite.swift @@ -295,6 +295,7 @@ struct IntegrationSuite: AsyncParsableCommand { Test("container no serial console", testNoSerialConsole), Test("unix socket into guest", testUnixSocketIntoGuest), Test("container non-closure constructor", testNonClosureConstructor), + Test("container test large stdio ingest", testLargeStdioOutput), // Pods Test("pod single container", testPodSingleContainer),