diff options
Diffstat (limited to 'SoraTests/SettingsManagerSyncTests.swift')
| -rw-r--r-- | SoraTests/SettingsManagerSyncTests.swift | 270 |
1 files changed, 72 insertions, 198 deletions
diff --git a/SoraTests/SettingsManagerSyncTests.swift b/SoraTests/SettingsManagerSyncTests.swift index 72cf2ad..50106dc 100644 --- a/SoraTests/SettingsManagerSyncTests.swift +++ b/SoraTests/SettingsManagerSyncTests.swift @@ -1,7 +1,5 @@ -import Foundation import XCTest -// swiftlint:disable type_body_length final class SettingsManagerSyncTests: XCTestCase { func testBookmarkMutationPathReusesEncodedPayload() throws { let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift") @@ -120,210 +118,86 @@ final class SettingsManagerSyncTests: XCTestCase { ) } - private func loadSource(at relativePath: String) throws -> String { - let currentFile = URL(fileURLWithPath: #filePath) - let repositoryRoot = currentFile.deletingLastPathComponent().deletingLastPathComponent() - let fileURL = repositoryRoot.appendingPathComponent(relativePath) - - return try String(contentsOf: fileURL, encoding: .utf8) - } - - // swiftlint:disable:next cyclomatic_complexity - private func extractFunction(named signature: String, from source: String) throws -> String { - guard let signatureRange = source.range(of: signature) else { - throw NSError(domain: "SettingsManagerSyncTests", code: 1) - } - guard let openingBrace = source[signatureRange.upperBound...].firstIndex(of: "{") else { - throw NSError(domain: "SettingsManagerSyncTests", code: 2) - } - - let characters = Array(source) - let startOffset = source.distance(from: source.startIndex, to: signatureRange.lowerBound) - var currentOffset = source.distance(from: source.startIndex, to: openingBrace) - - var braceDepth = 0 - var inLineComment = false - var blockCommentDepth = 0 - var inString = false - var isEscaped = false - - while currentOffset < characters.count { - let current = characters[currentOffset] - let next: Character? = currentOffset + 1 < characters.count ? characters[currentOffset + 1] : nil - - if inLineComment { - if current == "\n" { - inLineComment = false - } - currentOffset += 1 - continue - } - - if blockCommentDepth > 0 { - if current == "/", next == "*" { - blockCommentDepth += 1 - currentOffset += 2 - continue - } - - if current == "*", next == "/" { - blockCommentDepth -= 1 - currentOffset += 2 - continue - } - - currentOffset += 1 - continue - } - - if inString { - if isEscaped { - isEscaped = false - } else if current == "\\" { - isEscaped = true - } else if current == "\"" { - inString = false - } - - currentOffset += 1 - continue - } - - if current == "/", next == "/" { - inLineComment = true - currentOffset += 2 - continue - } - - if current == "/", next == "*" { - blockCommentDepth = 1 - currentOffset += 2 - continue - } - - if current == "\"" { - inString = true - currentOffset += 1 - continue - } - - if current == "{" { - braceDepth += 1 - } else if current == "}" { - braceDepth -= 1 - - if braceDepth == 0 { - let endIndex = source.index(source.startIndex, offsetBy: currentOffset + 1) - let startIndex = source.index(source.startIndex, offsetBy: startOffset) - - return String(source[startIndex..<endIndex]) - } - } + func testBatchedSyncUsesCoordinatorQueue() throws { + let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift") + let triggerBatchSection = try extractFunction( + named: "private func triggerBatchedSync()", + from: source + ) + let normalizedSection = strippingCommentsAndStrings(from: triggerBatchSection) - currentOffset += 1 - } + let coordinatorEnqueueCount = tokenCount( + matching: #"\bsyncCoordinator\s*\.\s*enqueue\s*\("#, + in: normalizedSection + ) + let detachedCount = tokenCount( + matching: #"\bTask\s*\.\s*detached\b"#, + in: normalizedSection + ) - throw NSError(domain: "SettingsManagerSyncTests", code: 3) + // swiftlint:disable:next prefer_nimble + XCTAssertGreaterThan( + coordinatorEnqueueCount, + 0, + "Batched sync should enqueue through a single sync coordinator." + ) + // swiftlint:disable:next prefer_nimble + XCTAssertEqual( + detachedCount, + 0, + "Batched sync should not launch detached tasks directly." + ) } - // swiftlint:disable:next cyclomatic_complexity - private func strippingCommentsAndStrings(from source: String) -> String { - let characters = Array(source) - var result: [Character] = [] - result.reserveCapacity(characters.count) - - var currentOffset = 0 - var inLineComment = false - var blockCommentDepth = 0 - var inString = false - var isEscaped = false - - while currentOffset < characters.count { - let current = characters[currentOffset] - let next: Character? = currentOffset + 1 < characters.count ? characters[currentOffset + 1] : nil - - if inLineComment { - if current == "\n" { - inLineComment = false - result.append("\n") - } else { - result.append(" ") - } - currentOffset += 1 - continue - } - - if blockCommentDepth > 0 { - if current == "/", next == "*" { - blockCommentDepth += 1 - result.append(" ") - result.append(" ") - currentOffset += 2 - continue - } - - if current == "*", next == "/" { - blockCommentDepth -= 1 - result.append(" ") - result.append(" ") - currentOffset += 2 - continue - } - - result.append(current == "\n" ? "\n" : " ") - currentOffset += 1 - continue - } - - if inString { - if isEscaped { - isEscaped = false - } else if current == "\\" { - isEscaped = true - } else if current == "\"" { - inString = false - } - - result.append(current == "\n" ? "\n" : " ") - currentOffset += 1 - continue - } - - if current == "/", next == "/" { - inLineComment = true - result.append(" ") - result.append(" ") - currentOffset += 2 - continue - } - - if current == "/", next == "*" { - blockCommentDepth = 1 - result.append(" ") - result.append(" ") - currentOffset += 2 - continue - } - - if current == "\"" { - inString = true - result.append(" ") - currentOffset += 1 - continue - } + func testPerKeySyncPathAvoidsDetachedFanOut() throws { + let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift") + let triggerSyncSection = try extractFunction( + named: "private func triggerSyncIfNeeded(for key: SettingsSyncKey)", + from: source + ) + let normalizedSection = strippingCommentsAndStrings(from: triggerSyncSection) - result.append(current) - currentOffset += 1 - } + let detachedCount = tokenCount( + matching: #"\bTask\s*\.\s*detached\b"#, + in: normalizedSection + ) - return String(result) + // swiftlint:disable:next prefer_nimble + XCTAssertEqual( + detachedCount, + 0, + "Per-key sync should run on the coordinator path without detached fan-out." + ) } - private func tokenCount(matching pattern: String, in source: String) -> Int { - guard let regex = try? NSRegularExpression(pattern: pattern) else { return 0 } - let range = NSRange(source.startIndex..<source.endIndex, in: source) + func testManualFullSyncAggregatesChangeNotification() throws { + let source = try loadSource(at: "Sora/Data/Settings/SettingsManager.swift") + let fullSyncSection = try extractFunction( + named: "func triggerSyncIfNeededForAll()", + from: source + ) + let normalizedSection = strippingCommentsAndStrings(from: fullSyncSection) - return regex.numberOfMatches(in: source, range: range) + let bookmarksKeyCount = tokenCount(matching: #"\.bookmarks\b"#, in: normalizedSection) + let favoritesKeyCount = tokenCount(matching: #"\.favorites\b"#, in: normalizedSection) + let searchHistoryKeyCount = tokenCount(matching: #"\.searchHistory\b"#, in: normalizedSection) + let customProvidersKeyCount = tokenCount(matching: #"\.customProviders\b"#, in: normalizedSection) + let objectWillChangeCount = tokenCount( + matching: #"\bobjectWillChange\s*\.\s*send\s*\("#, + in: normalizedSection + ) + + // swiftlint:disable:next prefer_nimble + XCTAssertEqual( + bookmarksKeyCount + favoritesKeyCount + searchHistoryKeyCount + customProvidersKeyCount, + 4, + "Full sync should evaluate all supported sync keys." + ) + // swiftlint:disable:next prefer_nimble + XCTAssertGreaterThan( + objectWillChangeCount, + 0, + "Full sync should emit a consolidated objectWillChange notification when merged state changes." + ) } } -// swiftlint:enable type_body_length |