Skip to content

Commit 8108205

Browse files
committed
Stub low-RAM model gate in AppState tests
CI free+inactive pages often sit under Medium's 5 GB estimate, so the new pre-load check rejected mock medium loads and broke serialization/restore tests.
1 parent 07297e9 commit 8108205

3 files changed

Lines changed: 44 additions & 1 deletion

File tree

Sources/VocaMac/Models/AppState.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,10 @@ final class AppState: ObservableObject {
324324
/// Set to `true` in tests to avoid side effects.
325325
let skipSystemIntegration: Bool
326326

327+
/// Pre-load memory gate. Production defaults to SystemInfo; tests stub this
328+
/// so CI free+inactive pages cannot flake medium/large mock loads.
329+
var modelFitsInMemory: (ModelSize) -> Bool = { SystemInfo.canFitModelInMemory($0) }
330+
327331
// MARK: - Initialization
328332

329333
init(
@@ -1255,7 +1259,7 @@ final class AppState: ObservableObject {
12551259
// Refuse known-too-large loads before WhisperKit/CoreML can hang the
12561260
// UI spinner under memory pressure (vocamac#250).
12571261
if let targetSize,
1258-
!SystemInfo.canFitModelInMemory(targetSize) {
1262+
!modelFitsInMemory(targetSize) {
12591263
let needed = String(format: "%.1f", targetSize.ramRequiredGB)
12601264
let failureMessage =
12611265
"Not enough free memory to load \(targetSize.displayName) "

Tests/VocaMacTests/AppStateTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,43 @@ final class AppStateModelLoadingTests: XCTestCase {
347347
)
348348
}
349349

350+
@MainActor
351+
func testLowMemoryGateRefusesMediumBeforeWhisperAndRestoresPrevious() async {
352+
UserDefaults.standard.set(ModelSize.small.rawValue, forKey: "vocamac.selectedModelSize")
353+
354+
let modelManager = MockModelManager()
355+
modelManager.downloadedModels = [.small, .medium]
356+
357+
let whisperService = MockWhisperService()
358+
whisperService.loadedModelName = "openai_whisper-small"
359+
whisperService.isModelLoaded = true
360+
whisperService.loadResponses = [
361+
.success("openai_whisper-small"),
362+
]
363+
364+
let (appState, mocks) = AppState.makeTestState(
365+
modelManager: modelManager,
366+
whisperService: whisperService
367+
)
368+
appState.modelFitsInMemory = { $0 != .medium }
369+
370+
await appState.loadModel(.medium)
371+
372+
XCTAssertTrue(
373+
appState.errorMessage?.contains("Not enough") == true
374+
|| appState.errorMessage?.localizedCaseInsensitiveContains("free memory") == true
375+
)
376+
XCTAssertFalse(
377+
mocks.whisperService.loadRequests.map { $0.name }.contains("openai_whisper-medium")
378+
)
379+
XCTAssertEqual(
380+
mocks.whisperService.loadRequests.map { $0.name },
381+
["openai_whisper-small"]
382+
)
383+
XCTAssertEqual(appState.currentModel?.size, .small)
384+
XCTAssertEqual(appState.selectedModelSize, ModelSize.small.rawValue)
385+
}
386+
350387
@MainActor
351388
func testDeleteModelRemovesDownloadedModel() async {
352389
let modelManager = MockModelManager()

Tests/VocaMacTests/Mocks/MockServices.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,8 @@ extension AppState {
572572
permissionManager: permissionManager,
573573
skipSystemIntegration: true
574574
)
575+
// Bypass host free-RAM probe so mock loads are not refused on CI.
576+
appState.modelFitsInMemory = { _ in true }
575577
return (appState, mocks)
576578
}
577579
}

0 commit comments

Comments
 (0)