spiegel-keyman/ios/engine/KMEI/KeymanEngineTests/AssociatingPackageInstallerTests.swift

363 lines
14 KiB
Swift

//
// AssociatingPackageInstallerTests.swift
// KeymanEngineTests
//
// Created by Joshua Horton on 8/6/20.
// Copyright © 2020 SIL International. All rights reserved.
//
import XCTest
@testable import KeymanEngine
class AssociatingPackageInstallerTests: XCTestCase {
var mockedURLSession: TestUtils.Downloading.URLSessionMock!
var downloadManager: ResourceDownloadManager!
let mockedSearchCallback: LanguagePickAssociator.AssociationSearcher = { lgCodes, callback in
var searchResult: [String: (KeymanPackage.Key, URL)?] = [:]
// We aren't downloading these models, so a placeholder URL is fine.
let placeholderURL = URL.init(string: "http://place.holder.com")!
lgCodes.forEach { lgCode in
switch lgCode {
case "en":
searchResult["en"] = (TestUtils.Packages.Keys.nrc_en_mtnt, placeholderURL)
case "str":
searchResult["str"] = (TestUtils.Packages.Keys.nrc_str_sencoten, placeholderURL)
default:
searchResult[lgCode] = nil
}
}
callback(searchResult)
}
override func setUp() {
// Ensure Manager's standard init() occurs before our tests.
// Otherwise, if our first Manager reference is DURING a test, we'll see unwanted behavior from it.
_ = Manager.shared
// Resets resource directories for a clean slate.
// We'll be testing against actually-installed packages.
TestUtils.standardTearDown()
mockedURLSession = TestUtils.Downloading.URLSessionMock()
downloadManager = ResourceDownloadManager(session: mockedURLSession!, autoExecute: true)
}
override func tearDownWithError() throws {
TestUtils.standardTearDown()
let queueWasCleared = mockedURLSession!.queueIsEmpty
mockedURLSession = nil
if !queueWasCleared {
throw NSError(domain: "Keyman",
code: 4,
userInfo: [NSLocalizedDescriptionKey: "A test did not fully utilize its queued mock results!"])
}
}
func testKeyboardNoAssociations() throws {
guard let package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.silEuroLatinKMP) as? KeyboardKeymanPackage else {
XCTFail()
return
}
let startExpectation = XCTestExpectation()
let completeExpectation = XCTestExpectation()
let installer = AssociatingPackageInstaller(for: package,
defaultLanguageCode: "en",
downloadManager: downloadManager) { status in
if status == .starting {
startExpectation.fulfill()
} else if status == .complete {
completeExpectation.fulfill()
} else if status == .cancelled {
XCTFail()
}
}
installer.pickLanguages(withCodes: Set(["en"]))
wait(for: [startExpectation, completeExpectation], timeout: 5)
guard let userKeyboards = Storage.active.userDefaults.userKeyboards else {
XCTFail()
return
}
XCTAssertTrue(userKeyboards.contains { $0.fullID == TestUtils.Keyboards.sil_euro_latin.fullID })
XCTAssertNil(Storage.active.userDefaults.userLexicalModels)
XCTAssertEqual(ResourceFileManager.shared.installState(forPackage: TestUtils.Packages.Keys.sil_euro_latin), .installed)
XCTAssertEqual(ResourceFileManager.shared.installState(forPackage: TestUtils.Packages.Keys.nrc_en_mtnt), .none)
}
func testLexicalModelNoAssociations() throws {
guard let package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) as? LexicalModelKeymanPackage else {
XCTFail()
return
}
let startExpectation = XCTestExpectation()
let completeExpectation = XCTestExpectation()
let installer = AssociatingPackageInstaller(for: package,
defaultLanguageCode: "en",
downloadManager: downloadManager) { status in
if status == .starting {
startExpectation.fulfill()
} else if status == .complete {
completeExpectation.fulfill()
} else if status == .cancelled {
XCTFail()
}
}
installer.pickLanguages(withCodes: Set(["en"]))
wait(for: [startExpectation, completeExpectation], timeout: 5)
guard let userLexicalModels = Storage.active.userDefaults.userLexicalModels else {
XCTFail()
return
}
XCTAssertTrue(userLexicalModels.contains { $0.fullID == TestUtils.LexicalModels.mtnt.fullID })
XCTAssertNil(Storage.active.userDefaults.userKeyboards)
XCTAssertEqual(ResourceFileManager.shared.installState(forPackage: TestUtils.Packages.Keys.nrc_en_mtnt), .installed)
XCTAssertEqual(ResourceFileManager.shared.installState(forPackage: TestUtils.Packages.Keys.sil_euro_latin), .none)
}
func testKeyboardWithAssociatedLexicalModels() throws {
let mockedQuery = TestUtils.Downloading.MockResult(location: TestUtils.Queries.model_case_en, error: nil)
let mockedDownload = TestUtils.Downloading.MockResult(location: TestUtils.LexicalModels.mtntKMP, error: nil)
mockedURLSession.queueMockResult(.data(mockedQuery))
mockedURLSession.queueMockResult(.download(mockedDownload))
guard let package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.silEuroLatinKMP) as? KeyboardKeymanPackage else {
XCTFail()
return
}
let startExpectation = XCTestExpectation()
let completeExpectation = XCTestExpectation()
let installer = AssociatingPackageInstaller(for: package,
defaultLanguageCode: "en",
downloadManager: downloadManager,
withAssociators: [ .lexicalModels ]) { status in
if status == .starting {
startExpectation.fulfill()
} else if status == .complete {
completeExpectation.fulfill()
} else if status == .cancelled {
XCTFail()
}
}
installer.pickLanguages(withCodes: Set(["en"]))
wait(for: [startExpectation, completeExpectation], timeout: 5)
guard let userKeyboards = Storage.active.userDefaults.userKeyboards,
let userLexicalModels = Storage.active.userDefaults.userLexicalModels else {
XCTFail()
return
}
XCTAssertTrue(userKeyboards.contains { $0.fullID == TestUtils.Keyboards.sil_euro_latin.fullID })
XCTAssertTrue(userLexicalModels.contains { $0.fullID == TestUtils.LexicalModels.mtnt.fullID} )
XCTAssertEqual(ResourceFileManager.shared.installState(forPackage: TestUtils.Packages.Keys.sil_euro_latin), .installed)
XCTAssertEqual(ResourceFileManager.shared.installState(forPackage: TestUtils.Packages.Keys.nrc_en_mtnt), .installed)
}
func testCancellationNoAssociations() throws {
guard let package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.silEuroLatinKMP) as? KeyboardKeymanPackage else {
XCTFail()
return
}
let cancelExpectation = XCTestExpectation()
let installer = AssociatingPackageInstaller(for: package,
defaultLanguageCode: "en",
downloadManager: downloadManager) { status in
if status == .cancelled {
// When the user 'cancels' language selection, this installer should also
// signal cancellation.
cancelExpectation.fulfill()
} else {
XCTFail()
}
}
// A bit of white-box testing - sets up the closure framework
installer.initializeSynchronizationGroups()
// And the install closure, which we'll send a 'cancel' signal to.
let installClosure = installer.coreInstallationClosure()
installClosure(nil)
wait(for: [cancelExpectation], timeout: 5)
XCTAssertNil(Storage.active.userDefaults.userKeyboards)
XCTAssertNil(Storage.active.userDefaults.userLexicalModels)
}
func testCancellationWithAssociations() throws {
guard let package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.silEuroLatinKMP) as? KeyboardKeymanPackage else {
XCTFail()
return
}
// While the query should occur, the installation phase should never be reached.
// So, we don't mock the download.
let mockedQuery = TestUtils.Downloading.MockResult(location: TestUtils.Queries.model_case_en, error: nil)
mockedURLSession.queueMockResult(.data(mockedQuery))
let cancelExpectation = XCTestExpectation()
let installer = AssociatingPackageInstaller(for: package,
defaultLanguageCode: "en",
downloadManager: downloadManager,
withAssociators: [.lexicalModels]) { status in
if status == .cancelled {
// When the user 'cancels' language selection, this installer should also
// signal cancellation.
cancelExpectation.fulfill()
} else {
XCTFail()
}
}
// Heavier white-box testing this time.
installer.initializeSynchronizationGroups()
installer.constructAssociationPickers()
installer.associationQueriers?.forEach {
$0.pickerInitialized()
$0.selectLanguages(Set(["en"])) // calls the expected query
// And, since we're modeling cancellation...
$0.pickerDismissed()
}
// Finally, the install closure, which we'll send a 'cancel' signal to.
let installClosure = installer.coreInstallationClosure()
installClosure(nil)
wait(for: [cancelExpectation], timeout: 5)
XCTAssertNil(Storage.active.userDefaults.userKeyboards)
XCTAssertNil(Storage.active.userDefaults.userLexicalModels)
}
func testDeinitConstructiveCancellation() throws {
guard let package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.silEuroLatinKMP) as? KeyboardKeymanPackage else {
XCTFail()
return
}
// While the query should occur, the installation phase should never be reached.
// So, we don't mock the download.
let mockedQuery = TestUtils.Downloading.MockResult(location: TestUtils.Queries.model_case_en, error: nil)
let queryExpectation = XCTestExpectation()
mockedURLSession.queueMockResult(.data(mockedQuery), expectation: queryExpectation)
let cancelExpectation = XCTestExpectation()
do {
let installer = AssociatingPackageInstaller(for: package,
defaultLanguageCode: "en",
downloadManager: downloadManager,
withAssociators: [.lexicalModels]) { status in
// When the user 'cancels' language selection, this installer should also
// signal cancellation.
if status == .cancelled {
// First, wait to ensure that the standard query did launch.
// Otherwise, we might accidentally skip the mocked query!
self.wait(for: [queryExpectation], timeout: 4)
cancelExpectation.fulfill()
} else {
XCTFail()
}
}
// Heavier white-box testing this time.
installer.initializeSynchronizationGroups()
installer.constructAssociationPickers()
installer.associationQueriers?.forEach {
$0.pickerInitialized()
$0.selectLanguages(Set(["en"])) // calls the expected query
// De-init will trigger the 'dismiss' signal.
}
}
wait(for: [cancelExpectation], timeout: 5)
XCTAssertNil(Storage.active.userDefaults.userKeyboards)
XCTAssertNil(Storage.active.userDefaults.userLexicalModels)
}
func testDeinitUnstarted() throws {
guard let package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.silEuroLatinKMP) as? KeyboardKeymanPackage else {
XCTFail()
return
}
let cancelExpectation = XCTestExpectation()
do {
let _ = AssociatingPackageInstaller(for: package,
defaultLanguageCode: "en",
downloadManager: downloadManager,
withAssociators: [.lexicalModels]) { status in
// Since language-picking never began, no "cancelled" signal should occur.
if status == .cancelled {
cancelExpectation.fulfill()
} else {
XCTFail()
}
}
}
wait(for: [cancelExpectation], timeout: 1)
XCTAssertNil(Storage.active.userDefaults.userKeyboards)
XCTAssertNil(Storage.active.userDefaults.userLexicalModels)
}
func testPackageLangCodePartialMatch() throws {
guard let strPackage = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.fvSencotenKMP) as? KeyboardKeymanPackage else {
XCTFail()
return
}
guard let eurolatinPackage = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.silEuroLatinKMP) as? KeyboardKeymanPackage else {
XCTFail()
return
}
let strInstaller = AssociatingPackageInstaller(for: strPackage,
defaultLanguageCode: "str", // correct code: str-latn
downloadManager: downloadManager) { _ in
}
// Package does not contain "str", but does contain "str-latn"
XCTAssertEqual(strInstaller.defaultLgCode, "str-latn")
let eurolatinInstaller = AssociatingPackageInstaller(for: eurolatinPackage,
defaultLanguageCode: "en-fake-bcp", // correct code: "en"
downloadManager: downloadManager) { _ in
}
// Package does not contain "en-fake-bcp", but does contain "en".
XCTAssertEqual(eurolatinInstaller.defaultLgCode, "en")
// Note: the current naive approach isn't exactly BCP-47 subtag aware - it relies on one tag
// containing the entire other tag as a substring.
}
}