Files
ShadowsocksX-NG/ShadowsocksX-NG2/Tests/Agent/AgentAuthenticationLogTests.swift
Qiu Yuzhou 1541e97fb3 test(runtime): silence unified-log writes during test runs
Add the SSXNG_SYSTEM_LOG=0 test seam: RuntimeLog.emit and the sslocal
unified-log destination skip their os.Logger writes when it is set,
while stderr lines and the event sink are unaffected. Test execution
no longer floods the unified log store (a full validate run previously
recorded 795 entries under the shared subsystem, 743 from xctest and
52 from test-spawned agents).

The switch is injected by the schemes' test actions only (Run/Profile
keep system logging so debugging from Xcode still shows it), and set
explicitly in the fixtures that spawn the agent/sslocal processes,
including the fixture that strips inherited SSXNG_* path seams. The
main scheme is now declared explicitly to keep the variable out of the
Launch/Profile actions. Document the seam in ADR-0045.
2026-10-20 12:38:21 +08:00

98 lines
4.5 KiB
Swift

import Darwin
import Foundation
import Testing
@testable import ShadowsocksX_NG2
struct AgentAuthenticationLogTests {
@Test func wrapperHidesPasswordsAcrossChunksAndDiscardsOversizedLines() throws {
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
defer { try? FileManager.default.removeItem(at: directory) }
let contract = directory.appendingPathComponent("sslocal-active.json")
var listen = ProxyListeningConfiguration()
listen.socksPort = Int.random(in: 30_000...60_000)
listen.httpPort = listen.socksPort + 1
try RuntimeFileStore(fileURL: contract).write(
SslocalRuntimeDocument(servers: [], listen: listen))
let stub = directory.appendingPathComponent("sslocal-stub")
let script = """
#!/bin/sh
/usr/bin/awk 'BEGIN {
print "authentication rejected, password: secret-one";
printf "password: ";
for (i=0; i<70000; i++) printf "x";
fflush(); system("/bin/sleep 0.05");
print "secret-tail"; print "ordinary event";
fflush(); system("/bin/sleep 0.1");
}'
"""
try script.write(to: stub, atomically: true, encoding: .utf8)
try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: stub.path)
let process = Process()
process.executableURL = AppArtifact.bundleURL.appendingPathComponent(
"Contents/MacOS/ShadowsocksX-NG2Agent")
var environment = ProcessInfo.processInfo.environment
environment["SSXNG_CONTRACT_PATH"] = contract.path
environment["SSXNG_SSLOCAL_PATH"] = stub.path
environment["SSXNG_RUNTIME_DIR"] = directory.path
environment["SSXNG_SYSTEM_LOG"] = "0"
process.environment = environment
try process.run()
defer {
if process.isRunning { kill(process.processIdentifier, SIGKILL) }
process.waitUntilExit()
}
let deadline = Date().addingTimeInterval(5)
while process.isRunning && Date() < deadline { Thread.sleep(forTimeInterval: 0.01) }
#expect(!process.isRunning)
let log = try String(contentsOf: directory.appendingPathComponent("agent.log"), encoding: .utf8)
#expect(!log.contains("secret-one"))
#expect(!log.contains("secret-tail"))
#expect(log.contains("password: [redacted]"))
#expect(log.contains("ordinary event"))
}
}
extension AgentAuthenticationLogTests {
@Test(arguments: [false, true])
func wrapperCreatesRuntimeIndependentlyOfLogs(contractOverride: Bool) throws {
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
defer { try? FileManager.default.removeItem(at: root) }
let paths = ApplicationSupportPaths(rootURL: root)
let process = Process()
process.executableURL = AppArtifact.bundleURL.appendingPathComponent(
"Contents/MacOS/ShadowsocksX-NG2Agent")
var environment = ProcessInfo.processInfo.environment.filter { !$0.key.hasPrefix("SSXNG_") }
// 剥离只针对路径缝;统一日志开关继续显式关闭(与 scheme 测试动作一致)。
environment["SSXNG_SYSTEM_LOG"] = "0"
if contractOverride {
environment["SSXNG_CONTRACT_PATH"] = paths.runtime.fileURL.path
environment["SSXNG_RUNTIME_DIR"] = root.appendingPathComponent("unused").path
} else {
environment["SSXNG_RUNTIME_DIR"] = paths.runtime.directoryURL.path
}
environment["SSXNG_LOG_PATH"] = paths.agentLogURL.path
process.environment = environment
try process.run()
defer {
if process.isRunning { kill(process.processIdentifier, SIGKILL) }
process.waitUntilExit()
}
let deadline = Date().addingTimeInterval(5)
while process.isRunning && Date() < deadline { Thread.sleep(forTimeInterval: 0.01) }
try #require(!process.isRunning)
#expect(process.terminationStatus == 0)
#expect(FileManager.default.fileExists(atPath: paths.runtime.agentLockURL.path))
#expect(!FileManager.default.fileExists(atPath: root.appendingPathComponent("unused").path))
#expect(try String(contentsOf: paths.agentLogURL, encoding: .utf8).contains("contract missing"))
for directory in [root, paths.runtime.directoryURL, paths.logsDirectoryURL] {
let attributes = try FileManager.default.attributesOfItem(atPath: directory.path)
#expect(attributes[.posixPermissions] as? Int == 0o700)
}
for file in [paths.runtime.agentLockURL, paths.agentLogURL] {
let attributes = try FileManager.default.attributesOfItem(atPath: file.path)
#expect(attributes[.posixPermissions] as? Int == 0o600)
}
}
}