Forji/integration/forgejo-seed/Sources/DockerExec.swift
Stefan Hausotte 2a679140e6 test: stream seed progress and add a docker exec timeout (#69)
The integration seeder gave no visible progress and could hang
indefinitely. A `docker compose exec` for the admin-user step wedged on
a transient Docker-on-macOS flake, and `DockerExec.run` waited on it
with no timeout, blocking the whole UI test suite forever with no output
(stdout was block-buffered, so the existing phase prints never flushed).

- Line-buffer the seeder's stdout so phase progress streams live instead
  of being dumped all at once when stdout is a pipe.
- Add numbered "[n/7] <phase> (Ns elapsed)" headers for a clear progress
  and timing signal.
- Add a 60s timeout to `docker compose exec` and retry the admin-user
  step, so a hung exec fails fast and recovers instead of wedging the
  suite.

Reviewed-on: https://codeberg.org/secana/Forji/pulls/69
2026-06-09 12:55:45 +02:00

46 lines
1.6 KiB
Swift

import Foundation
enum DockerExec {
static func run(
composeFile: String, service: String,
command: [String],
timeout: TimeInterval = 60,
) throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
process.arguments = [
"docker", "compose", "-f", composeFile,
"exec", "-T", "-u", "git", service,
] + command
let stderrPipe = Pipe()
process.standardError = stderrPipe
process.standardOutput = FileHandle.nullDevice
let didExit = DispatchSemaphore(value: 0)
process.terminationHandler = { _ in didExit.signal() }
try process.run()
// Enforce a timeout so a hung `docker compose exec` (a known Docker-on-macOS flake)
// fails fast and can be retried, instead of blocking the whole seed indefinitely.
if didExit.wait(timeout: .now() + timeout) == .timedOut {
process.terminate()
_ = didExit.wait(timeout: .now() + 5)
throw SeedError.dockerExecTimedOut(
command: command.joined(separator: " "),
seconds: timeout,
)
}
if process.terminationStatus != 0 {
let stderrData = stderrPipe.fileHandleForReading.readDataToEndOfFile()
let stderr = String(data: stderrData, encoding: .utf8) ?? ""
throw SeedError.dockerExecFailed(
command: command.joined(separator: " "),
exitCode: process.terminationStatus,
stderr: stderr,
)
}
}
}