1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
|
//
// BuildDBBindingsTests.swift
// llbuildSwiftTests
//
// Copyright © 2019 Apple Inc. All rights reserved.
//
import XCTest
// The Swift package has llbuildSwift as module
#if SWIFT_PACKAGE
import llbuild
import llbuildSwift
#else
import llbuild
#endif
private func createExampleBuildDB(at path: String, file: StaticString = #file, line: UInt = #line) throws {
typealias Compute = ([BuildValue]) -> BuildValue
enum Keys {
static let A = Key(BuildKey.Command(name: "A"))
static let B = Key(BuildKey.Target(name: "B"))
static let C = Key(BuildKey.Node(path: "C"))
}
enum FileInfo {
static let A = BuildValue.FileInfo()
static let B = BuildValue.FileInfo()
static let C = BuildValue.FileInfo()
}
class ExampleTask: Task {
let inputs: [Key]
private var values: [Int: BuildValue] = [:]
let compute: Compute
init(inputs: [Key], compute: @escaping Compute) {
self.inputs = inputs
self.compute = compute
}
func start(_ engine: TaskBuildEngine) {
for (index, input) in inputs.enumerated() {
engine.taskNeedsInput(input, inputID: index)
}
}
func provideValue(_ engine: TaskBuildEngine, inputID: Int, value: Value) {
values[inputID] = BuildValue.construct(from: value)
}
func inputsAvailable(_ engine: TaskBuildEngine) {
let inputValues = inputs.indices.map { self.values[$0]! }
engine.taskIsComplete(Value(self.compute(inputValues).valueData))
}
}
class ExampleRule: Rule {
let inputs: [Key]
let compute: Compute
init(inputs: [Key] = [], compute: @escaping Compute) {
self.inputs = inputs
self.compute = compute
}
func createTask() -> Task {
return ExampleTask(inputs: inputs, compute: compute)
}
}
class ExampleDelegate: BuildEngineDelegate {
func lookupRule(_ key: Key) -> Rule {
switch key {
case Keys.A: return ExampleRule { _ in BuildValue.SuccessfulCommand(outputInfos: [FileInfo.A]) }
case Keys.B: return ExampleRule { _ in BuildValue.SuccessfulCommand(outputInfos: [FileInfo.B]) }
case Keys.C: return ExampleRule(inputs: [Keys.A, Keys.B]) { values in
BuildValue.SuccessfulCommand(outputInfos: values.flatMap { ($0 as? BuildValue.SuccessfulCommand)?.outputInfos ?? [FileInfo.C] })
}
default: fatalError("Unexpected key: \(key.toString())")
}
}
}
let delegate = ExampleDelegate()
let engine = BuildEngine(delegate: delegate)
try engine.attachDB(path: path, schemaVersion: 9)
let result = engine.build(key: Keys.C)
XCTAssertNotNil(BuildValue.construct(from: result) as? BuildValue.SuccessfulCommand, file: file, line: line)
}
class BuildDBBindingsTests: XCTestCase {
let exampleBuildDBClientSchemaVersion: UInt32 = 9
var exampleBuildDBPath: String {
return "\(self.tmpDirectory!)/build.db"
}
func exampleDB(path: String, file: StaticString = #file, line: UInt = #line) throws -> BuildDB {
try createExampleBuildDB(at: path, file: file, line: line)
return try BuildDB(path: path, clientSchemaVersion: exampleBuildDBClientSchemaVersion)
}
var tmpDirectory: String!
override func setUp() {
super.setUp()
let tmpDir = "/tmp/llbuild-test/\(self.name)"
// We intentionally ignore errors here since the directory might not exist yet
do { try FileManager.default.removeItem(atPath: tmpDir) } catch {}
do {
try FileManager.default.createDirectory(atPath: tmpDir, withIntermediateDirectories: true)
} catch {
fatalError("Could not create or remove temporary directory for test case BuildDBBindingsTests at path: \(tmpDir)")
}
self.tmpDirectory = tmpDir
}
func testCouldNotOpenDatabaseErrors() {
func expectCouldNotOpenError(path: String, clientSchemaVersion: UInt32 = 0, expectedError: String, file: StaticString = #file, line: UInt = #line) {
do {
_ = try BuildDB(path: path, clientSchemaVersion: clientSchemaVersion)
XCTFail("Expected to throw error with not existing database path.", file: file, line: line)
} catch BuildDB.Error.couldNotOpenDB(error: let got) {
XCTAssertEqual(got, expectedError, file: file, line: line)
} catch {
XCTFail("Unexpected error while opening non existing database: \(error)", file: file, line: line)
}
}
expectCouldNotOpenError(path: "/tmp/invalid/path",
expectedError: "Database at path '/tmp/invalid/path' does not exist.")
expectCouldNotOpenError(path: "/tmp",
expectedError: "Path '/tmp' exists, but is a directory.")
// Create the example database for the following tests
XCTAssertNoThrow(try createExampleBuildDB(at: exampleBuildDBPath))
expectCouldNotOpenError(path: exampleBuildDBPath,
clientSchemaVersion: 8,
expectedError: "Version mismatch. (database-schema: 17 requested schema: 17. database-client: \(exampleBuildDBClientSchemaVersion) requested client: 8)")
XCTAssertNoThrow(try BuildDB(path: exampleBuildDBPath, clientSchemaVersion: exampleBuildDBClientSchemaVersion))
}
func testGetKeys() throws {
let db = try exampleDB(path: exampleBuildDBPath)
try withExtendedLifetime(db) {
let keys = try db.getKeys()
XCTAssertEqual(keys.count, 3)
XCTAssertEqual(keys[0], BuildKey.Target(name: "B"))
XCTAssertEqual(keys[1], BuildKey.Command(name: "A"))
XCTAssertEqual(keys[2], BuildKey.Node(path: "C"))
}
}
func testGetResults() throws {
let db = try exampleDB(path: exampleBuildDBPath)
try withExtendedLifetime(db) {
guard let result1 = try db.lookupRuleResult(buildKey: BuildKey.Node(path: "C")) else {
XCTFail("Expected to get result for build key C.")
return
}
XCTAssertGreaterThan(result1.start, 0)
XCTAssertGreaterThanOrEqual(result1.end, result1.start)
XCTAssertEqual(result1.dependencies.count, 2)
XCTAssertEqual(result1.dependencies[0], BuildKey.Command(name: "A"))
XCTAssertEqual(result1.dependencies[1], BuildKey.Target(name: "B"))
XCTAssertEqual(result1.value, BuildValue.SuccessfulCommand(outputInfos: [BuildValue.SuccessfulCommand.FileInfo(), BuildValue.SuccessfulCommand.FileInfo()]))
guard let result2 = try db.lookupRuleResult(buildKey: BuildKey.Target(name: "B")) else {
XCTFail("Expected to get result for build key B.")
return
}
XCTAssertGreaterThan(result2.start, 0)
XCTAssertGreaterThanOrEqual(result2.end, result2.start)
XCTAssertTrue(result2.dependencies.isEmpty)
XCTAssertEqual(result2.value, BuildValue.SuccessfulCommand(outputInfos: [BuildValue.SuccessfulCommand.FileInfo()]))
guard let result3 = try db.lookupRuleResult(buildKey: BuildKey.Command(name: "A")) else {
XCTFail("Expected to get result for build key A.")
return
}
XCTAssertGreaterThan(result3.start, 0)
XCTAssertGreaterThanOrEqual(result3.end, result3.start)
XCTAssertTrue(result3.dependencies.isEmpty)
XCTAssertEqual(result3.value, BuildValue.SuccessfulCommand(outputInfos: [BuildValue.SuccessfulCommand.FileInfo()]))
}
}
func testRuleResult() throws {
let deps = [BuildKey.CustomTask(name: "name", taskData: "taskData"), BuildKey.Command(name: "command")]
let result = RuleResult(value: BuildValue.FailedCommand(), signature: 0xff, computedAt: 1, builtAt: 2, start: 3, end: 4, dependencies: deps)
XCTAssertEqual(result.value, BuildValue.FailedCommand())
XCTAssertEqual(result.signature, 0xff)
XCTAssertEqual(result.computedAt, 1)
XCTAssertEqual(result.builtAt, 2)
XCTAssertEqual(result.start, 3)
XCTAssertEqual(result.end, 4)
XCTAssertEqual(result.dependencies.count, 2)
XCTAssertEqual(result.dependencies, deps)
}
}
|