diff --git a/ShadowsocksX-NG2/Tests/AgentLifecycleTests.swift b/ShadowsocksX-NG2/Tests/AgentLifecycleTests.swift index 372f6f28..b2902245 100644 --- a/ShadowsocksX-NG2/Tests/AgentLifecycleTests.swift +++ b/ShadowsocksX-NG2/Tests/AgentLifecycleTests.swift @@ -32,7 +32,7 @@ final class AgentLifecycleTests: XCTestCase { private var wrapperURL: URL { get throws { - let url = Bundle.main.bundleURL.appendingPathComponent("Contents/MacOS/ShadowsocksX-NG2Agent") + let url = AppArtifact.bundleURL.appendingPathComponent("Contents/MacOS/ShadowsocksX-NG2Agent") return try XCTUnwrap( FileManager.default.fileExists(atPath: url.path) ? url : nil, "wrapper 未嵌入 app bundle(Contents/MacOS/ShadowsocksX-NG2Agent)") diff --git a/ShadowsocksX-NG2/Tests/AppArtifact.swift b/ShadowsocksX-NG2/Tests/AppArtifact.swift new file mode 100644 index 00000000..8191120c --- /dev/null +++ b/ShadowsocksX-NG2/Tests/AppArtifact.swift @@ -0,0 +1,23 @@ +import Foundation + +/// 构建产物定位:沿测试 bundle 落盘位置向上查找 app 产物。覆盖两种布局—— +/// TEST_HOST 过渡期 `.xctest` 嵌在 app bundle 的 `Contents/PlugIns/` 内, +/// 去宿主化后 `.xctest` 与 `.app` 平级同在 `Build/Products//`。 +/// 测试需要 app 产物(嵌入二进制、Info.plist 打包面断言)时一律经此定位, +/// 不得用 `Bundle.main`(去宿主化后它是 xctest runner 的 bundle)。 +enum AppArtifact { + static let bundleURL: URL = { + var directory = Bundle(for: Marker.self).bundleURL + while directory.path != "/" { + directory.deleteLastPathComponent() + if directory.lastPathComponent == "ShadowsocksX-NG2.app" { + return directory + } + } + // 定位失败时返回原目录让用例以「产物缺失」失败并暴露实际路径,不炸进程。 + return Bundle(for: Marker.self).bundleURL + }() + + /// `Bundle(for:)` 需要类对象;marker 只为取得测试 bundle 的落盘位置。 + private final class Marker {} +} diff --git a/ShadowsocksX-NG2/Tests/BundleManagedPluginProviderTests.swift b/ShadowsocksX-NG2/Tests/BundleManagedPluginProviderTests.swift index 1883894d..81bdc922 100644 --- a/ShadowsocksX-NG2/Tests/BundleManagedPluginProviderTests.swift +++ b/ShadowsocksX-NG2/Tests/BundleManagedPluginProviderTests.swift @@ -66,12 +66,14 @@ final class BundleManagedPluginProviderTests: XCTestCase { XCTAssertNil(provider.executablePath(forProgram: "")) } - /// 组合根事实:宿主 app bundle(Bundle.main,生产默认 provider 的取值来源) - /// 内受管插件在位——防止打包回归静默断开激活链路(编辑器显示提供而激活拒绝)。 + /// 组合根事实:app bundle(生产默认 provider 的取值来源,经 AppArtifact 按 + /// 构建产物定位、不依赖测试宿主)内受管插件在位——防止打包回归静默断开激 + /// 活链路(编辑器显示提供而激活拒绝)。 func testHostAppBundleProvidesManagedPlugin() throws { let path = try XCTUnwrap( - BundleManagedPluginProvider().executablePath(forProgram: pluginBinaryName), - "宿主 app bundle 未嵌入受管插件(先跑 fetch-external-binaries.sh)") + BundleManagedPluginProvider(bundleURL: AppArtifact.bundleURL) + .executablePath(forProgram: pluginBinaryName), + "app bundle 未嵌入受管插件(先跑 fetch-external-binaries.sh)") XCTAssertTrue(path.hasSuffix("Contents/Helpers/Plugins/\(pluginBinaryName)")) } diff --git a/ShadowsocksX-NG2/Tests/SkeletonTests.swift b/ShadowsocksX-NG2/Tests/SkeletonTests.swift index 5321b6d8..ff08a600 100644 --- a/ShadowsocksX-NG2/Tests/SkeletonTests.swift +++ b/ShadowsocksX-NG2/Tests/SkeletonTests.swift @@ -4,16 +4,18 @@ import XCTest @MainActor final class SkeletonTests: XCTestCase { - // 以下两个 Bundle.main 断言依赖测试包以 app 为 TEST_HOST 注入(target dependency); - // 若移除该依赖,Bundle.main 不再是 app bundle,断言会失效。 + // app bundle 断言按构建产物定位(AppArtifact),不依赖测试宿主进程;守住 + // 的是打包面:bundle id 与旧版区分、菜单栏形态(LSUIElement)。 - func testAppBundleIDIsDistinctFromLegacy() { - XCTAssertEqual(Bundle.main.bundleIdentifier, "com.qiuyuzhou.ShadowsocksX-NG2") - XCTAssertNotEqual(Bundle.main.bundleIdentifier, "com.qiuyuzhou.ShadowsocksX-NG") + func testAppBundleIDIsDistinctFromLegacy() throws { + let bundle = try XCTUnwrap(Bundle(url: AppArtifact.bundleURL)) + XCTAssertEqual(bundle.bundleIdentifier, "com.qiuyuzhou.ShadowsocksX-NG2") + XCTAssertNotEqual(bundle.bundleIdentifier, "com.qiuyuzhou.ShadowsocksX-NG") } - func testAppIsMenuBarAgent() { - XCTAssertEqual(Bundle.main.object(forInfoDictionaryKey: "LSUIElement") as? Bool, true) + func testAppIsMenuBarAgent() throws { + let bundle = try XCTUnwrap(Bundle(url: AppArtifact.bundleURL)) + XCTAssertEqual(bundle.object(forInfoDictionaryKey: "LSUIElement") as? Bool, true) } } diff --git a/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests+Plugin.swift b/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests+Plugin.swift index 3fbcdcbb..d0a9efd2 100644 --- a/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests+Plugin.swift +++ b/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests+Plugin.swift @@ -11,7 +11,7 @@ extension RealSslocalSmokeTests { /// 连带 sslocal 退出(D10 以 sslocal 信号为准);真实服务器数据面验证属发布 /// 门槛人工项(spec #21 Further Notes #6),在此以进程边界验证。 func testRealSslocalLaunchesManagedV2rayPluginProcess() throws { - let pluginURL = Bundle.main.bundleURL.appendingPathComponent( + let pluginURL = AppArtifact.bundleURL.appendingPathComponent( "Contents/Helpers/Plugins/v2ray-plugin") _ = try XCTUnwrap( FileManager.default.isExecutableFile(atPath: pluginURL.path) ? pluginURL : nil, diff --git a/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests.swift b/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests.swift index 0fcf7a81..9350a533 100644 --- a/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests.swift +++ b/ShadowsocksX-NG2/Tests/Smoke/RealSslocalSmokeTests.swift @@ -32,7 +32,7 @@ final class RealSslocalSmokeTests: XCTestCase { private var sslocalURL: URL { get throws { - let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Helpers/sslocal") + let url = AppArtifact.bundleURL.appendingPathComponent("Contents/Helpers/sslocal") return try XCTUnwrap( FileManager.default.fileExists(atPath: url.path) ? url : nil, "sslocal 未嵌入 app bundle(先跑 fetch-external-binaries.sh)") @@ -72,7 +72,7 @@ final class RealSslocalSmokeTests: XCTestCase { func launchWrapper(_ document: SslocalRuntimeDocument) throws -> Process { try RuntimeFileStore(fileURL: contractURL).write(document) - let wrapperURL = Bundle.main.bundleURL.appendingPathComponent( + let wrapperURL = AppArtifact.bundleURL.appendingPathComponent( "Contents/MacOS/ShadowsocksX-NG2Agent") let process = Process() process.executableURL = wrapperURL diff --git a/ShadowsocksX-NG2/project.yml b/ShadowsocksX-NG2/project.yml index b5fc7688..8a9e3632 100644 --- a/ShadowsocksX-NG2/project.yml +++ b/ShadowsocksX-NG2/project.yml @@ -183,6 +183,8 @@ targets: platform: macOS sources: - path: Tests/Smoke + # app 产物定位 helper(与单测 target 共享,见 Tests/AppArtifact.swift)。 + - path: Tests/AppArtifact.swift settings: base: PRODUCT_BUNDLE_IDENTIFIER: com.qiuyuzhou.ShadowsocksX-NG2.SmokeTests