blob: 21619e9d22db44979f95476138ed939ee2080d23 [file] [log] [blame]
// Copyright 2016 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 XCTest
@testable import BazelIntegrationTestCase
@testable import TulsiGenerator
// Base class for end-to-end tests that generate xcodeproj bundles and validate them against golden
// versions.
class EndToEndIntegrationTestCase : BazelIntegrationTestCase {
enum Error: Swift.Error {
/// A subdirectory for the Xcode project could not be created.
case testSubdirectoryNotCreated
/// The Xcode project could not be generated.
case projectGenerationFailure(String)
}
let fakeBazelURL = URL(fileURLWithPath: "/fake/tulsi_test_bazel", isDirectory: false)
let testTulsiVersion = "9.99.999.9999"
final func validateDiff(_ diffLines: [String], file: StaticString = #file, line: UInt = #line) {
for diff in diffLines {
XCTFail(diff, file: file, line: line)
}
}
final func diffProjectAt(_ projectURL: URL,
againstGoldenProject resourceName: String,
file: StaticString = #file,
line: UInt = #line) -> [String] {
let bundle = Bundle(for: type(of: self))
let goldenProjectURL = workspaceRootURL.appendingPathComponent(fakeBazelWorkspace
.resourcesPathBase,
isDirectory: true)
.appendingPathComponent("GoldenProjects/\(resourceName).xcodeproj", isDirectory: true)
guard FileManager.default.fileExists(atPath: goldenProjectURL.path) else {
assertionFailure("Missing required test resource file \(resourceName).xcodeproj")
XCTFail("Missing required test resource file \(resourceName).xcodeproj",
file: file,
line: line)
return []
}
var diffOutput = [String]()
let semaphore = DispatchSemaphore(value: 0)
let process = ProcessRunner.createProcess("/usr/bin/diff",
arguments: ["-r",
// For the sake of simplicity in
// maintaining the golden data, copied
// Tulsi artifacts are assumed to have
// been installed correctly.
"--exclude=.tulsi",
projectURL.path,
goldenProjectURL.path]) {
completionInfo in
defer {
semaphore.signal()
}
if let stdout = NSString(data: completionInfo.stdout, encoding: String.Encoding.utf8.rawValue) {
diffOutput = stdout.components(separatedBy: "\n").filter({ !$0.isEmpty })
} else {
XCTFail("No output received for diff command", file: file, line: line)
}
}
process.currentDirectoryPath = workspaceRootURL.path
process.launch()
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
return diffOutput
}
final func copyOutput(source: URL, outputDir: String) throws {
if testUndeclaredOutputsDir != nil {
guard let testOutputURL = makeTestSubdirectory(outputDir,
rootDirectory: testUndeclaredOutputsDir,
cleanupOnTeardown: false) else {
throw Error.testSubdirectoryNotCreated
}
let testOutputProjURL = testOutputURL.appendingPathComponent(source.lastPathComponent)
if FileManager.default.fileExists(atPath: testOutputProjURL.path) {
try FileManager.default.removeItem(at: testOutputProjURL)
}
try FileManager.default.copyItem(at: source, to: testOutputProjURL)
}
}
final func generateProjectNamed(_ projectName: String,
buildTargets: [RuleInfo],
pathFilters: [String],
additionalFilePaths: [String] = [],
outputDir: String,
options: TulsiOptionSet = TulsiOptionSet()) throws -> URL {
if !bazelStartupOptions.isEmpty {
options[.BazelBuildStartupOptionsDebug].projectValue =
bazelStartupOptions.joined(separator: " ")
}
let debugBuildOptions = ["--define=TULSI_TEST=dbg"] + bazelBuildOptions
let releaseBuildOptions = ["--define=TULSI_TEST=rel"] + bazelBuildOptions
options[.BazelBuildOptionsDebug].projectValue = debugBuildOptions.joined(separator: " ")
options[.BazelBuildOptionsRelease].projectValue = releaseBuildOptions.joined(separator: " ")
let bazelURLParam = TulsiParameter(value: fakeBazelURL, source: .explicitlyProvided)
let config = TulsiGeneratorConfig(projectName: projectName,
buildTargets: buildTargets,
pathFilters: Set<String>(pathFilters),
additionalFilePaths: additionalFilePaths,
options: options,
bazelURL: bazelURLParam)
guard let outputFolderURL = makeXcodeProjPath(outputDir) else {
throw Error.testSubdirectoryNotCreated
}
let projectGenerator = TulsiXcodeProjectGenerator(workspaceRootURL: workspaceRootURL,
config: config,
extractorBazelURL: bazelURL,
tulsiVersion: testTulsiVersion)
// Bazel built-in preprocessor defines are suppressed in order to prevent any
// environment-dependent variables from mismatching the golden data.
projectGenerator.xcodeProjectGenerator.suppressCompilerDefines = true
// Don't modify any user defaults.
projectGenerator.xcodeProjectGenerator.suppressModifyingUserDefaults = true
// The username is forced to a known value.
projectGenerator.xcodeProjectGenerator.usernameFetcher = { "_TEST_USER_" }
// The workspace symlink is forced to a known value.
projectGenerator.xcodeProjectGenerator.redactWorkspaceSymlink = true
let errorInfo: String
do {
let generatedProjURL = try projectGenerator.generateXcodeProjectInFolder(outputFolderURL)
try copyOutput(source: generatedProjURL, outputDir: outputDir)
return generatedProjURL
} catch TulsiXcodeProjectGenerator.GeneratorError.unsupportedTargetType(let targetType) {
errorInfo = "Unsupported target type: \(targetType)"
} catch TulsiXcodeProjectGenerator.GeneratorError.serializationFailed(let details) {
errorInfo = "General failure: \(details)"
} catch let error {
errorInfo = "Unexpected failure: \(error)"
}
throw Error.projectGenerationFailure(errorInfo)
}
}