diff options
| author | Fuwn <[email protected]> | 2026-02-18 11:41:58 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-18 11:42:11 -0800 |
| commit | dd690e6be3bba1f0d3aa6e853e50e23cf44f75ec (patch) | |
| tree | 146ec3964c02579f63465c88199fcc8270d0c4e1 /SoraTests | |
| parent | Fix SwiftLint violations (diff) | |
| download | sora-testing-dd690e6be3bba1f0d3aa6e853e50e23cf44f75ec.tar.xz sora-testing-dd690e6be3bba1f0d3aa6e853e50e23cf44f75ec.zip | |
test: add baseline test harness and performance baseline report
Diffstat (limited to 'SoraTests')
| -rw-r--r-- | SoraTests/SettingsManagerSyncTests.swift | 237 | ||||
| -rw-r--r-- | SoraTests/ViewDerivedDataTests.swift | 192 |
2 files changed, 429 insertions, 0 deletions
diff --git a/SoraTests/SettingsManagerSyncTests.swift b/SoraTests/SettingsManagerSyncTests.swift new file mode 100644 index 0000000..27f05f5 --- /dev/null +++ b/SoraTests/SettingsManagerSyncTests.swift @@ -0,0 +1,237 @@ +import Foundation +import XCTest + +final class SettingsManagerSyncTests: XCTestCase { + func testBatchedSyncPathGuardsUnchangedPayloadWrites() 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) + + let cloudWriteCount = tokenCount( + matching: #"\bNSUbiquitousKeyValueStore\s*\.\s*default\s*\.\s*set\s*\(\s*encoded\s*,\s*forKey\s*:"#, + in: normalizedSection + ) + let unchangedGuardCount = tokenCount( + matching: #"\!\=\s*encoded\b"#, + in: normalizedSection + ) + + // swiftlint:disable:next prefer_nimble + XCTAssertGreaterThan(cloudWriteCount, 0, "Expected batched sync path to contain cloud writes.") + // swiftlint:disable:next prefer_nimble + XCTAssertGreaterThanOrEqual( + unchangedGuardCount, + cloudWriteCount, + "Every batched iCloud write should be guarded to avoid unchanged payload rewrites." + ) + } + + 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]) + } + } + + currentOffset += 1 + } + + throw NSError(domain: "SettingsManagerSyncTests", code: 3) + } + + // 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 + } + + result.append(current) + currentOffset += 1 + } + + return String(result) + } + + 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) + + return regex.numberOfMatches(in: source, range: range) + } +} diff --git a/SoraTests/ViewDerivedDataTests.swift b/SoraTests/ViewDerivedDataTests.swift new file mode 100644 index 0000000..6737265 --- /dev/null +++ b/SoraTests/ViewDerivedDataTests.swift @@ -0,0 +1,192 @@ +import Foundation +import XCTest + +final class ViewDerivedDataTests: XCTestCase { + func testGenericListViewDerivedCollectionsAreReferencedOncePerRenderPass() throws { + let source = try loadSource(at: "Sora/Views/Generic/GenericListView.swift") + let normalizedSource = strippingCommentsAndStrings(from: source) + + let filteredItemsUsages = referenceCount( + for: "filteredItems", + in: normalizedSource + ) + let sortedFilteredItemsUsages = referenceCount( + for: "sortedFilteredItems", + in: normalizedSource + ) + + // swiftlint:disable:next prefer_nimble + XCTAssertLessThanOrEqual( + filteredItemsUsages, + 1, + "filteredItems should be consumed once per dependency change." + ) + // swiftlint:disable:next prefer_nimble + XCTAssertLessThanOrEqual( + sortedFilteredItemsUsages, + 1, + "sortedFilteredItems should be consumed once per dependency change." + ) + } + + func testPostGridViewDerivedCollectionsAreReferencedOncePerRenderPass() throws { + let source = try loadSource(at: "Sora/Views/Post/Grid/PostGridView.swift") + let normalizedSource = strippingCommentsAndStrings(from: source) + + let activePostsUsages = referenceCount( + for: "activePosts", + in: normalizedSource + ) + let getColumnsDataUsages = invocationCount( + forFunction: "getColumnsData", + in: normalizedSource + ) + + // swiftlint:disable:next prefer_nimble + XCTAssertLessThanOrEqual( + activePostsUsages, + 1, + "activePosts-derived data should be consumed once per dependency change." + ) + // swiftlint:disable:next prefer_nimble + XCTAssertLessThanOrEqual( + getColumnsDataUsages, + 1, + "getColumnsData should be invoked once per dependency change." + ) + } + + 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 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 + } + + result.append(current) + currentOffset += 1 + } + + return String(result) + } + + private func referenceCount(for symbol: String, in source: String) -> Int { + let totalMatches = tokenCount( + matching: #"\b\#(symbol)\b"#, + in: source + ) + let declarationMatches = tokenCount( + matching: #"\b(?:var|let)\s+\#(symbol)\b"#, + in: source + ) + + return max(0, totalMatches - declarationMatches) + } + + private func invocationCount(forFunction name: String, in source: String) -> Int { + let totalMatches = tokenCount( + matching: #"\b\#(name)\s*\("#, + in: source + ) + let declarationMatches = tokenCount( + matching: #"\bfunc\s+\#(name)\s*\("#, + in: source + ) + + return max(0, totalMatches - declarationMatches) + } + + 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) + + return regex.numberOfMatches(in: source, range: range) + } +} |