Files
ShadowsocksX-NG/ShadowsocksX-NG2/Tests/Runtime/RuntimeFileStoreTransactionTests.swift
T
Qiu Yuzhou 20a62117f2 refactor(tests): 按功能领域重组 Tests/ 目录
Tests/ 根层原有 138 个平铺测试文件,无法从目录读出测试覆盖的功能领域。

按功能领域重组为 17 个目录(Catalog/Workflow 与 Rules/Custom 两处嵌套,分别
对应 App/Application/CatalogWorkflow 与 Domain/Rules/Custom):Architecture /
Support / Agent / Activation / Catalog / Subscription / Rules / ProxyRuntime /
ProxyControl / Runtime / Settings / SystemProxy / Plugin / Diagnostics /
Credentials / Workspace / Servers;Smoke/ 保持独立 target 不动。138 个移动中
132 个是 100% 相似度的纯重命名,其余 6 个在重命名之上带路径反推改写(见下)。

本次不是纯搬移:测试代码比生产代码多一层位置耦合,有 7 处用固定层数的
deletingLastPathComponent() 从 #filePath 反推仓库路径(都假定文件就在 Tests/
根层)。这 7 处改走新增的 Tests/Support/TestSourceTree.swift——从调用点沿父
目录上溯找到含 project.yml 的目录,因此不再依赖父目录层数,以后移动测试文件
也不会静默指错。

配套改动:

- 新增 Tests 布局守卫 TestsLayoutTests,断言 Tests/ 根层无平铺 .swift。比
  Domain 那条弱一档——根层的 Info.plist(info.path 生成物)与
  Fixtures/RuleSnapshots 下的 JSON(folder resource)都必须保留,不能用
  「树下只有 .swift」覆盖。
- project.yml:Smoke target 按文件引用的 Tests/AppArtifact.swift 改指
  Tests/Support/;两处注释里写死的 Tests/SkeletonTests.swift 随移动更新。
  excludes 的裸名 "Smoke" / "Fixtures" 仍匹配 Tests/ 直属子目录,无需改动。
- README 的 Tests 条目展开为目录说明,并修掉 Domain 条目里写死的
  Tests/SkeletonTests.swift。
- 顺带删去 DomainLayoutTests 文档注释里「整理前 62 个文件」这类易漂移的数字。

验证:xcodegen generate 后单测 target 为 139 个源文件(全部非 Smoke 文件,0 个
Smoke 文件混入)且仍以 Fixtures/RuleSnapshots 为资源,Smoke target 为 8 个
Smoke 文件加 AppArtifact;依赖路径反推的 6 个测试类全绿(路径指错即失败);
task format 未改写任何被移动文件;task lint 与整理前同为 21 条内容型 warning;
build 0 error;task test 1025 用例全通过(整理前 1024,+1 为新守卫);
TestsLayoutTests 经变异验证(根层投放探针即失败);packaging-gate ALL CHECKS
PASSED。
2026-10-05 20:37:35 +08:00

162 lines
6.8 KiB
Swift

import XCTest
@testable import ShadowsocksX_NG2
/// ACL 部署事务通过临时目录与原子写入故障 seam 验证。
final class RuntimeFileStoreTransactionTests: XCTestCase {
private var runtime: ProxyRuntimeFixture.TemporaryRuntime!
private var store: RuntimeFileStore!
override func setUpWithError() throws {
runtime = ProxyRuntimeFixture.makeTemporaryRuntime()
store = RuntimeFileStore(fileURL: runtime.contract)
}
override func tearDownWithError() throws {
try? FileManager.default.removeItem(at: runtime.directory)
}
func testDecodedDocumentContractFailurePreservesExistingVariant() throws {
let original = SslocalRuntimeDocument(
servers: [], listen: SslocalListenSettings(), acl: .direct(at: store.aclFileURL))
try store.write(original)
let decoded = try XCTUnwrap(store.loadDocument())
let variant = store.aclVariantFileURL(summary: "direct")
let bytes = try Data(contentsOf: variant)
let failing = RuntimeFileStore(fileURL: runtime.contract) { _, _ in
throw NSError(domain: "contract", code: 1)
}
XCTAssertThrowsError(try failing.write(decoded))
XCTAssertEqual(try Data(contentsOf: variant), bytes)
XCTAssertEqual(store.loadDocument(), original)
}
func testUnchangedACLFailureDoesNotRewriteVariantOrLink() throws {
let document = SslocalRuntimeDocument(
servers: [], listen: SslocalListenSettings(), acl: .direct(at: store.aclFileURL))
try store.write(document)
let variant = store.aclVariantFileURL(summary: "direct")
let bytes = try Data(contentsOf: variant)
let linkNumber =
try FileManager.default.attributesOfItem(atPath: store.aclFileURL.path)[.systemFileNumber]
as? NSNumber
var touched: [URL] = []
let failing = RuntimeFileStore(fileURL: runtime.contract) { _, url in
touched.append(url)
throw NSError(domain: "contract", code: 1)
}
XCTAssertThrowsError(try failing.write(document))
XCTAssertEqual(touched, [runtime.contract])
XCTAssertEqual(try Data(contentsOf: variant), bytes)
XCTAssertEqual(
try FileManager.default.attributesOfItem(atPath: store.aclFileURL.path)[.systemFileNumber]
as? NSNumber, linkNumber)
}
func testLinkRestoreFailureStillRestoresVariant() throws {
let old = SslocalRuntimeDocument(
servers: [], listen: SslocalListenSettings(), acl: .global(at: store.aclFileURL))
try store.write(old)
let variant = store.aclVariantFileURL(summary: "global")
let oldBytes = try Data(contentsOf: variant)
try store.write(old.replacingACL(.direct(at: store.aclFileURL)))
let failing = RuntimeFileStore(fileURL: runtime.contract) { data, url in
if url == self.runtime.contract {
try FileManager.default.removeItem(at: self.store.aclFileURL)
try FileManager.default.createDirectory(
at: self.store.aclFileURL, withIntermediateDirectories: false)
throw NSError(domain: "contract", code: 1)
}
try AtomicFileWriter.write(data, to: url)
}
let changed = old.replacingACL(
ProxyACLDocument(
path: store.aclFileURL.path, summary: "global",
content: "[proxy_all]\n[bypass_list]\nexample.com\n"))
XCTAssertThrowsError(try failing.write(changed)) { error in
guard case RuntimeFileStore.PersistenceError.rollbackFailed = error else {
return XCTFail("Expected explicit link rollback failure: \(error)")
}
}
XCTAssertEqual(try Data(contentsOf: variant), oldBytes)
}
func testDecodedDocumentRejectsMissingOrSymlinkVariant() throws {
let document = SslocalRuntimeDocument(
servers: [], listen: SslocalListenSettings(), acl: .direct(at: store.aclFileURL))
try store.write(document)
let decoded = try XCTUnwrap(store.loadDocument())
let before = try Data(contentsOf: runtime.contract)
let variant = store.aclVariantFileURL(summary: "direct")
try FileManager.default.removeItem(at: variant)
XCTAssertThrowsError(try store.write(decoded))
try FileManager.default.createSymbolicLink(
atPath: variant.path, withDestinationPath: runtime.contract.path)
XCTAssertThrowsError(try store.write(decoded))
XCTAssertEqual(try Data(contentsOf: runtime.contract), before)
}
func testRollbackFailureStillRestoresOtherChangedResources() throws {
let old = SslocalRuntimeDocument(
servers: [], listen: SslocalListenSettings(), acl: .direct(at: store.aclFileURL))
try store.write(old)
let global = SslocalRuntimeDocument(
servers: [], listen: SslocalListenSettings(), acl: .global(at: store.aclFileURL))
try store.write(global)
try store.write(old)
let variant = store.aclVariantFileURL(summary: "global")
var writes = 0
let failing = RuntimeFileStore(fileURL: runtime.contract) { data, url in
if url == self.runtime.contract { throw NSError(domain: "contract", code: 1) }
if url == variant {
writes += 1
if writes == 2 { throw NSError(domain: "restore", code: 2) }
}
try AtomicFileWriter.write(data, to: url)
}
let changed = SslocalRuntimeDocument(
servers: [], listen: SslocalListenSettings(),
acl: ProxyACLDocument(
path: store.aclFileURL.path, summary: "global",
content: "[proxy_all]\n[bypass_list]\n127.0.0.1\n"))
XCTAssertThrowsError(try failing.write(changed)) { error in
guard case RuntimeFileStore.PersistenceError.rollbackFailed = error else {
return XCTFail("Expected explicit rollback failure: \(error)")
}
}
XCTAssertEqual(
try FileManager.default.destinationOfSymbolicLink(atPath: store.aclFileURL.path),
"acl-direct.ini")
XCTAssertEqual(try Data(contentsOf: runtime.contract), try old.jsonData())
}
func testPreparationFailureRestoresWrittenVariantAndReportsRestoreFailure() throws {
let document = SslocalRuntimeDocument(
servers: [], listen: SslocalListenSettings(), acl: .direct(at: store.aclFileURL))
try store.write(document)
let variant = store.aclVariantFileURL(summary: "direct")
try FileManager.default.removeItem(at: store.aclFileURL)
try FileManager.default.createDirectory(
at: store.aclFileURL, withIntermediateDirectories: false)
var writes = 0
let failing = RuntimeFileStore(fileURL: runtime.contract) { data, url in
if url == variant {
writes += 1
if writes == 2 { throw NSError(domain: "restore", code: 1) }
}
try AtomicFileWriter.write(data, to: url)
}
let changed = document.replacingACL(
ProxyACLDocument(
path: store.aclFileURL.path, summary: "direct",
content: "[bypass_all]\n[proxy_list]\nexample.com\n"))
XCTAssertThrowsError(try failing.write(changed)) { error in
guard case RuntimeFileStore.PersistenceError.rollbackFailed = error else {
return XCTFail("Expected explicit preparation rollback failure: \(error)")
}
}
XCTAssertEqual(writes, 2)
}
}