Black box end-to-end test to generate Buttons.
Generates an xcodeproj for Buttons and runs tests to ensure project works.
PiperOrigin-RevId: 212677610
diff --git a/Tulsi.tulsiproj/Configs/Tulsi.tulsigen b/Tulsi.tulsiproj/Configs/Tulsi.tulsigen
index 8f8f69b..4d8c99a 100644
--- a/Tulsi.tulsiproj/Configs/Tulsi.tulsigen
+++ b/Tulsi.tulsiproj/Configs/Tulsi.tulsigen
@@ -3,6 +3,7 @@
"src/..."
],
"buildTargets" : [
+ "//src/TulsiEndToEndTests:TulsiEndToEndTest",
"//src/TulsiGeneratorIntegrationTests:AspectTests",
"//src/TulsiGeneratorIntegrationTests:EndToEndGenerationTests",
"//src/TulsiGeneratorIntegrationTests:PlatformDependentEndToEndGenerationTests",
diff --git a/src/TulsiEndToEndTests/BUILD b/src/TulsiEndToEndTests/BUILD
new file mode 100644
index 0000000..cbe6f7c
--- /dev/null
+++ b/src/TulsiEndToEndTests/BUILD
@@ -0,0 +1,17 @@
+licenses(["notice"]) # Apache 2.0
+
+load("//src/TulsiGeneratorIntegrationTests:tulsi_integration_test.bzl", "tulsi_integration_test")
+
+test_suite(
+ name = "TulsiEndToEndTests",
+)
+
+tulsi_integration_test(
+ name = "TulsiEndToEndTest",
+ srcs = ["TulsiEndToEndTest.swift"],
+ data = [
+ "Resources/Buttons.tulsiproj",
+ "//:tulsi.zip",
+ "@build_bazel_rules_apple//examples/multi_platform/Buttons:all_files",
+ ],
+)
diff --git a/src/TulsiEndToEndTests/Resources/Buttons.tulsiproj/Configs/Buttons.tulsigen b/src/TulsiEndToEndTests/Resources/Buttons.tulsiproj/Configs/Buttons.tulsigen
new file mode 100644
index 0000000..a1ec320
--- /dev/null
+++ b/src/TulsiEndToEndTests/Resources/Buttons.tulsiproj/Configs/Buttons.tulsigen
@@ -0,0 +1,50 @@
+{
+ "sourceFilters" : [
+ ],
+ "buildTargets" : [
+ "\/\/build_bazel_rules_apple/examples\/multi_platform\/Buttons:ButtonsTests",
+ "\/\/build_bazel_rules_apple/examples\/multi_platform\/Buttons:ButtonsUITests"
+ ],
+ "projectName" : "Buttons",
+ "optionSet" : {
+ "BazelBuildOptionsDebug" : {
+ "p" : "$(inherited)"
+ },
+ "BazelBuildStartupOptionsRelease" : {
+ "p" : "$(inherited)"
+ },
+ "LaunchActionPreActionScript" : {
+ "p" : "$(inherited)"
+ },
+ "BazelBuildOptionsRelease" : {
+ "p" : "$(inherited)"
+ },
+ "EnvironmentVariables" : {
+ "p" : "$(inherited)"
+ },
+ "BuildActionPreActionScript" : {
+ "p" : "$(inherited)"
+ },
+ "CommandlineArguments" : {
+ "p" : "$(inherited)"
+ },
+ "TestActionPreActionScript" : {
+ "p" : "$(inherited)"
+ },
+ "TestActionPostActionScript" : {
+ "p" : "$(inherited)"
+ },
+ "BuildActionPostActionScript" : {
+ "p" : "$(inherited)"
+ },
+ "BazelBuildStartupOptionsDebug" : {
+ "p" : "$(inherited)"
+ },
+ "LaunchActionPostActionScript" : {
+ "p" : "$(inherited)"
+ }
+ },
+ "additionalFilePaths" : [
+ "build_bazel_rules_apple/examples\/multi_platform\/Buttons\/BUILD"
+ ]
+}
diff --git a/src/TulsiEndToEndTests/Resources/Buttons.tulsiproj/project.tulsiconf b/src/TulsiEndToEndTests/Resources/Buttons.tulsiproj/project.tulsiconf
new file mode 100644
index 0000000..339f649
--- /dev/null
+++ b/src/TulsiEndToEndTests/Resources/Buttons.tulsiproj/project.tulsiconf
@@ -0,0 +1,17 @@
+{
+ "configDefaults" : {
+ "optionSet" : {
+ "BazelBuildOptionsDebug" : {
+ "p" : "--ios_minimum_os=9.0 --workspace_status_command=/usr/bin/true"
+ },
+ "BazelBuildOptionsRelease" : {
+ "p" : "--ios_minimum_os=9.0 --workspace_status_command=/usr/bin/true"
+ }
+ }
+ },
+ "projectName" : "Buttons",
+ "packages" : [
+ "build_bazel_rules_apple/examples\/multi_platform\/Buttons"
+ ],
+ "workspaceRoot" : "..\/..\/..\/..\/..\/.."
+}
diff --git a/src/TulsiEndToEndTests/TulsiEndToEndTest.swift b/src/TulsiEndToEndTests/TulsiEndToEndTest.swift
new file mode 100644
index 0000000..c43091c
--- /dev/null
+++ b/src/TulsiEndToEndTests/TulsiEndToEndTest.swift
@@ -0,0 +1,177 @@
+
+// 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
+
+
+// End to end tests that generate an xcodeproj with the Tulsi binary and runs tests on the xcodeproj
+// to verify it was generated correctly.
+class TulsiEndToEndTest: BazelIntegrationTestCase {
+ let fileManager = FileManager.default
+
+ override func setUp() {
+ super.setUp()
+
+ let runfilesWorkspaceURL = fakeBazelWorkspace.runfilesWorkspaceURL
+
+ // Takes a short path to data files and adds them to the correct location in the workspace.
+ func copyDataToFakeWorkspace(_ path: String) -> Bool {
+ let sourceURL = runfilesWorkspaceURL.appendingPathComponent(path, isDirectory: true)
+ let destURL = workspaceRootURL.appendingPathComponent(path, isDirectory: true)
+ do {
+ if fileManager.fileExists(atPath: destURL.path) {
+ try fileManager.removeItem(at: destURL)
+ }
+ // Symlinks cause issues with Tulsi and Storyboards so must deep copy any data files.
+ try fileManager.deepCopyItem(at: sourceURL, to: destURL)
+ return true
+ } catch let e as NSError {
+ print(e.localizedDescription)
+ return false
+ }
+ }
+
+ if (!copyDataToFakeWorkspace("build_bazel_rules_apple/examples/multi_platform/Buttons")) {
+ XCTFail("Failed to copy Buttons files to fake execroot.")
+ }
+
+ if (!copyDataToFakeWorkspace("src/TulsiEndToEndTests/Resources")) {
+ XCTFail("Failed to copy Buttons tulsiproj to fake execroot.")
+ }
+
+ // Unzip the Tulsi.app bundle to the temp space.
+ let semaphore = DispatchSemaphore(value: 0)
+ let tulsiZipPath = "tulsi.zip"
+ let tulsiZipURL = runfilesWorkspaceURL.appendingPathComponent(tulsiZipPath, isDirectory: false)
+ let process = TulsiProcessRunner.createProcess("/usr/bin/unzip",
+ arguments: [tulsiZipURL.path,
+ "-d",
+ workspaceRootURL.path]) {
+ completionInfo in
+ if let error = String(data: completionInfo.stderr, encoding: .utf8), !error.isEmpty {
+ XCTFail(error)
+ }
+ semaphore.signal()
+ }
+ process.launch()
+ _ = semaphore.wait(timeout: DispatchTime.distantFuture)
+ }
+
+ func generateXcodeProject(tulsiProject path: String, config: String) -> URL{
+ let tulsiBinURL = workspaceRootURL.appendingPathComponent("Tulsi.app/Contents/MacOS/Tulsi", isDirectory: false)
+ XCTAssert(fileManager.fileExists(atPath: tulsiBinURL.path), "Tulsi binary is missing.")
+
+ let projectURL = workspaceRootURL.appendingPathComponent(path, isDirectory: true)
+ XCTAssert(fileManager.fileExists(atPath: projectURL.path), "Tulsi project is missing.")
+ let configPath = projectURL.path + ":" + config
+
+ // Generate Xcode project with Tulsi.
+ let semaphore = DispatchSemaphore(value: 0)
+ let process = TulsiProcessRunner.createProcess(tulsiBinURL.path,
+ arguments: ["--",
+ "--genconfig",
+ configPath,
+ "--outputfolder",
+ workspaceRootURL.path,
+ "--bazel",
+ bazelURL.path,
+ "--no-open-xcode"]) {
+ completionInfo in
+ if let stdoutput = String(data: completionInfo.stdout, encoding: .utf8) {
+ print(stdoutput)
+ }
+ if let erroutput = String(data: completionInfo.stderr, encoding: .utf8) {
+ print(erroutput)
+ }
+ semaphore.signal()
+ }
+ process.launch()
+ _ = semaphore.wait(timeout: DispatchTime.distantFuture)
+
+ let filename = TulsiGeneratorConfig.sanitizeFilename("\(config).xcodeproj")
+ let xcodeProjectURL = workspaceRootURL.appendingPathComponent(filename, isDirectory: true)
+ return xcodeProjectURL
+ }
+
+ func testXcodeProject(_ xcodeProjectURL: URL, scheme: String) {
+ // Run Xcode tests.
+ let semaphore = DispatchSemaphore(value: 0)
+ let xcodeTest = TulsiProcessRunner.createProcess("/usr/bin/xcodebuild",
+ arguments: ["test",
+ "-project",
+ xcodeProjectURL.path,
+ "-scheme",
+ scheme,
+ "-destination",
+ "platform=iOS Simulator,name=iPhone 8,OS=11.2"]) {
+ completionInfo in
+ if let stdoutput = String(data: completionInfo.stdout, encoding: .utf8),
+ let result = stdoutput.split(separator: "\n").last {
+ XCTAssertEqual(String(result), "** TEST SUCCEEDED **", "xcodebuild did not return test success.")
+ } else if let error = String(data: completionInfo.stderr, encoding: .utf8), !error.isEmpty {
+ XCTFail(error)
+ } else {
+ XCTFail("Xcode project tests did not return success.")
+ }
+ semaphore.signal()
+ }
+ xcodeTest.launch()
+ _ = semaphore.wait(timeout: DispatchTime.distantFuture)
+ }
+
+ func testButtons() throws {
+ let buttonsProjectPath = "src/TulsiEndToEndTests/Resources/Buttons.tulsiproj"
+ let xcodeProjectURL = generateXcodeProject(tulsiProject: buttonsProjectPath,
+ config: "Buttons")
+ XCTAssert(fileManager.fileExists(atPath: xcodeProjectURL.path), "Xcode project was not generated.")
+ testXcodeProject(xcodeProjectURL, scheme: "ButtonsTests")
+ }
+
+ func testInvalidConfig() throws {
+ let buttonsProjectPath = "src/TulsiEndToEndTests/Resources/Buttons.tulsiproj"
+ let xcodeProjectURL = generateXcodeProject(tulsiProject: buttonsProjectPath,
+ config: "InvalidConfig")
+ XCTAssertFalse(fileManager.fileExists(atPath: xcodeProjectURL.path), "Xcode project was generated despite invalid config.")
+ }
+}
+
+extension FileManager {
+ // Performs a deep copy of the item at sourceURL, resolving any symlinks along the way.
+ func deepCopyItem(at sourceURL: URL, to destURL: URL) throws {
+ do {
+ try self.createDirectory(atPath: destURL.deletingLastPathComponent().path, withIntermediateDirectories: true)
+ try self.copyItem(at: sourceURL, to: destURL)
+
+ let path = destURL.path
+ if let paths = self.subpaths(atPath: path) {
+ for subpath in paths {
+ let fullSubpath = path + "/" + subpath
+ if let attributes = try? self.attributesOfItem(atPath: fullSubpath) {
+ // If a file is a symbolic link, find the original file, remove the symlink, and copy
+ // over the original file.
+ if attributes[FileAttributeKey.type] as? FileAttributeType == FileAttributeType.typeSymbolicLink {
+ let resolvedPath = try self.destinationOfSymbolicLink(atPath: fullSubpath)
+ try self.removeItem(atPath: fullSubpath)
+ try self.copyItem(atPath: resolvedPath, toPath: fullSubpath)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/TulsiGenerator/TulsiApplicationSupport.swift b/src/TulsiGenerator/TulsiApplicationSupport.swift
index 0bf9b82..1208c35 100644
--- a/src/TulsiGenerator/TulsiApplicationSupport.swift
+++ b/src/TulsiGenerator/TulsiApplicationSupport.swift
@@ -20,6 +20,10 @@
let tulsiFolder: URL
init?(fileManager: FileManager = .default) {
+ // Fail if we are running in a test so that we don't install files to ~/Library/Application Support.
+ if ProcessInfo.processInfo.environment["TEST_SRCDIR"] != nil {
+ return nil
+ }
/// Fetching the appName this way will result in failure for our tests, which is intentional as
/// we don't want to install files to ~/Library/Application Support when testing.
guard let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String else { return nil }
diff --git a/src/TulsiGeneratorIntegrationTests/BUILD b/src/TulsiGeneratorIntegrationTests/BUILD
index 7c54018..706166e 100644
--- a/src/TulsiGeneratorIntegrationTests/BUILD
+++ b/src/TulsiGeneratorIntegrationTests/BUILD
@@ -17,6 +17,7 @@
resources = [
"//:strings",
],
+ visibility = ["//:__subpackages__"],
deps = ["//src/TulsiGenerator:tulsi_generator_lib"],
)
diff --git a/src/TulsiGeneratorIntegrationTests/BazelFakeWorkspace.swift b/src/TulsiGeneratorIntegrationTests/BazelFakeWorkspace.swift
index 532f610..907b0bb 100644
--- a/src/TulsiGeneratorIntegrationTests/BazelFakeWorkspace.swift
+++ b/src/TulsiGeneratorIntegrationTests/BazelFakeWorkspace.swift
@@ -19,6 +19,7 @@
class BazelFakeWorkspace {
let resourcesPathBase = "src/TulsiGeneratorIntegrationTests/Resources"
var runfilesURL: URL
+ var runfilesWorkspaceURL: URL
var fakeExecroot: URL
var workspaceRootURL: URL
var bazelURL: URL
@@ -26,6 +27,7 @@
init(runfilesURL: URL, tempDirURL: URL) {
self.runfilesURL = runfilesURL
+ self.runfilesWorkspaceURL = runfilesURL.appendingPathComponent("__main__", isDirectory: true)
self.fakeExecroot = tempDirURL.appendingPathComponent("fake_execroot", isDirectory: true)
self.workspaceRootURL = fakeExecroot.appendingPathComponent("__main__", isDirectory: true)
self.bazelURL = BazelLocator.bazelURL!