Skip to content

Commit 8785292

Browse files
committed
fix: preserve log trim boundaries and verify cached audio
1 parent 9ef8fb8 commit 8785292

3 files changed

Lines changed: 48 additions & 3 deletions

File tree

Sources/VocaMac/Services/Logger.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,16 @@ enum LogFileStore {
6666
let size = try handle.seekToEnd()
6767
guard size > maximumBytes else { return }
6868

69-
try handle.seek(toOffset: size - UInt64(maximumBytes))
69+
// Inspect the preceding byte so a cutoff at a complete line does not
70+
// discard that line. Only drop a partial leading entry.
71+
try handle.seek(toOffset: size - UInt64(maximumBytes) - 1)
72+
let precedingByte = try handle.read(upToCount: 1)?.first
7073
var tail = try handle.readToEnd() ?? Data()
71-
if let newline = tail.firstIndex(of: 0x0A) {
74+
if precedingByte != 0x0A, let newline = tail.firstIndex(of: 0x0A) {
7275
tail.removeSubrange(tail.startIndex...newline)
76+
} else if precedingByte != 0x0A {
77+
// No complete entry fits; avoid writing a partial UTF-8 sequence.
78+
tail.removeAll()
7379
}
7480
try tail.write(to: url, options: .atomic)
7581
}
@@ -363,7 +369,7 @@ final class VocaLogger {
363369
)) ?? []
364370
for file in files {
365371
let name = file.deletingPathExtension().lastPathComponent
366-
guard name.hasPrefix("vocamac."),
372+
guard file.pathExtension == "log", name.hasPrefix("vocamac."),
367373
let index = Int(name.dropFirst("vocamac.".count)),
368374
index > maxRotatedFiles else {
369375
continue

Tests/VocaMacTests/LoggerTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,22 @@ final class LogLevelTests: XCTestCase {
7777

7878
final class VocaLoggerTests: XCTestCase {
7979

80+
func testTrimPreservesCompleteLineAtExactCutoff() throws {
81+
let file = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
82+
defer { try? FileManager.default.removeItem(at: file) }
83+
try Data("old\nनमस्ते\n".utf8).write(to: file)
84+
try LogFileStore.trimToTail(at: file, maximumBytes: Data("नमस्ते\n".utf8).count)
85+
XCTAssertEqual(try String(contentsOf: file, encoding: .utf8), "नमस्ते\n")
86+
}
87+
88+
func testTrimDoesNotKeepPartialUnicodeEntry() throws {
89+
let file = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
90+
defer { try? FileManager.default.removeItem(at: file) }
91+
try Data("नमस्ते".utf8).write(to: file)
92+
try LogFileStore.trimToTail(at: file, maximumBytes: 2)
93+
XCTAssertEqual(try Data(contentsOf: file), Data())
94+
}
95+
8096
func testRotationReplacesFullBackupSetAndPreservesOrder() throws {
8197
let directory = FileManager.default.temporaryDirectory
8298
.appendingPathComponent(UUID().uuidString, isDirectory: true)

Tests/VocaMacTests/ServiceTests.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,29 @@ extension XCTestCase {
446446

447447
final class AudioEngineTests: XCTestCase {
448448

449+
func testCachedConversionMatchesFreshConverterAcrossBuffersAndRouteChanges() throws {
450+
let cache = AudioConverterCache()
451+
for rate in [48_000.0, 48_000.0, 44_100.0, 44_100.0] {
452+
let format = try XCTUnwrap(AVAudioFormat(standardFormatWithSampleRate: rate, channels: 1))
453+
let input = try XCTUnwrap(AVAudioPCMBuffer(pcmFormat: format, frameCapacity: 4_096))
454+
input.frameLength = 4_096
455+
let samples = try XCTUnwrap(input.floatChannelData?[0])
456+
for frame in 0..<4_096 {
457+
samples[frame] = Float(sin(Double(frame) * 2 * .pi * 440 / rate)) * 0.25
458+
}
459+
let fresh = try XCTUnwrap(AudioEngine.convertToWhisperFormat(input, from: format))
460+
let cached = try XCTUnwrap(AudioEngine.convertToWhisperFormat(
461+
input, from: format, converterProvider: { cache.converter(from: $0, to: $1) }
462+
))
463+
XCTAssertEqual(cached.frameLength, fresh.frameLength)
464+
let expected = try XCTUnwrap(fresh.floatChannelData?[0])
465+
let actual = try XCTUnwrap(cached.floatChannelData?[0])
466+
for frame in 0..<Int(fresh.frameLength) {
467+
XCTAssertEqual(actual[frame], expected[frame], accuracy: 0.000_001)
468+
}
469+
}
470+
}
471+
449472
func testConverterCacheReusesMatchingFormatAndReplacesChangedFormat() throws {
450473
let cache = AudioConverterCache()
451474
let destination = try XCTUnwrap(AVAudioFormat(

0 commit comments

Comments
 (0)