mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 17:05:34 +00:00
432 lines
19 KiB
Swift
432 lines
19 KiB
Swift
//
|
|
// KeymanPackageTests.swift
|
|
// KeymanEngineTests
|
|
//
|
|
// Created by Joshua Horton on 6/3/20.
|
|
// Copyright © 2020 SIL International. All rights reserved.
|
|
//
|
|
|
|
import XCTest
|
|
@testable import KeymanEngine
|
|
|
|
class KeymanPackageTests: XCTestCase {
|
|
var mockedURLSession: TestUtils.Downloading.URLSessionMock!
|
|
|
|
override func setUp() {
|
|
mockedURLSession = TestUtils.Downloading.URLSessionMock()
|
|
}
|
|
|
|
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 testKeyboardPackage_extractWithoutKmpExtension_succeeds() throws {
|
|
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.kmp")
|
|
try ResourceFileManager.shared.copyWithOverwrite(from: TestUtils.Keyboards.khmerAngkorKMP, to: khmerPackageZip)
|
|
|
|
let destinationFolderURL = cacheDirectory.appendingPathComponent("khmer_angkor")
|
|
|
|
do {
|
|
if let kmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) {
|
|
// Run assertions on the package's kmp.info.
|
|
// Assumes the KMP used for testing here has the same kmp.info used for those tests.
|
|
let kmp_json_testcase = KMPJSONTests()
|
|
kmp_json_testcase.kmp_info_khmer_angkor_assertions(kmp.metadata)
|
|
|
|
XCTAssertNotNil(kmp as? KeyboardKeymanPackage, "Keyboard KMP test extraction did not yield a keyboard package!")
|
|
XCTAssertTrue(kmp.isKeyboard(), "Keyboard KMP test extraction did not yield a keyboard package!")
|
|
XCTAssertEqual(kmp.id, "khmer_angkor", "Incorrect package ID")
|
|
// extracted ok, test kmp
|
|
XCTAssert(kmp.sourceFolder == destinationFolderURL,
|
|
"The KMP's reported 'source folder' should match the specified destination folder")
|
|
} else {
|
|
XCTAssert(false, "KeymanPackage.extract failed")
|
|
}
|
|
} catch {
|
|
XCTFail("KeymanPackage.extract failed with error \(error)")
|
|
}
|
|
}
|
|
|
|
func testKeyboardPackage_clearNonexistentDirectory_doesNothing() throws {
|
|
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
let destinationDirectory = cacheDirectory.appendingPathComponent("doesnotexist")
|
|
|
|
do {
|
|
// clear directory
|
|
try KeymanPackage.clearDirectory(destination: destinationDirectory)
|
|
} catch {
|
|
XCTFail("error clearing the nonexistent directory \(error)")
|
|
}
|
|
}
|
|
|
|
func testKeyboardPackage_clearEmptyDirectory_throwsNoError() throws {
|
|
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
let destinationDirectory = cacheDirectory.appendingPathComponent("destination")
|
|
|
|
do {
|
|
// create directory
|
|
try FileManager.default.createDirectory(
|
|
atPath: destinationDirectory.path,
|
|
withIntermediateDirectories: false,
|
|
attributes: nil
|
|
)
|
|
|
|
// clear directory
|
|
try KeymanPackage.clearDirectory(destination: destinationDirectory)
|
|
} catch {
|
|
XCTFail("error clearing the empty directory \(error)")
|
|
}
|
|
|
|
let fileArray = try FileManager.default.contentsOfDirectory(atPath: destinationDirectory.path)
|
|
XCTAssert(fileArray.count == 0, "directory still contains \(fileArray.count) items")
|
|
}
|
|
|
|
func testKeyboardPackage_clearNonEmptyDirectory_directoryIsEmpty() throws {
|
|
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
let destinationDirectory = cacheDirectory.appendingPathComponent("destination")
|
|
|
|
do {
|
|
// create directory
|
|
try FileManager.default.createDirectory(
|
|
atPath: destinationDirectory.path,
|
|
withIntermediateDirectories: false,
|
|
attributes: nil
|
|
)
|
|
|
|
// add some files
|
|
FileManager.default.createFile(atPath: destinationDirectory.appendingPathComponent("fileone").path, contents: nil)
|
|
FileManager.default.createFile(atPath: destinationDirectory.appendingPathComponent("filetwo").path, contents: nil)
|
|
FileManager.default.createFile(atPath: destinationDirectory.appendingPathComponent("filethree").path, contents: nil)
|
|
|
|
// clear directory
|
|
try KeymanPackage.clearDirectory(destination: destinationDirectory)
|
|
} catch {
|
|
XCTFail("error clearing the empty directory \(error)")
|
|
}
|
|
|
|
let fileArray = try FileManager.default.contentsOfDirectory(atPath: destinationDirectory.path)
|
|
XCTAssert(fileArray.count == 0, "directory still contains \(fileArray.count) items")
|
|
}
|
|
|
|
func testKeyboardPackage_extractTwice_noDuplicateFileError() throws {
|
|
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.kmp")
|
|
try ResourceFileManager.shared.copyWithOverwrite(from: TestUtils.Keyboards.khmerAngkorKMP, to: khmerPackageZip)
|
|
|
|
let destinationFolderURL = cacheDirectory.appendingPathComponent("khmer_angkor")
|
|
|
|
do {
|
|
if let kmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) {
|
|
do {
|
|
// clear directory before second extract
|
|
try KeymanPackage.clearDirectory(destination: destinationFolderURL)
|
|
|
|
if let secondKmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) {
|
|
} else {
|
|
XCTAssert(false, "*** second unzip failed")
|
|
}
|
|
} catch {
|
|
XCTFail("unzip 2 failure with error \(error)")
|
|
}
|
|
} else {
|
|
XCTAssert(false, "*** first unzip failed")
|
|
}
|
|
} catch {
|
|
XCTFail("unzip 1 failure with error \(error)")
|
|
}
|
|
}
|
|
|
|
func testLexicalModelPackageExtraction() throws {
|
|
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
|
let mtntZip = cacheDirectory.appendingPathComponent("nrc.en.mtnt.kmp")
|
|
try ResourceFileManager.shared.copyWithOverwrite(from: TestUtils.LexicalModels.mtntKMP, to: mtntZip)
|
|
|
|
let destinationFolderURL = cacheDirectory.appendingPathComponent("nrc.en.mtnt.model")
|
|
|
|
do {
|
|
if let kmp = try KeymanPackage.extract(fileUrl: mtntZip, destination: destinationFolderURL) {
|
|
// Run assertions on the package's kmp.info.
|
|
// Assumes the KMP used for testing here has the same kmp.info used for those tests.
|
|
let kmp_json_testcase = KMPJSONTests()
|
|
|
|
// As this test takes place after construction of the LexicalModelPackage,
|
|
// the version will be set accordingly, unlike in the other JSON-related tests.
|
|
kmp_json_testcase.kmp_info_nrc_en_mtnt_assertions(kmp.metadata, version: "0.1.4")
|
|
|
|
XCTAssertNotNil(kmp as? LexicalModelKeymanPackage, "Lexical model KMP test extraction yielded a keyboard package!")
|
|
XCTAssertTrue(!kmp.isKeyboard(), "Lexical model KMP test extraction yielded a keyboard package!")
|
|
XCTAssertEqual(kmp.id, "nrc.en.mtnt")
|
|
|
|
// extracted ok, test kmp
|
|
XCTAssert(kmp.sourceFolder == destinationFolderURL,
|
|
"The KMP's reported 'source folder' should match the specified destination folder")
|
|
} else {
|
|
XCTAssert(false, "KeymanPackage.extract failed")
|
|
}
|
|
} catch {
|
|
XCTFail("KeymanPackage.extract failed with error \(error)")
|
|
}
|
|
}
|
|
|
|
func testPackageFindResourceMatch() throws {
|
|
guard let kmp1 = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) as? KeyboardKeymanPackage else {
|
|
XCTFail("Incorrect package type loaded for test")
|
|
return
|
|
}
|
|
let kbd = kmp1.findResource(withID: TestUtils.Keyboards.khmer_angkor.fullID)
|
|
XCTAssertNotNil(kbd)
|
|
XCTAssertEqual(kbd?.packageID, "khmer_angkor", "Keyboard package ID not properly set")
|
|
// This keyboard's not in the specified testing package.
|
|
XCTAssertNil(kmp1.findResource(withID: TestUtils.Keyboards.khmer10.fullID))
|
|
|
|
// Thanks to our package typing hierarchy, it's impossible to even TRY finding
|
|
// a FullLexicalModelID within a KeyboardKeymanPackage!
|
|
|
|
guard let kmp2 = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) as? LexicalModelKeymanPackage else {
|
|
XCTFail("Incorrect package type loaded for test")
|
|
return
|
|
}
|
|
let lm = kmp2.findResource(withID: TestUtils.LexicalModels.mtnt.fullID)
|
|
XCTAssertNotNil(lm)
|
|
XCTAssertEqual(lm?.packageID, "nrc.en.mtnt", "Lexical model ID not properly set")
|
|
|
|
// Thanks to our package typing hierarchy, it's impossible to even TRY finding
|
|
// a FullKeyboardID within a LexicalModelKeymanPackage!
|
|
}
|
|
|
|
func testInstalledPackageDeinit() throws {
|
|
let kmp = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP)
|
|
XCTAssertNotNil(kmp, "Failed to prepare KMP for installation")
|
|
XCTAssertNotNil(kmp as? KeyboardKeymanPackage, "KMP resource type improperly recognized - expected a keyboard package!")
|
|
|
|
try ResourceFileManager.shared.finalizePackageInstall(kmp, isCustom: true)
|
|
|
|
let installedDir = Storage.active.resourceDir(for: TestUtils.Keyboards.khmer_angkor)!
|
|
let installedURL = Storage.active.resourceURL(for: TestUtils.Keyboards.khmer_angkor)!
|
|
|
|
// Intentionally scopes the `package` variable
|
|
do {
|
|
let package = try KeymanPackage.parse(installedDir)!
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: installedURL.path))
|
|
XCTAssertEqual(package.id, "khmer_angkor")
|
|
}
|
|
|
|
// Deinit should have triggered - is everything still in place?
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: installedURL.path))
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: installedDir.path))
|
|
}
|
|
|
|
func testTempPackageDeinit() throws {
|
|
var tempDir: URL
|
|
|
|
// Intentionally scopes the `package` variable.
|
|
do {
|
|
let package = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP)
|
|
XCTAssertNotNil(package, "Failed to prepare KMP for installation")
|
|
XCTAssertNotNil(package as? KeyboardKeymanPackage, "KMP resource type improperly recognized - expected a keyboard package!")
|
|
|
|
tempDir = package.sourceFolder
|
|
|
|
XCTAssertTrue(FileManager.default.fileExists(atPath: tempDir.path))
|
|
}
|
|
|
|
// Deinit should have triggered - were the files automatically cleaned up?
|
|
XCTAssertFalse(FileManager.default.fileExists(atPath: tempDir.path))
|
|
}
|
|
|
|
// Analogous to QueryPackageVersionTests.testMockedBatchFetchParse, but with more analysis applied
|
|
// and more integration.
|
|
func testQueryDistributionStates() throws {
|
|
let expectation = XCTestExpectation(description: "The query completes as expected.")
|
|
|
|
// Test setup
|
|
|
|
let mockedResult = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_case_1, error: nil)
|
|
mockedURLSession!.queueMockResult(.data(mockedResult))
|
|
|
|
let badKbdKey = KeymanPackage.Key(id: "foo", type: .keyboard)
|
|
let badLexKey = KeymanPackage.Key(id: "bar", type: .lexicalModel)
|
|
let packageKeys = [KeymanPackage.Key(forResource: TestUtils.Keyboards.khmer_angkor),
|
|
KeymanPackage.Key(forResource: TestUtils.Keyboards.sil_euro_latin),
|
|
KeymanPackage.Key(id: "foo", type: .keyboard),
|
|
KeymanPackage.Key(forResource: TestUtils.LexicalModels.mtnt),
|
|
KeymanPackage.Key(id: "bar", type: .lexicalModel)]
|
|
|
|
KeymanPackage.queryDistributionStates(for: packageKeys, withSession: mockedURLSession!) { results, error in
|
|
guard error == nil, let results = results else {
|
|
XCTFail()
|
|
expectation.fulfill()
|
|
return
|
|
}
|
|
|
|
let khmer_angkor = KeymanPackage.Key(forResource: TestUtils.Keyboards.khmer_angkor)
|
|
let sil_euro_latin = KeymanPackage.Key(forResource: TestUtils.Keyboards.sil_euro_latin)
|
|
let mtnt = KeymanPackage.Key(forResource: TestUtils.LexicalModels.mtnt)
|
|
XCTAssertEqual(results[khmer_angkor]?.latestVersion, "1.0.6")
|
|
XCTAssertEqual(results[khmer_angkor]?.distributionMethod, .cloud)
|
|
|
|
XCTAssertEqual(results[sil_euro_latin]?.latestVersion, "1.9.1")
|
|
XCTAssertEqual(results[sil_euro_latin]?.distributionMethod, .cloud)
|
|
|
|
XCTAssertEqual(results[mtnt]?.latestVersion, "0.1.4")
|
|
XCTAssertEqual(results[mtnt]?.distributionMethod, .cloud)
|
|
|
|
XCTAssertNotNil(results[badKbdKey])
|
|
XCTAssertEqual(results[badKbdKey]?.distributionMethod, .custom)
|
|
|
|
XCTAssertNotNil(results[badLexKey])
|
|
XCTAssertEqual(results[badLexKey]?.distributionMethod, .custom)
|
|
|
|
// Test that this metadata is stored and persisted (cached) in UserDefaults.
|
|
let userDefaults = Storage.active.userDefaults
|
|
|
|
let cache_khmer_angkor = userDefaults.cachedPackageQueryResult(forPackageKey: khmer_angkor)
|
|
XCTAssertNotNil(cache_khmer_angkor)
|
|
XCTAssertEqual(cache_khmer_angkor?.distributionMethod, results[khmer_angkor]?.distributionMethod)
|
|
XCTAssertEqual(cache_khmer_angkor?.latestVersion, "1.0.6")
|
|
XCTAssertEqual(cache_khmer_angkor?.timestampForLastQuery, results[khmer_angkor]?.timestampForLastQuery)
|
|
|
|
let cache_mtnt = userDefaults.cachedPackageQueryResult(forPackageKey: mtnt)
|
|
XCTAssertNotNil(cache_mtnt)
|
|
XCTAssertEqual(cache_mtnt?.distributionMethod, results[mtnt]?.distributionMethod)
|
|
XCTAssertEqual(cache_mtnt?.latestVersion, "0.1.4")
|
|
XCTAssertEqual(cache_mtnt?.timestampForLastQuery, results[mtnt]?.timestampForLastQuery)
|
|
|
|
let cache_foo = userDefaults.cachedPackageQueryResult(forPackageKey: badKbdKey)
|
|
XCTAssertNotNil(cache_foo)
|
|
XCTAssertEqual(cache_foo?.distributionMethod, results[badKbdKey]?.distributionMethod)
|
|
XCTAssertNil(cache_foo?.latestVersion)
|
|
|
|
expectation.fulfill()
|
|
}
|
|
|
|
wait(for: [expectation], timeout: 5)
|
|
}
|
|
|
|
// Analogous to QueryPackageVersionTests.testMockedBatchFetchParse, but with more analysis applied
|
|
// and more integration.
|
|
func testQueryCurrentVersions() throws {
|
|
let mockedURLSession = TestUtils.Downloading.URLSessionMock()
|
|
|
|
let expectation = XCTestExpectation(description: "The query completes as expected.")
|
|
|
|
// Test setup
|
|
|
|
let mockedResult = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_case_1, error: nil)
|
|
mockedURLSession.queueMockResult(.data(mockedResult))
|
|
|
|
let badKbdKey = KeymanPackage.Key(id: "foo", type: .keyboard)
|
|
let badLexKey = KeymanPackage.Key(id: "bar", type: .lexicalModel)
|
|
let packageKeys = [KeymanPackage.Key(forResource: TestUtils.Keyboards.khmer_angkor),
|
|
KeymanPackage.Key(forResource: TestUtils.Keyboards.sil_euro_latin),
|
|
KeymanPackage.Key(id: "foo", type: .keyboard),
|
|
KeymanPackage.Key(forResource: TestUtils.LexicalModels.mtnt),
|
|
KeymanPackage.Key(id: "bar", type: .lexicalModel)]
|
|
|
|
KeymanPackage.queryCurrentVersions(for: packageKeys, withSession: mockedURLSession) { results, error in
|
|
guard error == nil, let results = results else {
|
|
XCTFail()
|
|
expectation.fulfill()
|
|
return
|
|
}
|
|
|
|
let khmer_angkor = KeymanPackage.Key(forResource: TestUtils.Keyboards.khmer_angkor)
|
|
let sil_euro_latin = KeymanPackage.Key(forResource: TestUtils.Keyboards.sil_euro_latin)
|
|
let mtnt = KeymanPackage.Key(forResource: TestUtils.LexicalModels.mtnt)
|
|
XCTAssertEqual(results[khmer_angkor], Version("1.0.6"))
|
|
XCTAssertEqual(results[sil_euro_latin], Version("1.9.1"))
|
|
XCTAssertEqual(results[mtnt], Version("0.1.4"))
|
|
XCTAssertNil(results[badKbdKey])
|
|
XCTAssertNil(results[badLexKey])
|
|
|
|
expectation.fulfill()
|
|
}
|
|
|
|
wait(for: [expectation], timeout: 5)
|
|
|
|
// Post-execution cleanup
|
|
let queueWasCleared = mockedURLSession.queueIsEmpty
|
|
|
|
if !queueWasCleared {
|
|
throw NSError(domain: "Keyman",
|
|
code: 4,
|
|
userInfo: [NSLocalizedDescriptionKey: "A test did not fully utilize its queued mock results!"])
|
|
}
|
|
}
|
|
|
|
func testVersionState() throws {
|
|
// Step 1: Install khmer_angkor. Fixture: version 1.0.6.
|
|
guard let installPackage = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) as? KeyboardKeymanPackage else {
|
|
XCTFail("Could not load keyboard KMP for test")
|
|
return
|
|
}
|
|
|
|
try ResourceFileManager.shared.install(resourceWithID: TestUtils.Keyboards.khmer_angkor.fullID, from: installPackage)
|
|
|
|
// Step 2: retrieve the installed version of the package. We're currently using the temp version.
|
|
guard let package = ResourceFileManager.shared.getInstalledPackage(withKey: installPackage.key) else {
|
|
XCTFail("Could not load installed form of keyboard KMP for test")
|
|
return
|
|
}
|
|
|
|
// No query has yet occurred - version state unknown.
|
|
XCTAssertEqual(package.versionState, .unknown)
|
|
|
|
// Step 3: Prepare mocking for two separate query rounds.
|
|
let mockedVersionIsCurrent = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km, error: nil) // For Step 4.
|
|
let mockedVersionIsOutdated = TestUtils.Downloading.MockResult(location: TestUtils.Queries.package_version_km_updated, error: nil) // For Step 5.
|
|
mockedURLSession.queueMockResult(.data(mockedVersionIsCurrent))
|
|
mockedURLSession.queueMockResult(.data(mockedVersionIsOutdated))
|
|
|
|
// Step 4: Mocked package-version query returns the same version as we installed.
|
|
let expectationCurrent = XCTestExpectation(description: "Query matching 'current version' completed successfully.")
|
|
|
|
KeymanPackage.queryCurrentVersions(for: [package.key], withSession: mockedURLSession) { _, _ in
|
|
expectationCurrent.fulfill()
|
|
}
|
|
|
|
wait(for: [expectationCurrent], timeout: 5)
|
|
XCTAssertEqual(package.versionState, .upToDate)
|
|
|
|
// Step 5: Mocked package-version query returns an updated version compared to what is installed.
|
|
let expectationUpdate = XCTestExpectation(description: "Query matching 'update available' completed successfully.")
|
|
|
|
KeymanPackage.queryCurrentVersions(for: [package.key], withSession: mockedURLSession) { _, _ in
|
|
expectationUpdate.fulfill()
|
|
}
|
|
|
|
wait(for: [expectationUpdate], timeout: 5)
|
|
XCTAssertEqual(package.versionState, .needsUpdate)
|
|
}
|
|
|
|
func testInstallState() throws {
|
|
// Step 1: Install khmer_angkor. Fixture: version 1.0.6.
|
|
guard let installPackage = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.khmerAngkorKMP) as? KeyboardKeymanPackage else {
|
|
XCTFail("Could not load keyboard KMP for test")
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(installPackage.installState, .pending)
|
|
|
|
try ResourceFileManager.shared.install(resourceWithID: TestUtils.Keyboards.khmer_angkor.fullID, from: installPackage)
|
|
|
|
// Step 2: retrieve an instance for the installation. We're currently using the temp version.
|
|
guard let package = ResourceFileManager.shared.getInstalledPackage(withKey: installPackage.key) else {
|
|
XCTFail("Could not load installed form of keyboard KMP for test")
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(installPackage.installState, .pending) // Reflects that the instance is a temporary extraction.
|
|
XCTAssertEqual(package.installState, .installed) // Reflects the actually-installed package
|
|
}
|
|
}
|