mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-17 08:02:12 -06:00
ab5e10fd17
* fix(macos): tolerate first-run state migrations during gateway start * fix(macos): tolerate loaded-host version probes
141 lines
5.6 KiB
Swift
141 lines
5.6 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import OpenClaw
|
|
|
|
struct RuntimeLocatorTests {
|
|
private func makeTempExecutable(contents: String) throws -> URL {
|
|
let dir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
|
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
try FileManager().createDirectory(at: dir, withIntermediateDirectories: true)
|
|
let path = dir.appendingPathComponent("node")
|
|
try contents.write(to: path, atomically: true, encoding: .utf8)
|
|
try FileManager().setAttributes([.posixPermissions: 0o755], ofItemAtPath: path.path)
|
|
return path
|
|
}
|
|
|
|
@Test func `resolve succeeds with valid node`() async throws {
|
|
let script = """
|
|
#!/bin/sh
|
|
echo v22.22.3
|
|
"""
|
|
let node = try self.makeTempExecutable(contents: script)
|
|
let result = await RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
|
guard case let .success(res) = result else {
|
|
Issue.record("Expected success, got \(result)")
|
|
return
|
|
}
|
|
#expect(res.path == node.path)
|
|
#expect(res.version == RuntimeVersion(major: 22, minor: 22, patch: 3))
|
|
}
|
|
|
|
@Test func `runtime version probe tolerates loaded host delay`() async throws {
|
|
let script = """
|
|
#!/bin/sh
|
|
/bin/sleep 2.1
|
|
echo v22.22.3
|
|
"""
|
|
let node = try self.makeTempExecutable(contents: script)
|
|
let result = await RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
|
guard case let .success(resolution) = result else {
|
|
Issue.record("Expected delayed version probe to succeed, got \(result)")
|
|
return
|
|
}
|
|
#expect(resolution.version == RuntimeVersion(major: 22, minor: 22, patch: 3))
|
|
}
|
|
|
|
@Test func `resolve fails on boundary below minimum`() async throws {
|
|
let script = """
|
|
#!/bin/sh
|
|
echo v22.22.2
|
|
"""
|
|
let node = try self.makeTempExecutable(contents: script)
|
|
let result = await RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
|
guard case let .failure(.unsupported(_, found, path, _)) = result else {
|
|
Issue.record("Expected unsupported error, got \(result)")
|
|
return
|
|
}
|
|
#expect(found == RuntimeVersion(major: 22, minor: 22, patch: 2))
|
|
#expect(path == node.path)
|
|
}
|
|
|
|
@Test func `resolve rejects node 23`() async throws {
|
|
let script = """
|
|
#!/bin/sh
|
|
echo v23.11.0
|
|
"""
|
|
let node = try self.makeTempExecutable(contents: script)
|
|
let result = await RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
|
guard case let .failure(.unsupported(_, found, path, _)) = result else {
|
|
Issue.record("Expected unsupported error, got \(result)")
|
|
return
|
|
}
|
|
#expect(found == RuntimeVersion(major: 23, minor: 11, patch: 0))
|
|
#expect(path == node.path)
|
|
}
|
|
|
|
@Test(arguments: [
|
|
("22.22.2", false),
|
|
("22.22.3", true),
|
|
("23.11.0", false),
|
|
("24.14.1", false),
|
|
("24.15.0", true),
|
|
("25.8.1", false),
|
|
("25.9.0", true),
|
|
("26.0.0", true),
|
|
])
|
|
func `node support matches the core runtime contract`(version: String, supported: Bool) throws {
|
|
let parsed = try #require(RuntimeVersion.from(string: version))
|
|
#expect(RuntimeLocator.isSupportedNodeVersion(parsed) == supported)
|
|
}
|
|
|
|
@Test func `resolve fails when too old`() async throws {
|
|
let script = """
|
|
#!/bin/sh
|
|
echo v18.2.0
|
|
"""
|
|
let node = try self.makeTempExecutable(contents: script)
|
|
let result = await RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
|
guard case let .failure(.unsupported(_, found, path, _)) = result else {
|
|
Issue.record("Expected unsupported error, got \(result)")
|
|
return
|
|
}
|
|
#expect(found == RuntimeVersion(major: 18, minor: 2, patch: 0))
|
|
#expect(path == node.path)
|
|
}
|
|
|
|
@Test func `resolve fails when version unparsable`() async throws {
|
|
let script = """
|
|
#!/bin/sh
|
|
echo node-version:unknown
|
|
"""
|
|
let node = try self.makeTempExecutable(contents: script)
|
|
let result = await RuntimeLocator.resolve(searchPaths: [node.deletingLastPathComponent().path])
|
|
guard case let .failure(.versionParse(_, raw, path, _)) = result else {
|
|
Issue.record("Expected versionParse error, got \(result)")
|
|
return
|
|
}
|
|
#expect(raw.contains("unknown"))
|
|
#expect(path == node.path)
|
|
}
|
|
|
|
@Test func `describe failure includes paths`() {
|
|
let msg = RuntimeLocator.describeFailure(.notFound(searchPaths: ["/tmp/a", "/tmp/b"]))
|
|
#expect(msg.contains("Node >=22.22.3 <23, >=24.15.0 <25, or >=25.9.0"))
|
|
#expect(msg.contains("PATH searched: /tmp/a:/tmp/b"))
|
|
|
|
let parseMsg = RuntimeLocator.describeFailure(
|
|
.versionParse(
|
|
kind: .node,
|
|
raw: "garbage",
|
|
path: "/usr/local/bin/node",
|
|
searchPaths: ["/usr/local/bin"]))
|
|
#expect(parseMsg.contains("Node >=22.22.3 <23, >=24.15.0 <25, or >=25.9.0"))
|
|
}
|
|
|
|
@Test func `runtime version parses with leading V and metadata`() {
|
|
#expect(RuntimeVersion.from(string: "v22.1.3") == RuntimeVersion(major: 22, minor: 1, patch: 3))
|
|
#expect(RuntimeVersion.from(string: "node 22.3.0-alpha.1") == RuntimeVersion(major: 22, minor: 3, patch: 0))
|
|
#expect(RuntimeVersion.from(string: "bogus") == nil)
|
|
}
|
|
}
|