blob: 8289b05352d91863baed5874ddf5948685aae7c6 [file] [log] [blame]
// Copyright 2021 The Tulsi Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
import XCTest
@testable import ModuleCachePruner
/// Helper function that asserts that two sets are equal and prints the unique values in each set
/// when they are not.
func assertSetsEqual<T>(_ a: Set<T>, _ b: Set<T>) {
if a != b {
XCTFail(
"""
Set A=\(a) is not equal to Set B=\(b).
Set A - B = \(a.subtracting(b)).
Set B - A = \(b.subtracting(a)).
""")
}
}
enum TestSetupError: Error {
case noMetadataFile(msg: String)
case noModuleCache(msg: String)
}
func createFakes(
metadataFileWithExplicitModules explicitModules: [FakeModule],
moduleCacheDirectoryWith moduleCache: (
swiftModules: [FakeModule], clangModules: [String: [FakeModule]]
)
) throws -> (metadataFile: URL, moduleCache: URL) {
var fakeMetadataFile: URL
do {
fakeMetadataFile = try createFakeMetadataFile(withExplicitModules: explicitModules)
} catch {
throw TestSetupError.noMetadataFile(
msg: "Failed to create required fake metadata file: \(error)")
}
guard
let fakeModuleCache = createFakeModuleCache(
withSwiftModules: moduleCache.swiftModules,
andClangModules: moduleCache.clangModules)
else {
throw TestSetupError.noModuleCache(
msg: "Failed to create fake module cache required for test.")
}
return (fakeMetadataFile, fakeModuleCache)
}
class ModuleCachePrunerTests: XCTestCase {
let modules = FakeModules()
var fakeMetadataFile: URL?
var fakeModuleCacheURL: URL?
private let directoryHash1 = "ABCDEFGH"
private let directoryHash2 = "12345678"
override func tearDown() {
if let fakeMetadataFile = fakeMetadataFile {
try? FileManager.default.removeItem(at: fakeMetadataFile)
}
if let moduleCacheURL = fakeModuleCacheURL {
try? FileManager.default.removeItem(at: moduleCacheURL)
}
}
private func implicitModuleCacheURLs(
_ moduleCacheURL: URL, forModulesInDirectories modulesByDirectory: [String: [FakeModule]]
) -> Set<URL> {
var result = Set<URL>()
for (subdirectoryName, modules) in modulesByDirectory {
let subdirectoryPath = moduleCacheURL.appendingPathComponent(subdirectoryName)
for module in modules {
result.insert(subdirectoryPath.appendingPathComponent(module.implicitFilename))
}
}
return result
}
func testModuleCachePruning() {
let fakes: (metadataFile: URL, moduleCache: URL)
do {
fakes = try createFakes(
metadataFileWithExplicitModules: [
modules.system.foundation, modules.system.coreFoundation, modules.user.buttonsLib,
modules.user.buttonsIdentity,
],
moduleCacheDirectoryWith: (
swiftModules: [
modules.system.foundation,
modules.system.coreFoundation,
modules.system.darwin,
],
clangModules: [
directoryHash1: [
modules.user.buttonsLib, modules.user.buttonsIdentity,
modules.user.buttonsModel,
],
directoryHash2: [
modules.system.foundation, modules.system.coreFoundation,
modules.system.darwin,
],
]
))
} catch TestSetupError.noModuleCache(let msg), TestSetupError.noMetadataFile(let msg) {
XCTFail(msg)
return
} catch {
XCTFail(error.localizedDescription)
return
}
fakeMetadataFile = fakes.metadataFile
fakeModuleCacheURL = fakes.moduleCache
guard
let actualModulesRemovedFromModuleCache = pruneModuleCache(
moduleCachePath: fakes.moduleCache.path, explicitModuleMetadataFile: fakes.metadataFile.path
)
else {
XCTFail("Module cache pruning returned nil but expected a list")
return
}
let expectedModulesRemovedFromModuleCache = implicitModuleCacheURLs(
fakes.moduleCache,
forModulesInDirectories: [
directoryHash1: [modules.user.buttonsLib, modules.user.buttonsIdentity],
directoryHash2: [modules.system.foundation, modules.system.coreFoundation],
])
assertSetsEqual(Set(actualModulesRemovedFromModuleCache), expectedModulesRemovedFromModuleCache)
let actualFilenamesRemainingInModuleCache = getDirectoryContentsWithRelativePaths(
directory: fakes.moduleCache)
let expectedFilenamesRemainingInModuleCache = Set([
modules.system.foundation.swiftName, modules.system.coreFoundation.swiftName,
modules.system.darwin.swiftName,
"\(directoryHash1)/\(modules.user.buttonsModel.implicitFilename)",
"\(directoryHash2)/\(modules.system.darwin.implicitFilename)",
directoryHash1, directoryHash2, prunedModulesTokenFilename,
])
assertSetsEqual(actualFilenamesRemainingInModuleCache, expectedFilenamesRemainingInModuleCache)
}
func testModuleCachePruningNegativeCases() {
let fakes: (metadataFile: URL, moduleCache: URL)
do {
// For this test, ensure that there should be no overlap between explicit modules and implicit
// modules.
fakes = try createFakes(
metadataFileWithExplicitModules: [
modules.system.foundation, modules.user.buttonsLib,
],
moduleCacheDirectoryWith: (
swiftModules: [],
clangModules: [
directoryHash1: [
modules.user.buttonsIdentity, modules.user.buttonsModel,
],
directoryHash2: [
modules.system.coreFoundation, modules.system.darwin,
],
]
))
} catch TestSetupError.noModuleCache(let msg), TestSetupError.noMetadataFile(let msg) {
XCTFail(msg)
return
} catch {
XCTFail(error.localizedDescription)
return
}
fakeMetadataFile = fakes.metadataFile
fakeModuleCacheURL = fakes.moduleCache
guard
let modulesRemovedFromModuleCache1 = pruneModuleCache(
moduleCachePath: fakes.moduleCache.path, explicitModuleMetadataFile: fakes.metadataFile.path
)
else {
XCTFail("Module cache pruning returned nil but expected an empty list")
return
}
// There was no overlap so nothing should have been removed.
XCTAssertEqual(modulesRemovedFromModuleCache1.count, 0)
let modulesRemovedFromModuleCache2 = pruneModuleCache(
moduleCachePath: fakes.moduleCache.path, explicitModuleMetadataFile: fakes.metadataFile.path)
// Explicit module outputs did not change so we should have skipped pruning altogether.
XCTAssertNil(modulesRemovedFromModuleCache2)
let metadataFileWithNoExplicitModules: URL
do {
metadataFileWithNoExplicitModules = try createFakeMetadataFile(withExplicitModules: [])
} catch {
XCTFail("Failed to create required fake metadata file: \(error)")
return
}
let modulesRemovedFromModuleCache3 = pruneModuleCache(
moduleCachePath: fakes.moduleCache.path,
explicitModuleMetadataFile: metadataFileWithNoExplicitModules.path)
// Metadata file did not contain any explicit modules so we should have skipped pruning
// altogether.
XCTAssertNil(modulesRemovedFromModuleCache3)
}
}