mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-05 17:04:18 +00:00
refactor(tests): locate app product via bundle ancestor walk, not Bundle.main
测试需要 app 产物(嵌入二进制、打包面断言)时改经 AppArtifact 沿测试包 落盘位置向上查找 ShadowsocksX-NG2.app,覆盖两种布局:TEST_HOST 过渡期 .xctest 嵌于 Contents/PlugIns/,去宿主化后与 .app 平级。接触点: AgentLifecycleTests wrapper、BundleManagedPluginProviderTests 组合根事实 断言、smoke 三处 sslocal/wrapper/plugin 定位、SkeletonTests 改读产物 Info.plist。行为等价(665 单测 + 11 冒烟全绿),为去 TEST_HOST 铺路。
This commit is contained in:
@@ -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)")
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import Foundation
|
||||
|
||||
/// 构建产物定位:沿测试 bundle 落盘位置向上查找 app 产物。覆盖两种布局——
|
||||
/// TEST_HOST 过渡期 `.xctest` 嵌在 app bundle 的 `Contents/PlugIns/` 内,
|
||||
/// 去宿主化后 `.xctest` 与 `.app` 平级同在 `Build/Products/<config>/`。
|
||||
/// 测试需要 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 {}
|
||||
}
|
||||
@@ -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)"))
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user