Test the CacheController
This commit is contained in:
parent
1218c461d4
commit
5e0af454d1
|
@ -6,6 +6,9 @@ import TuistCore
|
|||
|
||||
public final class MockCacheStorage: CacheStoraging {
|
||||
var existsStub: ((String) -> Bool)?
|
||||
|
||||
public init() {}
|
||||
|
||||
public func exists(hash: String) -> Single<Bool> {
|
||||
if let existsStub = existsStub {
|
||||
return Single.just(existsStub(hash))
|
||||
|
|
|
@ -5,15 +5,22 @@ import TuistCache
|
|||
import TuistCore
|
||||
|
||||
public final class MockXCFrameworkBuilder: XCFrameworkBuilding {
|
||||
var buildProjectArgs: [(projectPath: AbsolutePath, target: Target)] = []
|
||||
var buildWorkspaceArgs: [(workspacePath: AbsolutePath, target: Target)] = []
|
||||
var buildProjectStub: AbsolutePath?
|
||||
var buildWorkspaceStub: AbsolutePath?
|
||||
public var buildProjectArgs: [(projectPath: AbsolutePath, target: Target)] = []
|
||||
public var buildWorkspaceArgs: [(workspacePath: AbsolutePath, target: Target)] = []
|
||||
public var buildProjectStub: ((AbsolutePath, Target) -> Result<AbsolutePath, Error>)?
|
||||
public var buildWorkspaceStub: ((AbsolutePath, Target) -> Result<AbsolutePath, Error>)?
|
||||
|
||||
public init() {}
|
||||
|
||||
public func build(projectPath: AbsolutePath, target: Target) throws -> Observable<AbsolutePath> {
|
||||
buildProjectArgs.append((projectPath: projectPath, target: target))
|
||||
if let buildProjectStub = buildProjectStub {
|
||||
return Observable.just(buildProjectStub)
|
||||
switch buildProjectStub(projectPath, target) {
|
||||
case let .failure(error):
|
||||
return Observable.error(error)
|
||||
case let .success(path):
|
||||
return Observable.just(path)
|
||||
}
|
||||
} else {
|
||||
return Observable.just(AbsolutePath.root)
|
||||
}
|
||||
|
@ -22,7 +29,12 @@ public final class MockXCFrameworkBuilder: XCFrameworkBuilding {
|
|||
public func build(workspacePath: AbsolutePath, target: Target) throws -> Observable<AbsolutePath> {
|
||||
buildWorkspaceArgs.append((workspacePath: workspacePath, target: target))
|
||||
if let buildWorkspaceStub = buildWorkspaceStub {
|
||||
return Observable.just(buildWorkspaceStub)
|
||||
switch buildWorkspaceStub(workspacePath, target) {
|
||||
case let .failure(error):
|
||||
return Observable.error(error)
|
||||
case let .success(path):
|
||||
return Observable.just(path)
|
||||
}
|
||||
} else {
|
||||
return Observable.just(AbsolutePath.root)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import Foundation
|
||||
import TuistCore
|
||||
@testable import TuistCache
|
||||
|
||||
public final class MockGraphContentHasher: GraphContentHashing {
|
||||
public var contentHashesStub: [TargetNode: String]?
|
||||
|
||||
public init() {}
|
||||
|
||||
public func contentHashes(for _: Graphing) throws -> [TargetNode: String] {
|
||||
contentHashesStub ?? [:]
|
||||
}
|
||||
}
|
|
@ -44,12 +44,21 @@ final class CacheController: CacheControlling {
|
|||
}
|
||||
|
||||
func cache(path: AbsolutePath) throws {
|
||||
// Generate the project.
|
||||
let (path, graph) = try generator.generate(at: path, manifestLoader: manifestLoader, projectOnly: false)
|
||||
let (path, graph) = try generator.generateWorkspace(at: path, manifestLoader: manifestLoader)
|
||||
|
||||
// Getting the hash
|
||||
Printer.shared.print(section: "Hashing cacheable frameworks")
|
||||
let targets: [TargetNode: String] = try graphContentHasher.contentHashes(for: graph)
|
||||
let cacheableTargets = try self.cacheableTargets(graph: graph)
|
||||
|
||||
let completables = try cacheableTargets.map { try buildAndCacheXCFramework(path: path, target: $0.key, hash: $0.value) }
|
||||
_ = try Completable.zip(completables).toBlocking().last()
|
||||
|
||||
Printer.shared.print(success: "All cacheable frameworks have been cached successfully")
|
||||
}
|
||||
|
||||
/// Returns all the targets that are cacheable and their hashes.
|
||||
/// - Parameter graph: Graph that contains all the dependency graph nodes.
|
||||
fileprivate func cacheableTargets(graph: Graphing) throws -> [TargetNode: String] {
|
||||
try graphContentHasher.contentHashes(for: graph)
|
||||
.filter { target, hash in
|
||||
if let exists = try self.cache.exists(hash: hash).toBlocking().first(), exists {
|
||||
Printer.shared.print("The target \(.bold(.raw(target.name))) with hash \(.bold(.raw(hash))) is already in the cache. Skipping...")
|
||||
|
@ -57,28 +66,36 @@ final class CacheController: CacheControlling {
|
|||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var completables: [Completable] = []
|
||||
try targets.forEach { target, hash in
|
||||
// Build targets sequentially
|
||||
let xcframeworkPath: AbsolutePath!
|
||||
if path.extension == "xcworkspace" {
|
||||
xcframeworkPath = try self.xcframeworkBuilder.build(workspacePath: path, target: target.target).toBlocking().single()
|
||||
} else {
|
||||
xcframeworkPath = try self.xcframeworkBuilder.build(projectPath: path, target: target.target).toBlocking().single()
|
||||
}
|
||||
/// Builds the .xcframework for the given target and returns an obervable to store them in the cache.
|
||||
/// - Parameters:
|
||||
/// - path: Path to either the .xcodeproj or .xcworkspace that contains the framework to be cached.
|
||||
/// - target: Target whose .xcframework will be built and cached.
|
||||
/// - hash: Hash of the target.
|
||||
fileprivate func buildAndCacheXCFramework(path: AbsolutePath, target: TargetNode, hash: String) throws -> Completable {
|
||||
// Build targets sequentially
|
||||
let xcframeworkPath: AbsolutePath!
|
||||
|
||||
// Create tasks to cache and delete the xcframeworks asynchronously
|
||||
let deleteXCFrameworkCompletable = Completable.create(subscribe: { completed in
|
||||
try? FileHandler.shared.delete(xcframeworkPath)
|
||||
completed(.completed)
|
||||
return Disposables.create()
|
||||
})
|
||||
completables.append(cache.store(hash: hash, xcframeworkPath: xcframeworkPath).concat(deleteXCFrameworkCompletable))
|
||||
// Note: Since building XCFrameworks involves calling xcodebuild, we run the building process sequentially.
|
||||
if path.extension == "xcworkspace" {
|
||||
xcframeworkPath = try xcframeworkBuilder.build(workspacePath: path, target: target.target).toBlocking().single()
|
||||
} else {
|
||||
xcframeworkPath = try xcframeworkBuilder.build(projectPath: path, target: target.target).toBlocking().single()
|
||||
}
|
||||
|
||||
_ = try Completable.zip(completables).toBlocking().last()
|
||||
|
||||
Printer.shared.print(success: "All cacheable frameworks have been cached successfully")
|
||||
// Create tasks to cache and delete the xcframeworks asynchronously
|
||||
let deleteXCFrameworkCompletable = Completable.create(subscribe: { completed in
|
||||
try? FileHandler.shared.delete(xcframeworkPath)
|
||||
completed(.completed)
|
||||
return Disposables.create()
|
||||
})
|
||||
return cache
|
||||
.store(hash: hash, xcframeworkPath: xcframeworkPath)
|
||||
.concat(deleteXCFrameworkCompletable)
|
||||
.catchError { error in
|
||||
// We propagate the error downstream
|
||||
deleteXCFrameworkCompletable.concat(Completable.error(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,34 +3,36 @@ import Foundation
|
|||
import ProjectDescription
|
||||
@testable import TuistLoader
|
||||
|
||||
final class MockManifestLoader: ManifestLoading {
|
||||
var loadProjectCount: UInt = 0
|
||||
var loadProjectStub: ((AbsolutePath) throws -> ProjectDescription.Project)?
|
||||
public final class MockManifestLoader: ManifestLoading {
|
||||
public var loadProjectCount: UInt = 0
|
||||
public var loadProjectStub: ((AbsolutePath) throws -> ProjectDescription.Project)?
|
||||
|
||||
var loadWorkspaceCount: UInt = 0
|
||||
var loadWorkspaceStub: ((AbsolutePath) throws -> ProjectDescription.Workspace)?
|
||||
public var loadWorkspaceCount: UInt = 0
|
||||
public var loadWorkspaceStub: ((AbsolutePath) throws -> ProjectDescription.Workspace)?
|
||||
|
||||
var manifestsAtCount: UInt = 0
|
||||
var manifestsAtStub: ((AbsolutePath) -> Set<Manifest>)?
|
||||
public var manifestsAtCount: UInt = 0
|
||||
public var manifestsAtStub: ((AbsolutePath) -> Set<Manifest>)?
|
||||
|
||||
var manifestPathCount: UInt = 0
|
||||
var manifestPathStub: ((AbsolutePath, Manifest) throws -> AbsolutePath)?
|
||||
public var manifestPathCount: UInt = 0
|
||||
public var manifestPathStub: ((AbsolutePath, Manifest) throws -> AbsolutePath)?
|
||||
|
||||
var loadSetupCount: UInt = 0
|
||||
var loadSetupStub: ((AbsolutePath) throws -> [Upping])?
|
||||
public var loadSetupCount: UInt = 0
|
||||
public var loadSetupStub: ((AbsolutePath) throws -> [Upping])?
|
||||
|
||||
var loadTuistConfigCount: UInt = 0
|
||||
var loadTuistConfigStub: ((AbsolutePath) throws -> ProjectDescription.TuistConfig)?
|
||||
public var loadTuistConfigCount: UInt = 0
|
||||
public var loadTuistConfigStub: ((AbsolutePath) throws -> ProjectDescription.TuistConfig)?
|
||||
|
||||
func loadProject(at path: AbsolutePath) throws -> ProjectDescription.Project {
|
||||
public init() {}
|
||||
|
||||
public func loadProject(at path: AbsolutePath) throws -> ProjectDescription.Project {
|
||||
try loadProjectStub?(path) ?? ProjectDescription.Project.test()
|
||||
}
|
||||
|
||||
func loadWorkspace(at path: AbsolutePath) throws -> ProjectDescription.Workspace {
|
||||
public func loadWorkspace(at path: AbsolutePath) throws -> ProjectDescription.Workspace {
|
||||
try loadWorkspaceStub?(path) ?? ProjectDescription.Workspace.test()
|
||||
}
|
||||
|
||||
func manifests(at path: AbsolutePath) -> Set<Manifest> {
|
||||
public func manifests(at path: AbsolutePath) -> Set<Manifest> {
|
||||
manifestsAtCount += 1
|
||||
return manifestsAtStub?(path) ?? Set()
|
||||
}
|
||||
|
@ -40,12 +42,12 @@ final class MockManifestLoader: ManifestLoading {
|
|||
return try manifestPathStub?(path, manifest) ?? TemporaryDirectory(removeTreeOnDeinit: true).path
|
||||
}
|
||||
|
||||
func loadSetup(at path: AbsolutePath) throws -> [Upping] {
|
||||
public func loadSetup(at path: AbsolutePath) throws -> [Upping] {
|
||||
loadSetupCount += 1
|
||||
return try loadSetupStub?(path) ?? []
|
||||
}
|
||||
|
||||
func loadTuistConfig(at path: AbsolutePath) throws -> TuistConfig {
|
||||
public func loadTuistConfig(at path: AbsolutePath) throws -> TuistConfig {
|
||||
loadTuistConfigCount += 1
|
||||
return try loadTuistConfigStub?(path) ?? ProjectDescription.TuistConfig.test()
|
||||
}
|
||||
|
|
|
@ -1,15 +1,94 @@
|
|||
import Basic
|
||||
import Foundation
|
||||
import TuistCacheTesting
|
||||
import TuistCore
|
||||
import TuistCoreTesting
|
||||
import TuistLoader
|
||||
import TuistLoaderTesting
|
||||
import TuistSupport
|
||||
import TuistSupportTesting
|
||||
import XCTest
|
||||
|
||||
@testable import TuistKit
|
||||
@testable import TuistSupportTesting
|
||||
|
||||
final class CacheControllerTests: XCTestCase {
|
||||
final class CacheControllerTests: TuistUnitTestCase {
|
||||
var generator: MockGenerator!
|
||||
var graphContentHasher: MockGraphContentHasher!
|
||||
var xcframeworkBuilder: MockXCFrameworkBuilder!
|
||||
var manifestLoader: MockManifestLoader!
|
||||
var cache: MockCacheStorage!
|
||||
var subject: CacheController!
|
||||
|
||||
override func setUp() {
|
||||
generator = MockGenerator()
|
||||
xcframeworkBuilder = MockXCFrameworkBuilder()
|
||||
cache = MockCacheStorage()
|
||||
manifestLoader = MockManifestLoader()
|
||||
graphContentHasher = MockGraphContentHasher()
|
||||
subject = CacheController(generator: generator,
|
||||
manifestLoader: manifestLoader,
|
||||
xcframeworkBuilder: xcframeworkBuilder,
|
||||
cache: cache,
|
||||
graphContentHasher: graphContentHasher)
|
||||
|
||||
super.setUp()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
super.tearDown()
|
||||
generator = nil
|
||||
xcframeworkBuilder = nil
|
||||
graphContentHasher = nil
|
||||
manifestLoader = nil
|
||||
cache = nil
|
||||
subject = nil
|
||||
}
|
||||
|
||||
func test_cache_builds_and_caches_the_frameworks() throws {
|
||||
// Given
|
||||
let path = try temporaryPath()
|
||||
let xcworkspacePath = path.appending(component: "Project.xcworkspace")
|
||||
let cache = GraphLoaderCache()
|
||||
let graph = Graph.test(cache: cache)
|
||||
let project = Project.test(path: path, name: "Cache")
|
||||
let aTarget = Target.test(name: "A")
|
||||
let bTarget = Target.test(name: "B")
|
||||
let axcframeworkPath = path.appending(component: "A.xcframework")
|
||||
let bxcframeworkPath = path.appending(component: "B.xcframework")
|
||||
try FileHandler.shared.createFolder(axcframeworkPath)
|
||||
try FileHandler.shared.createFolder(bxcframeworkPath)
|
||||
|
||||
let nodeWithHashes = [
|
||||
TargetNode.test(project: project, target: aTarget): "A_HASH",
|
||||
TargetNode.test(project: project, target: bTarget): "B_HASH",
|
||||
]
|
||||
|
||||
manifestLoader.manifestsAtStub = { (loadPath: AbsolutePath) -> Set<Manifest> in
|
||||
XCTAssertEqual(loadPath, path)
|
||||
return Set(arrayLiteral: .project)
|
||||
}
|
||||
generator.generateProjectWorkspaceStub = { (loadPath, _) -> (AbsolutePath, Graphing) in
|
||||
XCTAssertEqual(loadPath, path)
|
||||
return (xcworkspacePath, graph)
|
||||
}
|
||||
graphContentHasher.contentHashesStub = nodeWithHashes
|
||||
|
||||
xcframeworkBuilder.buildWorkspaceStub = { _xcworkspacePath, target in
|
||||
switch (_xcworkspacePath, target) {
|
||||
case (xcworkspacePath, aTarget): return .success(axcframeworkPath)
|
||||
case (xcworkspacePath, bTarget): return .success(bxcframeworkPath)
|
||||
default: return .failure(TestError("Received invalid Xcode project path or target"))
|
||||
}
|
||||
}
|
||||
|
||||
try subject.cache(path: path)
|
||||
|
||||
// Then
|
||||
XCTAssertPrinterOutputContains("""
|
||||
Hashing cacheable frameworks
|
||||
All cacheable frameworks have been cached successfully
|
||||
""")
|
||||
XCTAssertFalse(FileHandler.shared.exists(axcframeworkPath))
|
||||
XCTAssertFalse(FileHandler.shared.exists(bxcframeworkPath))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue