Files
Ivan Dergachev be50a45b1b Update brew before the outdated check
brew info never auto-updates, so its API data can be up to 7 days old
and tap clones never refresh. Automatic loads and the reconcile after an
operation run brew update-if-needed, which applies HOMEBREW_NO_AUTO_UPDATE
and the update interval itself. A user's refresh runs brew update.

Fixes #235
2026-09-28 00:16:26 +04:00

80 lines
2.4 KiB
Swift

//
// CommandRunnerDoubles.swift
// BrewServicesTestSupport
//
import BrewCore
import Foundation
/// Injects `BrewLookupError.executableNotFound` through the real repository (no disk, no real `brew`).
public struct MissingBrewExecutableLocator: BrewExecutableLocating {
public init() {}
public func findBrewExecutable() throws -> URL {
throw BrewLookupError.executableNotFound
}
}
/// Per-invocation result for ``MockBrewCommandRunner``.
public enum MockBrewCommandRunnerBehavior: Sendable {
case output(CommandOutput)
case `throw`(any Error & Sendable)
}
/// Maps `brew` argument lists to output or thrown errors — fails fast on unexpected invocations.
public struct MockBrewCommandRunner: BrewCommandRunning {
private let behaviors: [[String]: MockBrewCommandRunnerBehavior]
public init(behaviors: [[String]: MockBrewCommandRunnerBehavior]) {
self.behaviors = behaviors
}
/// Convenience: every invocation returns the given `CommandOutput`.
public init(responses: [[String]: CommandOutput]) {
behaviors = responses.mapValues { .output($0) }
}
public func run(
executableURL _: URL,
arguments: [String],
options _: BrewRunOptions,
) async throws -> CommandOutput {
guard let behavior = behaviors[arguments] else {
throw BrewCommandError.failed(exitCode: 99, stderr: "unmocked: \(arguments.joined(separator: " "))")
}
switch behavior {
case let .output(out):
return out
case let .throw(error):
throw error
}
}
}
/// Returns a different `brew info` JSON payload per invocation (last payload repeats), for two-load tests.
public actor QueuedBrewInfoRunner: BrewCommandRunning {
private let outputs: [CommandOutput]
private var index = 0
public init(infoJSON: [String]) {
outputs = infoJSON.map {
CommandOutput(standardOutput: $0, standardError: "", terminationStatus: 0)
}
}
public func run(
executableURL _: URL,
arguments: [String],
options _: BrewRunOptions,
) async throws -> CommandOutput {
guard arguments.first == "info" else {
return CommandOutput(standardOutput: "", standardError: "", terminationStatus: 0)
}
let output = outputs[min(index, outputs.count - 1)]
if index < outputs.count - 1 {
index += 1
}
return output
}
}