Files
ShadowsocksX-NG/ShadowsocksX-NG2/Tests/SystemProxy/SystemProxyHelperXPCRoundTripTests.swift
Qiu Yuzhou 94ef128db8 feat(system-proxy): emit privileged helper events to unified log
The root system-proxy helper logged nothing and launchd discards its
stdout/stderr, leaving helper launches, connection refusals, GUI-side
timeouts and root-side failures invisible. Share the minimal unified-log
emission unit (SystemLog) across GUI, wrapper and helper, and add a
dedicated closed helper event enum (lifecycle minimum: listener ready,
connection accepted/rejected with peer pid, typed apply/clear results)
with ADR-0045 privacy splitting. The emission seam is injected at the
helper engine and listener for test doubles. Helper events are
Console-only: no stderr, no agent.log, no diagnostic view or report
surface (ADR-0047).
2026-10-20 15:59:38 +08:00

72 lines
2.9 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
import XCTest
@testable import ShadowsocksX_NG2
/// 真实 XPC 装配层的契约 round-trip:匿名监听器挂真实 listener
/// delegate(注入假特权写入器,不触真实系统设置)+ listenerEndpoint 连接,覆盖
/// fakes 与纯逻辑单测覆盖不到的层:NSXPCInterface 方法签名、客户端签名
/// identifier 校验、Data payload 传输与 XPC 内部队列应答路径。去宿主化
/// (ADR 0021)后连接方是 xctest runner 本进程,delegate 期望身份按本进程
/// 实际签名 identifier 注入,无需 launchd。
/// 注意:NSXPCListener.delegate 是弱引用,delegate 必须由测试强持有。
final class SystemProxyHelperXPCRoundTripTests: XCTestCase {
func testTypedApplyAndClearRoundTripThroughRealXPCConnection() async throws {
let engine = SystemProxyHelperEngine(perform: { request in
switch request {
case .apply: return .applied(.written)
case .clear: return .cleared
}
})
let delegate = SystemProxyHelperListenerDelegate(
engine: engine,
expectedClientIdentifier: try XCTUnwrap(ProcessSigningIdentifier.current()))
let listener = NSXPCListener.anonymous()
listener.delegate = delegate
listener.resume()
defer { listener.invalidate() }
let connection = NSXPCConnection(listenerEndpoint: listener.endpoint)
connection.remoteObjectInterface = NSXPCInterface(with: SystemProxyHelperControlling.self)
connection.resume()
defer { connection.invalidate() }
let proxy = try XCTUnwrap(
connection.remoteObjectProxyWithErrorHandler { (error: Error) in
XCTFail("XPC 错误回调不应触发:\(error)")
} as? SystemProxyHelperControlling)
let configuration = SystemProxyConfiguration(
socks: .init(host: "127.0.0.1", port: 1086),
http: .init(host: "127.0.0.1", port: 1087), https: .init(host: "127.0.0.1", port: 1087),
exceptions: ["localhost"])
let payload = try SystemProxyHelperWire.encodeConfiguration(configuration)
let applyReplied = expectation(description: "apply reply")
var appliedResponse: SystemProxyHelperResponse?
proxy.apply(payload) { data in
do {
appliedResponse = try SystemProxyHelperWire.decode(data)
} catch {
XCTFail("apply 应答无法解码:\(error)")
}
applyReplied.fulfill()
}
await fulfillment(of: [applyReplied], timeout: 10)
XCTAssertEqual(appliedResponse, .applied(.written))
let clearReplied = expectation(description: "clear reply")
var clearedResponse: SystemProxyHelperResponse?
proxy.clear { data in
do {
clearedResponse = try SystemProxyHelperWire.decode(data)
} catch {
XCTFail("clear 应答无法解码:\(error)")
}
clearReplied.fulfill()
}
await fulfillment(of: [clearReplied], timeout: 10)
XCTAssertEqual(clearedResponse, .cleared)
}
}