mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-02 16:53:45 +00:00
test: 拆分运行时测试类并缩短健康等待窗口
This commit is contained in:
@@ -37,6 +37,9 @@ extension ProxyRuntimeControllerTests {
|
||||
return String(bytes: data, encoding: .utf8) ?? ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class CustomRuleControllerTests: ProxyRuntimeControllerTests {
|
||||
/// 规则内容变化重编译 ACL 并按完整重启路径生效。
|
||||
func testUpdateCustomRulesRecompilesACLAndRestarts() async throws {
|
||||
let seeded = try makeSeededCatalog()
|
||||
|
||||
@@ -2,7 +2,7 @@ import XCTest
|
||||
|
||||
@testable import ShadowsocksX_NG2
|
||||
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class CustomRuleDeletionControllerTests: ProxyRuntimeControllerTests {
|
||||
func testDeletionWhileOffPersistsBatchAndDisabledOrphansWithoutStarting() async throws {
|
||||
let (store, _) = try makeCustomRuleStore()
|
||||
let rules = [
|
||||
|
||||
@@ -4,7 +4,7 @@ import XCTest
|
||||
|
||||
/// Verify the editor's public commands against the production commit adapter,
|
||||
/// using the existing hostless runtime and system-proxy fixtures.
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class CustomRuleEditingControllerTests: ProxyRuntimeControllerTests {
|
||||
func testEditorSaveWhileOffPersistsWithoutStartingRuntime() async throws {
|
||||
let (store, _) = try makeCustomRuleStore()
|
||||
let controller = makeControllerWithCustomRules(
|
||||
|
||||
@@ -5,7 +5,7 @@ import XCTest
|
||||
/// Mode restoration and commands share the Domain availability policy without
|
||||
/// starting a real runtime or touching host proxy settings.
|
||||
@MainActor
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class ProxyRuntimeControllerModeTests: ProxyRuntimeControllerTests {
|
||||
func testRuntimeFactsProjectControllerStateAtStableSeam() async {
|
||||
let controller = makeController(probe: ProxyRuntimeFixture.FakeProbe.reachable())
|
||||
|
||||
@@ -26,7 +26,7 @@ extension ProxyRuntimeControllerTests {
|
||||
// 旧收敛复活;restore 先查时效再动持久层)
|
||||
|
||||
@MainActor
|
||||
extension ProxyRuntimeControllerTests {
|
||||
extension ProxyRuntimeControllerModeTests {
|
||||
/// agent-off 落在 mode 切换的健康门 await 窗口内:票据查全四事实 →
|
||||
/// superseded,不触发 restore 重注册,用户的 mode 意图保留在持久层与内存。
|
||||
func testAgentOffDuringModeTransitionHealthGateSupersedesWithoutRestore() async throws {
|
||||
|
||||
@@ -5,7 +5,7 @@ import XCTest
|
||||
|
||||
/// Agent 开关与系统代理开关的拆分语义(issue #60):与既有开关用例同文件
|
||||
/// 追加(Xcode 测试扫描限制),扩展持有独立用例组。
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class ProxyRuntimeAgentAndSystemProxyTests: ProxyRuntimeControllerTests {
|
||||
func testEnableWithoutActiveTargetDeploysEmptyServerListening() async throws {
|
||||
_ = try makeSeededCatalog()
|
||||
let controller = makeController(
|
||||
@@ -195,6 +195,9 @@ extension ProxyRuntimeControllerTests {
|
||||
}
|
||||
/// 关闭 agent 的级联(issue #71):系统代理意图仍开启时一并持久化为关闭,
|
||||
/// 先请求清理、再停止监听;之后重启 agent 不会恢复系统代理意图。
|
||||
}
|
||||
|
||||
extension ProxyRuntimeAgentAndSystemProxyTests {
|
||||
func testAgentOffClearsSystemProxyBeforeStoppingListeningAndCascadesIntentOff() async throws {
|
||||
let seeded = try makeSeededCatalog()
|
||||
let settingsStore = InMemoryProxySettingsStore()
|
||||
@@ -450,7 +453,7 @@ extension ProxyRuntimeControllerTests {
|
||||
}
|
||||
}
|
||||
|
||||
extension ProxyRuntimeControllerTests {
|
||||
extension ProxyRuntimeAgentAndSystemProxyTests {
|
||||
/// 意图已开启时的开启命令是失败态的显式重试入口(issue #60):无需先关再开。
|
||||
func testAgentEnableCommandRetriesConvergenceWhileIntentAlreadyOn() async throws {
|
||||
let seeded = try makeSeededCatalog()
|
||||
|
||||
@@ -4,7 +4,7 @@ import XCTest
|
||||
|
||||
/// 目录提交 → 运行时收敛语义(D3/D5 与 CONTEXT.md「有效值未变的提交不做
|
||||
/// 运行时收敛」):与主测试类同构扩展,按既有模式独立成文件。
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class ProxyRuntimeCatalogConvergenceTests: ProxyRuntimeControllerTests {
|
||||
// MARK: 目录重展开消费(D3/D5)
|
||||
|
||||
/// 模拟 wrapper 在跑(pid 指向本测试进程,kill(pid, 0) 判活通过)。
|
||||
|
||||
@@ -3,7 +3,7 @@ import XCTest
|
||||
|
||||
@testable import ShadowsocksX_NG2
|
||||
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class ProxyRuntimeFirewallTests: ProxyRuntimeControllerTests {
|
||||
func testLocalhostModeNeverQueriesApplicationFirewall() async throws {
|
||||
let seeded = try makeSeededCatalog()
|
||||
let firewall = ProxyRuntimeFixture.FakeFirewallChecker(.blocked)
|
||||
|
||||
@@ -5,7 +5,7 @@ import XCTest
|
||||
|
||||
/// 全局模式以 ACL 实现(issue #62):proxy_all + 固定本地绕过;无活动服务器
|
||||
/// 时 agent 仍监听、系统代理保持待应用;ACL 变化完整重启并失败回滚。
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class ProxyRuntimeGlobalModeTests: ProxyRuntimeControllerTests {
|
||||
func testGlobalModeDeploysProxyAllACLAndProjectsSOCKSProxyWithFixedExceptions() async throws {
|
||||
let seeded = try makeSeededCatalog()
|
||||
let settingsStore = InMemoryProxySettingsStore()
|
||||
|
||||
@@ -3,7 +3,7 @@ import XCTest
|
||||
|
||||
@testable import ShadowsocksX_NG2
|
||||
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class ProxyRuntimeModeCommandTests: ProxyRuntimeControllerTests {
|
||||
func testSetProxyModePersistsTheChoiceAndARestoreRestoresIt() async throws {
|
||||
let settingsStore = InMemoryProxySettingsStore()
|
||||
let controller = makeController(
|
||||
|
||||
@@ -3,7 +3,7 @@ import XCTest
|
||||
|
||||
@testable import ShadowsocksX_NG2
|
||||
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class ProxyRuntimeResyncAndSettingsTests: ProxyRuntimeControllerTests {
|
||||
// MARK: GUI 重启重同步(D5)
|
||||
|
||||
func testResyncWithRegisteredAgentAndMatchingContractSkipsRewrite() async throws {
|
||||
|
||||
@@ -7,7 +7,7 @@ import XCTest
|
||||
/// agent 开关与系统代理开关的独立语义、目录重展开消费与 GUI 重同步重合(不
|
||||
/// 触真实 SMAppService/launchd/SystemConfiguration)。
|
||||
@MainActor
|
||||
final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
class ProxyRuntimeControllerTests: XCTestCase {
|
||||
var runtime: ProxyRuntimeFixture.TemporaryRuntime!
|
||||
var catalogFileURL: URL!
|
||||
var activationFileURL: URL!
|
||||
@@ -24,6 +24,10 @@ final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
private(set) var records: [(pid: Int32, signal: Int32)] = []
|
||||
/// SIGUSR1 送达后模拟 wrapper 热重载刷新回执。
|
||||
var reloadReceipt: (() -> Void)?
|
||||
/// wrapper 进程存活位:生产中注销 LaunchAgent 后 wrapper 随 launchd 停止
|
||||
/// 消亡,pid 文件留存至 deleteRuntimeFiles、死亡靠 kill(_, 0) 探测;
|
||||
/// 重注册即拉起新进程。不置死则停止协议会空等退出超时上限。
|
||||
private var wrapperAlive = true
|
||||
|
||||
var signalsSent: [(pid: Int32, signal: Int32)] {
|
||||
lock.lock()
|
||||
@@ -37,6 +41,20 @@ final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
/// 注销 LaunchAgent:对应 wrapper 进程消亡。
|
||||
func terminateWrapper() {
|
||||
lock.lock()
|
||||
wrapperAlive = false
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
/// 重新注册:拉起新 wrapper 进程。
|
||||
func relaunchWrapper() {
|
||||
lock.lock()
|
||||
wrapperAlive = true
|
||||
lock.unlock()
|
||||
}
|
||||
|
||||
func send(_ pid: Int32, _ signal: Int32) -> Int32 {
|
||||
record(pid, signal)
|
||||
if signal == SIGUSR1 {
|
||||
@@ -45,7 +63,10 @@ final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
// kill(_, 0) 判活语义:只对夹具约定的存活 pid(42 与本测试进程)报告
|
||||
// 在跑;陈旧残留 pid 返回 ESRCH,停止协议才不必空等退出超时上限。
|
||||
if signal == 0 {
|
||||
return pid == 42 || pid == ProcessInfo.processInfo.processIdentifier
|
||||
lock.lock()
|
||||
let alive = wrapperAlive
|
||||
lock.unlock()
|
||||
return (pid == 42 && alive) || pid == ProcessInfo.processInfo.processIdentifier
|
||||
? 0 : Int32(ESRCH)
|
||||
}
|
||||
return 0
|
||||
@@ -63,6 +84,8 @@ final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
systemProxyHelper = ProxyRuntimeFixture.FakeSystemProxyHelperService()
|
||||
systemProxyNetworkChangeMonitor = ProxyRuntimeFixture.FakeSystemProxyNetworkChangeMonitor()
|
||||
signals = SignalRecorder()
|
||||
// 注销即 wrapper 消亡:停止协议的退出等待据 kill(_, 0) 放行,不空等超时。
|
||||
agent.onUnregister = { [weak self] in self?.signals?.terminateWrapper() }
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
@@ -102,7 +125,9 @@ final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
) -> ProxyRuntimeController {
|
||||
agent.setStatus(agentStatus)
|
||||
let runtimeFileStore = RuntimeFileStore(fileURL: runtime.contract)
|
||||
agent.onRegister = { [runtimeFileStore] in
|
||||
agent.onRegister = { [runtimeFileStore, signals] in
|
||||
// 重注册=拉起新 wrapper 进程(注销时已随 LaunchAgent 置死)。
|
||||
signals?.relaunchWrapper()
|
||||
guard let document = runtimeFileStore.loadDocument() else { return }
|
||||
try? runtimeFileStore.writeRuntimeReceipt(for: document, processID: 42)
|
||||
}
|
||||
@@ -145,6 +170,10 @@ final class ProxyRuntimeControllerTests: XCTestCase {
|
||||
processIsAlive: processIsAlive)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Concrete suite; the shared fixture must not contain inherited test methods.
|
||||
final class ProxyRuntimeControllerCoreTests: ProxyRuntimeControllerTests {
|
||||
// MARK: Agent 开关与首次默认
|
||||
|
||||
func testActivateWhileAgentOffPublishesActiveTargetIDWithoutDeploying() async throws {
|
||||
|
||||
@@ -2,7 +2,7 @@ import XCTest
|
||||
|
||||
@testable import ShadowsocksX_NG2
|
||||
|
||||
extension ProxyRuntimeControllerTests {
|
||||
final class RuleEnablementControllerTests: ProxyRuntimeControllerTests {
|
||||
func testRapidRuleSavesCoalesceIntoOneLatestACLDeployment() async throws {
|
||||
let seeded = try makeSeededCatalog()
|
||||
let (store, _) = try makeCustomRuleStore()
|
||||
@@ -38,7 +38,7 @@ extension ProxyRuntimeControllerTests {
|
||||
store: store,
|
||||
settings: ProxySettings(
|
||||
listen: ActivationFixture.listen, preferredMode: .rule, agentEnabled: true),
|
||||
proxyMode: .rule, launchHealthTimeoutSeconds: 2)
|
||||
proxyMode: .rule, launchHealthTimeoutSeconds: 0.3)
|
||||
try await controller.activate(seeded.server)
|
||||
let runtimeStore = RuntimeFileStore(fileURL: runtime.contract)
|
||||
let started = expectation(description: "first deployment awaiting receipt")
|
||||
@@ -238,6 +238,9 @@ extension ProxyRuntimeControllerTests {
|
||||
XCTAssertEqual(agent.registerCount, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension RuleEnablementControllerTests {
|
||||
func testAgentOffDuringRecoveryCannotBeOverwrittenByOldRuleTransaction() async throws {
|
||||
try await interruptRuleRecovery(switchMode: false)
|
||||
}
|
||||
@@ -256,7 +259,7 @@ extension ProxyRuntimeControllerTests {
|
||||
ruleDefaultAction: .proxyWhenUnmatched, agentEnabled: true)
|
||||
let controller = makeControllerWithCustomRules(
|
||||
store: store, settings: settings, proxyMode: .rule,
|
||||
launchHealthTimeoutSeconds: 1)
|
||||
launchHealthTimeoutSeconds: 0.2)
|
||||
try await controller.activate(seeded.server)
|
||||
let runtimeStore = RuntimeFileStore(fileURL: runtime.contract)
|
||||
let recoveryStarted = expectation(description: "recovery awaiting health")
|
||||
|
||||
Reference in New Issue
Block a user