native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
27 kB · 613 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614import Foundationimport Testing
@testable import supacode
actor LineChangesShellCallStore { private(set) var calls: [[String]] = []
func record(_ arguments: [String]) { calls.append(arguments) }}
struct GitClientLineChangesTests { @Test func lineChangesUsesShortstatAndParsesOutput() async throws { let store = LineChangesShellCallStore() let shell = ShellClient( run: { _, arguments, _ in await store.record(arguments) if arguments.contains("--shortstat") { return ShellOutput( stdout: " 1 file changed, 12 insertions(+), 3 deletions(-)\n", stderr: "", exitCode: 0) } return ShellOutput(stdout: "", stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
let changes = await client.lineChanges(at: URL(fileURLWithPath: "/tmp/repo"))
#expect(changes?.added == 12) #expect(changes?.removed == 3) let calls = await store.calls #expect(calls.count == 2) let diffArgs = try #require(calls.first { $0.contains("--shortstat") }) #expect(diffArgs.contains("/usr/bin/git")) #expect(diffArgs.contains("diff")) #expect(diffArgs.contains("HEAD")) #expect(!diffArgs.contains("--numstat")) let untrackedArgs = try #require(calls.first { $0.contains("ls-files") }) #expect(untrackedArgs.contains("--others")) #expect(untrackedArgs.contains("--exclude-standard")) #expect(untrackedArgs.contains("-z")) }
@Test func lineChangesHandlesMissingDeletions() async { let shell = ShellClient( run: { _, arguments, _ in if arguments.contains("--shortstat") { return ShellOutput(stdout: " 1 file changed, 5 insertions(+)\n", stderr: "", exitCode: 0) } return ShellOutput(stdout: "", stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
let changes = await client.lineChanges(at: URL(fileURLWithPath: "/tmp/repo"))
#expect(changes?.added == 5) #expect(changes?.removed == 0) }
@Test func lineChangesParsesShortstatLine() async { let shell = ShellClient( run: { _, arguments, _ in if arguments.contains("--shortstat") { return ShellOutput( stdout: "1 file changed, 10 insertions(+), 4 deletions(-)\n", stderr: "", exitCode: 0) } return ShellOutput(stdout: "", stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
let changes = await client.lineChanges(at: URL(fileURLWithPath: "/tmp/repo"))
#expect(changes?.added == 10) #expect(changes?.removed == 4) }
@Test func lineChangesHandlesEmptyOutput() async { let shell = ShellClient( run: { _, _, _ in ShellOutput(stdout: "\n", stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
let changes = await client.lineChanges(at: URL(fileURLWithPath: "/tmp/repo"))
#expect(changes?.added == 0) #expect(changes?.removed == 0) }
@Test func lineChangesIncludesUntrackedFileLines() async throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } let gitDirectory = tempRoot.appending(path: ".git") try fileManager.createDirectory(at: gitDirectory, withIntermediateDirectories: true) let headURL = gitDirectory.appending(path: "HEAD") try "ref: refs/heads/main\n".write(to: headURL, atomically: true, encoding: .utf8)
let untrackedFile = tempRoot.appending(path: "new_file.swift") try "line1\nline2\nline3\n".write(to: untrackedFile, atomically: true, encoding: .utf8)
let shell = ShellClient( run: { _, arguments, _ in if arguments.contains("--shortstat") { return ShellOutput( stdout: " 1 file changed, 10 insertions(+), 2 deletions(-)\n", stderr: "", exitCode: 0) } if arguments.contains("ls-files") { return ShellOutput(stdout: "new_file.swift\0", stderr: "", exitCode: 0) } return ShellOutput(stdout: "", stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
let changes = await client.lineChanges(at: tempRoot)
#expect(changes?.added == 13) #expect(changes?.removed == 2) }
@Test func lineChangesSkipsBinaryUntrackedFiles() async throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } let gitDirectory = tempRoot.appending(path: ".git") try fileManager.createDirectory(at: gitDirectory, withIntermediateDirectories: true) let headURL = gitDirectory.appending(path: "HEAD") try "ref: refs/heads/main\n".write(to: headURL, atomically: true, encoding: .utf8)
let binaryFile = tempRoot.appending(path: "image.png") var binaryData = Data("PNG\n".utf8) binaryData.append(0x00) binaryData.append(contentsOf: Data(repeating: 0x0A, count: 100)) try binaryData.write(to: binaryFile)
let textFile = tempRoot.appending(path: "readme.txt") try "hello\nworld\n".write(to: textFile, atomically: true, encoding: .utf8)
let shell = ShellClient( run: { _, arguments, _ in if arguments.contains("--shortstat") { return ShellOutput(stdout: "", stderr: "", exitCode: 0) } if arguments.contains("ls-files") { return ShellOutput(stdout: "image.png\0readme.txt\0", stderr: "", exitCode: 0) } return ShellOutput(stdout: "", stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
let changes = await client.lineChanges(at: tempRoot)
#expect(changes?.added == 2) #expect(changes?.removed == 0) }
@Test func lineChangesParsesNULSeparatedUntrackedFilePaths() async throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } let gitDirectory = tempRoot.appending(path: ".git") try fileManager.createDirectory(at: gitDirectory, withIntermediateDirectories: true) let headURL = gitDirectory.appending(path: "HEAD") try "ref: refs/heads/main\n".write(to: headURL, atomically: true, encoding: .utf8)
let relativePath = " leading space\nname.txt" try "alpha\nbeta".write(to: tempRoot.appending(path: relativePath), atomically: true, encoding: .utf8)
let shell = ShellClient( run: { _, arguments, _ in if arguments.contains("--shortstat") { return ShellOutput(stdout: "", stderr: "", exitCode: 0) } if arguments.contains("ls-files") { return ShellOutput(stdout: "\(relativePath)\0", stderr: "", exitCode: 0) } return ShellOutput(stdout: "", stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
let changes = await client.lineChanges(at: tempRoot)
#expect(changes?.added == 2) #expect(changes?.removed == 0) }
@Test func lineChangesSkipsWhenIndexLocked() async throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } let gitDirectory = tempRoot.appending(path: ".git") try fileManager.createDirectory(at: gitDirectory, withIntermediateDirectories: true) let headURL = gitDirectory.appending(path: "HEAD") try "ref: refs/heads/main\n".write(to: headURL, atomically: true, encoding: .utf8) let lockURL = gitDirectory.appending(path: "index.lock") try Data().write(to: lockURL) let store = LineChangesShellCallStore() let shell = ShellClient( run: { _, arguments, _ in await store.record(arguments) return ShellOutput(stdout: "", stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
let changes = await client.lineChanges(at: tempRoot)
#expect(changes == nil) let calls = await store.calls #expect(calls.isEmpty) }
@Test func indexEntryCountReadsGitIndexHeader() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } let gitDirectory = tempRoot.appending(path: ".git") try fileManager.createDirectory(at: gitDirectory, withIntermediateDirectories: true)
try writeGitIndexHeader(entryCount: 42_000, to: gitDirectory.appending(path: "index"))
let count = GitClient.indexEntryCount(at: tempRoot) #expect(count == 42_000) }
@Test func indexEntryCountRejectsInvalidHeader() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } let gitDirectory = tempRoot.appending(path: ".git") try fileManager.createDirectory(at: gitDirectory, withIntermediateDirectories: true)
var invalidMagic = Data() invalidMagic.append(contentsOf: "NOPE".utf8) invalidMagic.append(contentsOf: [0, 0, 0, 2]) invalidMagic.append(contentsOf: [0, 0, 0, 1]) try invalidMagic.write(to: gitDirectory.appending(path: "index"))
#expect(GitClient.indexEntryCount(at: tempRoot) == nil)
try writeGitIndexHeader(version: 99, entryCount: 1, to: gitDirectory.appending(path: "index"))
#expect(GitClient.indexEntryCount(at: tempRoot) == nil) }
@Test func indexEntryCountReturnsNilForMissingIndex() { let count = GitClient.indexEntryCount(at: URL(fileURLWithPath: "/nonexistent")) #expect(count == nil) }
@Test func countLinesInFilesCountsNewlines() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "a\nb\nc\n".write(to: tempRoot.appending(path: "a.txt"), atomically: true, encoding: .utf8) try "x\ny\n".write(to: tempRoot.appending(path: "b.txt"), atomically: true, encoding: .utf8)
let count = GitClient.countLinesInFiles(["a.txt", "b.txt"], relativeTo: tempRoot).lines #expect(count == 5) }
@Test func countLinesInFilesCountsFinalLineWithoutTrailingNewline() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "hello".write(to: tempRoot.appending(path: "single.txt"), atomically: true, encoding: .utf8) try "a\nb".write(to: tempRoot.appending(path: "multi.txt"), atomically: true, encoding: .utf8)
let count = GitClient.countLinesInFiles(["single.txt", "multi.txt"], relativeTo: tempRoot).lines #expect(count == 3) }
@Test func countLinesInFilesSkipsBinaryFiles() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "text\nfile\n".write(to: tempRoot.appending(path: "ok.txt"), atomically: true, encoding: .utf8) var binary = Data("header\n".utf8) binary.append(0x00) binary.append(contentsOf: Data(repeating: 0x0A, count: 50)) try binary.write(to: tempRoot.appending(path: "img.bin"))
let count = GitClient.countLinesInFiles(["ok.txt", "img.bin"], relativeTo: tempRoot).lines #expect(count == 2) }
/// The former per-file cutoff silently hid readable text changes. A file at /// that boundary must remain exact when the refresh-wide budget allows it. @Test func countLinesInFilesCountsFilesAtTheLegacyByteLimit() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "a\nb\n".write(to: tempRoot.appending(path: "small.txt"), atomically: true, encoding: .utf8) let legacyLimit = 2 * 1_024 * 1_024 // Text, not binary: every newline must contribute to the visible badge. let largeText = Data(repeating: 0x0A, count: legacyLimit) try largeText.write(to: tempRoot.appending(path: "sample.txt"))
let count = GitClient.countLinesInFiles( ["small.txt", "sample.txt"], relativeTo: tempRoot, byteBudget: legacyLimit + 16 ).lines #expect(count == legacyLimit + 2) }
/// Keep coverage on both sides of the removed cutoff so a future optimization /// cannot accidentally reintroduce the old discontinuity. @Test func countLinesInFilesCountsJustUnderTheLegacyByteLimit() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) let legacyLimit = 2 * 1_024 * 1_024 let justUnder = Data(repeating: 0x0A, count: legacyLimit - 1) try justUnder.write(to: tempRoot.appending(path: "big.txt"))
let count = GitClient.countLinesInFiles(["big.txt"], relativeTo: tempRoot).lines #expect(count == legacyLimit - 1) }
@Test func countLinesInFilesSkipsMissingFiles() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "a\nb\n".write(to: tempRoot.appending(path: "exists.txt"), atomically: true, encoding: .utf8)
let count = GitClient.countLinesInFiles(["exists.txt", "gone.txt"], relativeTo: tempRoot).lines #expect(count == 2) }
/// The reader works in 64 KiB chunks, so a scan that resumed from the wrong offset /// after a hit — or restarted per chunk — would miscount only once a file is larger /// than one chunk. Every other case in this file fits in a single chunk. @Test func countLinesInFilesCountsAcrossChunkBoundaries() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) // 40_000 lines of "line\n" is ~200 KB, spanning four chunks. let content = String(repeating: "line\n", count: 40_000) try content.write(to: tempRoot.appending(path: "big.txt"), atomically: true, encoding: .utf8)
#expect(GitClient.countLinesInFiles(["big.txt"], relativeTo: tempRoot).lines == 40_000) }
/// A newline landing on the final byte of a chunk is the boundary case: the scan must /// neither drop it nor double-count it against the next chunk's first byte. @Test func countLinesInFilesCountsANewlineOnTheChunkBoundary() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) let chunkByteCount = 64 * 1_024 var bytes = Data(repeating: UInt8(ascii: "a"), count: chunkByteCount - 1) bytes.append(0x0A) // exactly the last byte of chunk one bytes.append(contentsOf: Data("second\n".utf8)) try bytes.write(to: tempRoot.appending(path: "boundary.txt"))
#expect(GitClient.countLinesInFiles(["boundary.txt"], relativeTo: tempRoot).lines == 2) }
/// Binary detection probes only the first 8 KiB. A NUL past that window has always /// been counted as text, and the faster scan must not widen the probe by accident. @Test func countLinesInFilesTreatsALateNULAsText() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) var bytes = Data(repeating: UInt8(ascii: "a"), count: 10_000) bytes.append(0x00) // beyond the 8 KiB probe bytes.append(0x0A) try bytes.write(to: tempRoot.appending(path: "late-nul.txt"))
#expect(GitClient.countLinesInFiles(["late-nul.txt"], relativeTo: tempRoot).lines == 1) }
@Test func countLinesInFilesCountsAnEmptyFileAsZero() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try Data().write(to: tempRoot.appending(path: "empty.txt"))
#expect(GitClient.countLinesInFiles(["empty.txt"], relativeTo: tempRoot).lines == 0) }
@Test func countLinesInFilesReusesCachedCountsWithoutSpendingTheRefreshBudget() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "a\nb\n".write(to: tempRoot.appending(path: "cached.txt"), atomically: true, encoding: .utf8) let cache = UntrackedLineCountCache()
let cold = GitClient.countLinesInFiles( ["cached.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 4 ) let warm = GitClient.countLinesInFiles( ["cached.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 0 )
#expect(cold == UntrackedLineCountResult(lines: 2, skippedFileCount: 0)) #expect(warm == cold) }
@Test func countLinesInFilesInvalidatesCachedCountsWhenTheFileChanges() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) let fileURL = tempRoot.appending(path: "changing.txt") try "a\nb\n".write(to: fileURL, atomically: true, encoding: .utf8) let cache = UntrackedLineCountCache()
let initial = GitClient.countLinesInFiles( ["changing.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 4 ) try "a\nb\nc\n".write(to: fileURL, atomically: true, encoding: .utf8) let invalidated = GitClient.countLinesInFiles( ["changing.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 0 )
#expect(initial == UntrackedLineCountResult(lines: 2, skippedFileCount: 0)) #expect(invalidated == UntrackedLineCountResult(lines: 0, skippedFileCount: 1)) }
@Test func countLinesInFilesAppliesOneBudgetAcrossAllCacheMisses() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "a\n".write(to: tempRoot.appending(path: "small.txt"), atomically: true, encoding: .utf8) try "b\nc\n".write(to: tempRoot.appending(path: "large.txt"), atomically: true, encoding: .utf8)
let result = GitClient.countLinesInFiles( ["large.txt", "small.txt"], relativeTo: tempRoot, cache: UntrackedLineCountCache(), byteBudget: 2 )
#expect(result == UntrackedLineCountResult(lines: 1, skippedFileCount: 1)) }
@Test func countLinesInFilesBoundsCachedWorktreeLifetime() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } let firstRoot = tempRoot.appending(path: "first", directoryHint: .isDirectory) let secondRoot = tempRoot.appending(path: "second", directoryHint: .isDirectory) try fileManager.createDirectory(at: firstRoot, withIntermediateDirectories: true) try fileManager.createDirectory(at: secondRoot, withIntermediateDirectories: true) try "a\n".write(to: firstRoot.appending(path: "file.txt"), atomically: true, encoding: .utf8) try "b\n".write(to: secondRoot.appending(path: "file.txt"), atomically: true, encoding: .utf8) let cache = UntrackedLineCountCache(maximumWorktreeCount: 1)
_ = GitClient.countLinesInFiles( ["file.txt"], relativeTo: firstRoot, cache: cache, byteBudget: 2) _ = GitClient.countLinesInFiles( ["file.txt"], relativeTo: secondRoot, cache: cache, byteBudget: 2) let evicted = GitClient.countLinesInFiles( ["file.txt"], relativeTo: firstRoot, cache: cache, byteBudget: 0)
#expect(evicted == UntrackedLineCountResult(lines: 0, skippedFileCount: 1)) }
@Test func countLinesInFilesBoundsCachedEntriesPerWorktree() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "a\n".write(to: tempRoot.appending(path: "a.txt"), atomically: true, encoding: .utf8) try "b\n".write(to: tempRoot.appending(path: "b.txt"), atomically: true, encoding: .utf8) let cache = UntrackedLineCountCache( maximumWorktreeCount: 1, maximumEntryCountPerWorktree: 1, maximumTotalEntryCount: 2, maximumCachedPathByteCount: 128 )
_ = GitClient.countLinesInFiles( ["a.txt", "b.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 4) let capped = GitClient.countLinesInFiles( ["a.txt", "b.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 0)
#expect(capped == UntrackedLineCountResult(lines: 1, skippedFileCount: 1)) }
@Test func countLinesInFilesBoundsCachedEntriesAcrossWorktrees() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } let firstRoot = tempRoot.appending(path: "first", directoryHint: .isDirectory) let secondRoot = tempRoot.appending(path: "second", directoryHint: .isDirectory) try fileManager.createDirectory(at: firstRoot, withIntermediateDirectories: true) try fileManager.createDirectory(at: secondRoot, withIntermediateDirectories: true) try "a\n".write(to: firstRoot.appending(path: "file.txt"), atomically: true, encoding: .utf8) try "b\n".write(to: secondRoot.appending(path: "file.txt"), atomically: true, encoding: .utf8) let cache = UntrackedLineCountCache( maximumWorktreeCount: 2, maximumEntryCountPerWorktree: 2, maximumTotalEntryCount: 1, maximumCachedPathByteCount: 128 )
_ = GitClient.countLinesInFiles( ["file.txt"], relativeTo: firstRoot, cache: cache, byteBudget: 2) _ = GitClient.countLinesInFiles( ["file.txt"], relativeTo: secondRoot, cache: cache, byteBudget: 2) let capped = GitClient.countLinesInFiles( ["file.txt"], relativeTo: firstRoot, cache: cache, byteBudget: 0)
#expect(capped == UntrackedLineCountResult(lines: 0, skippedFileCount: 1)) }
@Test func countLinesInFilesBoundsCachedPathBytes() throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "a\n".write(to: tempRoot.appending(path: "a.txt"), atomically: true, encoding: .utf8) try "b\n".write(to: tempRoot.appending(path: "b.txt"), atomically: true, encoding: .utf8) let cache = UntrackedLineCountCache( maximumWorktreeCount: 1, maximumEntryCountPerWorktree: 2, maximumTotalEntryCount: 2, maximumCachedPathByteCount: 9 )
_ = GitClient.countLinesInFiles( ["a.txt", "b.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 4) let capped = GitClient.countLinesInFiles( ["a.txt", "b.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 0)
#expect(capped == UntrackedLineCountResult(lines: 1, skippedFileCount: 1)) }
@Test func countLinesInFilesSupportsConcurrentRefreshes() async throws { let fileManager = FileManager.default let tempRoot = fileManager.temporaryDirectory.appending(path: UUID().uuidString) defer { try? fileManager.removeItem(at: tempRoot) } try fileManager.createDirectory(at: tempRoot, withIntermediateDirectories: true) try "a\nb\n".write(to: tempRoot.appending(path: "shared.txt"), atomically: true, encoding: .utf8) let cache = UntrackedLineCountCache()
let results = await withTaskGroup(of: UntrackedLineCountResult.self) { group in for _ in 0..<16 { group.addTask { GitClient.countLinesInFiles( ["shared.txt"], relativeTo: tempRoot, cache: cache, byteBudget: 4) } } var results: [UntrackedLineCountResult] = [] for await result in group { results.append(result) } return results }
#expect(results.count == 16) #expect(results.allSatisfy { $0 == UntrackedLineCountResult(lines: 2, skippedFileCount: 0) }) }
private func writeGitIndexHeader( version: UInt32 = 2, entryCount: UInt32, to url: URL ) throws { var header = Data() header.append(contentsOf: "DIRC".utf8) header.append(UInt8((version >> 24) & 0xff)) header.append(UInt8((version >> 16) & 0xff)) header.append(UInt8((version >> 8) & 0xff)) header.append(UInt8(version & 0xff)) header.append(UInt8((entryCount >> 24) & 0xff)) header.append(UInt8((entryCount >> 16) & 0xff)) header.append(UInt8((entryCount >> 8) & 0xff)) header.append(UInt8(entryCount & 0xff)) try header.write(to: url) }}