149 lines
5.7 KiB
Swift
149 lines
5.7 KiB
Swift
import Foundation
|
|
import TermindApplication
|
|
import TermindDomain
|
|
import TermindInfrastructure
|
|
import TermindRemoteInfrastructure
|
|
import Testing
|
|
|
|
@Suite
|
|
struct SSHMetricsSamplerTests {
|
|
@Test
|
|
func structuredMetricsIgnoreRemoteShellPreambleAndTrailer() async throws {
|
|
let session = FramedMetricsSession(results: [
|
|
Self.result(framing: Self.primaryPayload),
|
|
Self.result(framing: Self.filesystemPayload),
|
|
Self.result(framing: Self.processPayload),
|
|
Self.result(framing: Self.gpuPayload),
|
|
])
|
|
let sampler = SSHMetricsSampler(connection: session)
|
|
|
|
let sample = try await sampler.sample()
|
|
#expect(sample.uptime == 100)
|
|
#expect(sample.cpu?.first == 100)
|
|
|
|
let filesystems = try #require(try await sampler.filesystems())
|
|
#expect(filesystems.first?.mountPoint == "/")
|
|
#expect(filesystems.first?.total == 2_097_152)
|
|
|
|
let processes = try #require(try await sampler.processes())
|
|
#expect(processes.coreCount == 1)
|
|
#expect(processes.processes.first?.pid == 42)
|
|
|
|
let gpu = try #require(try await sampler.gpu())
|
|
#expect(gpu.devices.first?.id == "GPU-test")
|
|
|
|
let commands = await session.executedCommands
|
|
#expect(commands.count == 4)
|
|
#expect(commands.allSatisfy { $0.contains(Self.begin) && $0.contains(Self.end) })
|
|
#expect(commands.allSatisfy { $0.contains("__termind_metrics_status") })
|
|
}
|
|
|
|
@Test
|
|
func missingTruncatedAndOversizedFramesRemainFailures() async {
|
|
let missing = SSHMetricsSampler(
|
|
connection: FramedMetricsSession(results: [
|
|
SSHExecResult(
|
|
stdout: Self.primaryPayload, stderr: "", exitCode: 0,
|
|
stdoutTruncated: false, stderrTruncated: false)
|
|
]))
|
|
await #expect(throws: MetricsReadError.self) { try await missing.sample() }
|
|
|
|
let truncated = SSHMetricsSampler(
|
|
connection: FramedMetricsSession(results: [
|
|
SSHExecResult(
|
|
stdout: Self.framed(Self.primaryPayload), stderr: "", exitCode: 0,
|
|
stdoutTruncated: true, stderrTruncated: false)
|
|
]))
|
|
await #expect(throws: MetricsReadError.self) { try await truncated.sample() }
|
|
|
|
let oversizedPayload = Self.primaryPayload + String(repeating: "x", count: 262_144)
|
|
let oversized = SSHMetricsSampler(
|
|
connection: FramedMetricsSession(results: [Self.result(framing: oversizedPayload)]))
|
|
await #expect(throws: MetricsReadError.self) { try await oversized.sample() }
|
|
}
|
|
|
|
@Test
|
|
func specialAndNonzeroExitCodesKeepTheirMeaning() async throws {
|
|
let unsupported = SSHMetricsSampler(
|
|
connection: FramedMetricsSession(results: [Self.result(exitCode: 42)]))
|
|
do {
|
|
_ = try await unsupported.sample()
|
|
Issue.record("Expected unsupported metrics")
|
|
} catch MetricsReadError.unsupported {
|
|
} catch {
|
|
Issue.record("Expected MetricsReadError.unsupported, received \(error)")
|
|
}
|
|
|
|
let gpuSession = FramedMetricsSession(results: [
|
|
Self.result(exitCode: 42), Self.result(exitCode: 6), Self.result(exitCode: 1),
|
|
])
|
|
let gpuSampler = SSHMetricsSampler(connection: gpuSession)
|
|
#expect(try await gpuSampler.gpu() == nil)
|
|
#expect(try await gpuSampler.gpu() == nil)
|
|
await #expect(throws: MetricsReadError.self) { try await gpuSampler.gpu() }
|
|
}
|
|
|
|
private static let begin = "__TERMIND_METRICS_BEGIN__"
|
|
private static let end = "__TERMIND_METRICS_END__"
|
|
|
|
private static func framed(_ payload: String) -> String {
|
|
"\u{1B}]7;file://instance/home/ubuntu\u{07}" + begin + "\n" + payload + "\n" + end
|
|
+ "\n\u{1B}]7;file://instance/home/ubuntu\u{07}"
|
|
}
|
|
|
|
private static func result(framing payload: String = "", exitCode: Int32 = 0) -> SSHExecResult {
|
|
SSHExecResult(
|
|
stdout: framed(payload), stderr: "", exitCode: exitCode,
|
|
stdoutTruncated: false, stderrTruncated: false)
|
|
}
|
|
|
|
private static let primaryPayload = """
|
|
__TERMIND_UPTIME__
|
|
100.0 200.0
|
|
__TERMIND_CPU__
|
|
cpu 100 0 100 800 0 0 0 0
|
|
cpu0 100 0 100 800 0 0 0 0
|
|
"""
|
|
|
|
private static let filesystemPayload = """
|
|
24 1 8:1 / / rw - ext4 /dev/sda1 rw
|
|
__TERMIND_DF__
|
|
Filesystem Type 1024-blocks Used Available Capacity Mounted on
|
|
/dev/sda1 ext4 2048 1024 1024 50% /
|
|
"""
|
|
|
|
private static let processPayload = """
|
|
cpu 100 0 0 900 0 0 0 0
|
|
cpu0 100 0 0 900 0 0 0 0
|
|
pagesize 4096
|
|
__TERMIND_PROCESSES__
|
|
42 (worker) R 0 0 0 0 0 0 0 0 0 0 10 5 0 0 0 0 0 0 100 0 25
|
|
"""
|
|
|
|
private static let gpuPayload = """
|
|
<?xml version="1.0"?>
|
|
<nvidia_smi_log><gpu><uuid>GPU-test</uuid><product_name>Test GPU</product_name></gpu></nvidia_smi_log>
|
|
"""
|
|
}
|
|
|
|
private actor FramedMetricsSession: SSHConnectionProtocol {
|
|
private var results: [SSHExecResult]
|
|
private(set) var executedCommands: [String] = []
|
|
|
|
init(results: [SSHExecResult]) { self.results = results }
|
|
|
|
func connect(using _: SSHConnectionConfiguration) async throws {}
|
|
|
|
func execute(
|
|
_ command: String, workingDirectory _: String?, stdin _: SSHCommandInput?, timeout _: TimeInterval,
|
|
outputLimit _: Int, onOutput _: SSHExecOutputHandler?
|
|
) async throws -> SSHExecResult {
|
|
executedCommands.append(command)
|
|
guard !results.isEmpty else { throw MetricsReadError.output }
|
|
return results.removeFirst()
|
|
}
|
|
|
|
func openSFTPChannel() async throws -> any SFTPChannelProtocol { throw SFTPError.notConnected }
|
|
func disconnect() async {}
|
|
}
|