E2E tests for user_build.py
These tests make sure that the flags that user_build.py resolves
for a given target matches that used during project generation
(as well as making sure that the template file is valid).
PiperOrigin-RevId: 204802803
diff --git a/src/TulsiGenerator/BazelAspectInfoExtractor.swift b/src/TulsiGenerator/BazelAspectInfoExtractor.swift
index ada235f..62ecf3f 100644
--- a/src/TulsiGenerator/BazelAspectInfoExtractor.swift
+++ b/src/TulsiGenerator/BazelAspectInfoExtractor.swift
@@ -199,6 +199,7 @@
}
let tulsiFlags = bazelSettingsProvider.tulsiFlags(hasSwift: hasSwift,
+ options: nil,
features: features).getFlags(forDebug: isDbg)
var arguments = startupOptions
arguments.append(contentsOf: tulsiFlags.startup)
diff --git a/src/TulsiGenerator/BazelSettingsProvider.swift b/src/TulsiGenerator/BazelSettingsProvider.swift
index fcfdac0..5d1dd5d 100644
--- a/src/TulsiGenerator/BazelSettingsProvider.swift
+++ b/src/TulsiGenerator/BazelSettingsProvider.swift
@@ -101,7 +101,9 @@
/// Defines an object that provides flags for Bazel invocations.
protocol BazelSettingsProviderProtocol {
/// All general-Tulsi flags, varying based on whether the project has Swift or not.
- func tulsiFlags(hasSwift: Bool, features: Set<BazelSettingFeature>) -> BazelFlagsSet
+ func tulsiFlags(hasSwift: Bool,
+ options: TulsiOptionSet?,
+ features: Set<BazelSettingFeature>) -> BazelFlagsSet
/// Bazel build settings, used during Xcode/user Bazel builds.
func buildSettings(bazel: String,
@@ -169,10 +171,19 @@
self.nonSwiftFlags = nonSwiftFlags
}
- func tulsiFlags(hasSwift: Bool, features: Set<BazelSettingFeature>) -> BazelFlagsSet {
+ func tulsiFlags(hasSwift: Bool,
+ options: TulsiOptionSet?,
+ features: Set<BazelSettingFeature>) -> BazelFlagsSet {
+ let optionFlags: BazelFlagsSet
+ if let options = options {
+ optionFlags = optionsBasedFlags(options)
+ } else {
+ optionFlags = BazelFlagsSet()
+ }
let languageFlags = (hasSwift ? swiftFlags : nonSwiftFlags) + featureFlags(features,
hasSwift: hasSwift)
- return BazelFlagsSet(common: universalFlags) + cacheableFlags + nonCacheableFlags + languageFlags
+ return cacheableFlags + optionFlags + BazelFlagsSet(common: universalFlags) +
+ nonCacheableFlags + languageFlags
}
/// Non-cacheable Bazel flags based off of BazelSettingFeatures for the project.
diff --git a/src/TulsiGeneratorIntegrationTests/EndToEndGenerationTests.swift b/src/TulsiGeneratorIntegrationTests/EndToEndGenerationTests.swift
index d77337d..1746435 100644
--- a/src/TulsiGeneratorIntegrationTests/EndToEndGenerationTests.swift
+++ b/src/TulsiGeneratorIntegrationTests/EndToEndGenerationTests.swift
@@ -123,6 +123,8 @@
return
} catch Error.testSubdirectoryNotCreated {
XCTFail("Failed to create output folder, aborting test.")
+ } catch Error.userBuildScriptInvocationFailure(let info) {
+ XCTFail("Failed to invoke user_build.py script. Context: \(info)")
} catch let error {
XCTFail("Unexpected failure: \(error)")
}
@@ -169,6 +171,8 @@
outputDir: "tulsi_e2e_output",
options: projectOptions)
+ try validateBuildCommandForProject(projectURL, options: projectOptions, targets: [appLabel.value])
+
let diffLines = diffProjectAt(projectURL, againstGoldenProject: projectName)
validateDiff(diffLines)
}
@@ -191,6 +195,7 @@
"bazel-genfiles/..."],
additionalFilePaths: additionalFilePaths,
outputDir: "tulsi_e2e_output")
+ try validateBuildCommandForProject(projectURL, swift: true, targets: [appLabel.value])
let diffLines = diffProjectAt(projectURL, againstGoldenProject: projectName)
validateDiff(diffLines)
@@ -215,6 +220,8 @@
additionalFilePaths: additionalFilePaths,
outputDir: "tulsi_e2e_output")
+ try validateBuildCommandForProject(projectURL, swift: true, targets: [appLabel.value])
+
let diffLines = diffProjectAt(projectURL, againstGoldenProject: projectName)
validateDiff(diffLines)
}
@@ -238,6 +245,8 @@
additionalFilePaths: additionalFilePaths,
outputDir: "tulsi_e2e_output")
+ try validateBuildCommandForProject(projectURL, targets: [appLabel.value])
+
let diffLines = diffProjectAt(projectURL, againstGoldenProject: projectName)
validateDiff(diffLines)
}
@@ -261,6 +270,8 @@
additionalFilePaths: additionalFilePaths,
outputDir: "tulsi_e2e_output")
+ try validateBuildCommandForProject(projectURL, targets: [appLabel.value])
+
let diffLines = diffProjectAt(projectURL, againstGoldenProject: "SkylarkBundlingProject")
validateDiff(diffLines)
}
@@ -288,6 +299,8 @@
additionalFilePaths: additionalFilePaths,
outputDir: "tulsi_e2e_output")
+ try validateBuildCommandForProject(projectURL, targets: [appLabel.value])
+
let diffLines = diffProjectAt(projectURL, againstGoldenProject: projectName)
validateDiff(diffLines)
}
@@ -321,6 +334,8 @@
additionalFilePaths: additionalFilePaths,
outputDir: "tulsi_e2e_output")
+ try validateBuildCommandForProject(projectURL, targets: [appLabel.value])
+
let diffLines = diffProjectAt(projectURL, againstGoldenProject: projectName)
validateDiff(diffLines)
}
@@ -341,6 +356,8 @@
additionalFilePaths: additionalFilePaths,
outputDir: "tulsi_e2e_output")
+ try validateBuildCommandForProject(projectURL, targets: [appLabel.value])
+
let diffLines = diffProjectAt(projectURL, againstGoldenProject: projectName)
validateDiff(diffLines)
}
diff --git a/src/TulsiGeneratorIntegrationTests/EndToEndIntegrationTestCase.swift b/src/TulsiGeneratorIntegrationTests/EndToEndIntegrationTestCase.swift
index 21619e9..8bca3c5 100644
--- a/src/TulsiGeneratorIntegrationTests/EndToEndIntegrationTestCase.swift
+++ b/src/TulsiGeneratorIntegrationTests/EndToEndIntegrationTestCase.swift
@@ -25,8 +25,12 @@
case testSubdirectoryNotCreated
/// The Xcode project could not be generated.
case projectGenerationFailure(String)
+ /// Unable to execute user_build.py script.
+ case userBuildScriptInvocationFailure(String)
}
+ let extraDebugFlags = ["--define=TULSI_TEST=dbg"]
+ let extraReleaseFlags = ["--define=TULSI_TEST=rel"]
let fakeBazelURL = URL(fileURLWithPath: "/fake/tulsi_test_bazel", isDirectory: false)
let testTulsiVersion = "9.99.999.9999"
@@ -96,6 +100,112 @@
}
}
+ final func validateBuildCommandForProject(_ projectURL: URL,
+ swift: Bool = false,
+ options: TulsiOptionSet = TulsiOptionSet(),
+ targets: [String]) throws {
+ let actualDebug = try userBuildCommandForProject(projectURL, release: false, targets: targets)
+ let actualRelease = try userBuildCommandForProject(projectURL, release: true, targets: targets)
+ let (debug, release) = expectedBuildCommands(swift: swift, options: options, targets: targets)
+
+ XCTAssertEqual(actualDebug, debug)
+ XCTAssertEqual(actualRelease, release)
+ }
+
+ final func expectedBuildCommands(swift: Bool,
+ options: TulsiOptionSet,
+ targets: [String]) -> (String, String) {
+ let provider = BazelSettingsProvider(universalFlags: bazelUniversalFlags)
+ let execRoot = workspaceInfoFetcher.getExecutionRoot()
+ let features = BazelBuildSettingsFeatures.enabledFeatures(options: options,
+ workspaceRoot: workspaceRootURL.path,
+ bazelExecRoot: execRoot)
+ let dbg = provider.tulsiFlags(hasSwift: swift, options: options, features: features).debug
+ let rel = provider.tulsiFlags(hasSwift: swift, options: options, features: features).release
+
+ let config: PlatformConfiguration
+ if let identifier = options[.ProjectGenerationPlatformConfiguration].commonValue,
+ let parsedConfig = PlatformConfiguration(identifier: identifier) {
+ config = parsedConfig
+ } else {
+ config = PlatformConfiguration.defaultConfiguration
+ }
+
+ func buildCommand(extraBuildFlags: [String], tulsiFlags: BazelFlags) -> String {
+ var args = [fakeBazelURL.path]
+ args.append(contentsOf: bazelStartupOptions)
+ args.append(contentsOf: tulsiFlags.startup)
+ args.append("build")
+ args.append(contentsOf: extraBuildFlags)
+ args.append(contentsOf: bazelBuildOptions)
+ args.append(contentsOf: config.bazelFlags)
+ args.append(contentsOf: tulsiFlags.build)
+ args.append(contentsOf: targets)
+
+ return args.map { $0.escapingForShell }.joined(separator: " ")
+ }
+
+ let debugCommand = buildCommand(extraBuildFlags: extraDebugFlags, tulsiFlags: dbg)
+ let releaseCommand = buildCommand(extraBuildFlags: extraReleaseFlags, tulsiFlags: rel)
+
+ return (debugCommand, releaseCommand)
+ }
+
+ final func userBuildCommandForProject(_ projectURL: URL,
+ release: Bool = false,
+ targets: [String],
+ file: StaticString = #file,
+ line: UInt = #line) throws -> String {
+ let expectedScriptURL = projectURL.appendingPathComponent(".tulsi/Scripts/user_build.py",
+ isDirectory: false)
+ let fileManager = FileManager.default
+
+ guard fileManager.fileExists(atPath: expectedScriptURL.path) else {
+ throw Error.userBuildScriptInvocationFailure(
+ "user_build.py script not found: expected at path \(expectedScriptURL.path)")
+ }
+
+ var output = "<none>"
+ let semaphore = DispatchSemaphore(value: 0)
+ var args = [
+ "--norun",
+ ]
+ if release {
+ args.append("--release")
+ }
+ args.append(contentsOf: targets)
+
+ let process = ProcessRunner.createProcess(
+ expectedScriptURL.path,
+ arguments: args,
+ messageLogger: localizedMessageLogger
+ ) { completionInfo in
+ defer {
+ semaphore.signal()
+ }
+ let exitcode = completionInfo.terminationStatus
+ guard exitcode == 0 else {
+ let stderr =
+ String(data: completionInfo.stderr, encoding: .utf8) ?? "<no stderr>"
+ XCTFail("user_build.py returned \(exitcode). stderr: \(stderr)", file: file, line: line)
+ return
+ }
+ if let stdout = String(data: completionInfo.stdout, encoding: .utf8)?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines),
+ !stdout.isEmpty {
+ output = stdout
+ } else {
+ let stderr =
+ String(data: completionInfo.stderr, encoding: .utf8) ?? "<no stderr>"
+ XCTFail("user_build.py had no stdout. stderr: \(stderr)", file: file, line: line)
+ }
+ }
+ process.currentDirectoryPath = workspaceRootURL.path
+ process.launch()
+
+ _ = semaphore.wait(timeout: DispatchTime.distantFuture)
+ return output
+ }
+
final func generateProjectNamed(_ projectName: String,
buildTargets: [RuleInfo],
pathFilters: [String],
@@ -103,12 +213,13 @@
outputDir: String,
options: TulsiOptionSet = TulsiOptionSet()) throws -> URL {
if !bazelStartupOptions.isEmpty {
- options[.BazelBuildStartupOptionsDebug].projectValue =
- bazelStartupOptions.joined(separator: " ")
+ let startupFlags = bazelStartupOptions.joined(separator: " ")
+ options[.BazelBuildStartupOptionsDebug].projectValue = startupFlags
+ options[.BazelBuildStartupOptionsRelease].projectValue = startupFlags
}
- let debugBuildOptions = ["--define=TULSI_TEST=dbg"] + bazelBuildOptions
- let releaseBuildOptions = ["--define=TULSI_TEST=rel"] + bazelBuildOptions
+ let debugBuildOptions = extraDebugFlags + bazelBuildOptions
+ let releaseBuildOptions = extraReleaseFlags + bazelBuildOptions
options[.BazelBuildOptionsDebug].projectValue = debugBuildOptions.joined(separator: " ")
options[.BazelBuildOptionsRelease].projectValue = releaseBuildOptions.joined(separator: " ")
diff --git a/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift b/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
index 2313041..5f61302 100644
--- a/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
+++ b/src/TulsiGeneratorTests/MockWorkspaceInfoExtractor.swift
@@ -16,7 +16,10 @@
@testable import TulsiGenerator
class MockBazelSettingsProvider: BazelSettingsProviderProtocol {
- func tulsiFlags(hasSwift: Bool, features: Set<BazelSettingFeature>) -> BazelFlagsSet {
+
+ func tulsiFlags(hasSwift: Bool,
+ options: TulsiOptionSet?,
+ features: Set<BazelSettingFeature>) -> BazelFlagsSet {
return BazelFlagsSet()
}