Decoupled the Buttons specific logic from TulsiEndToEndTest. TulsiEndToEndTest is now just a parent class for other Tulsi end to end tests. Moved the Buttons test cases into a separate dedicated subclass.

PiperOrigin-RevId: 217768811
diff --git a/src/TulsiEndToEndTests/BUILD b/src/TulsiEndToEndTests/BUILD
index 7040e0c..08ea711 100644
--- a/src/TulsiEndToEndTests/BUILD
+++ b/src/TulsiEndToEndTests/BUILD
@@ -1,18 +1,36 @@
 licenses(["notice"])  # Apache 2.0
 
+load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
 load("//src/TulsiGeneratorIntegrationTests:tulsi_integration_test.bzl", "tulsi_integration_test")
 
 test_suite(
     name = "TulsiEndToEndTests",
 )
 
+swift_library(
+    name = "TulsiEndToEndTestBase",
+    srcs = [
+        "TulsiEndToEndTest.swift",
+    ],
+    module_name = "TulsiEndToEndTestBase",
+    deps = [
+        "//src/TulsiGenerator:tulsi_generator_lib",
+        "//src/TulsiGeneratorIntegrationTests:BazelIntegrationTestCase",
+    ],
+)
+
 tulsi_integration_test(
     name = "TulsiEndToEndTest",
     size = "large",
-    srcs = ["TulsiEndToEndTest.swift"],
+    srcs = [
+        "ButtonsEndToEndTest.swift",
+    ],
     data = [
         "Resources/Buttons.tulsiproj",
         "//:tulsi.zip",
         "@build_bazel_rules_apple//examples/multi_platform/Buttons:all_files",
     ],
+    deps = [
+        ":TulsiEndToEndTestBase",
+    ],
 )
diff --git a/src/TulsiEndToEndTests/ButtonsEndToEndTest.swift b/src/TulsiEndToEndTests/ButtonsEndToEndTest.swift
new file mode 100644
index 0000000..d226679
--- /dev/null
+++ b/src/TulsiEndToEndTests/ButtonsEndToEndTest.swift
@@ -0,0 +1,61 @@
+
+// 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 TulsiEndToEndTestBase
+
+
+// End to end test that generates the Buttons project and runs its unit tests.
+class ButtonsEndToEndTest: TulsiEndToEndTest {
+  override func setUp() {
+    super.setUp()
+
+    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.")
+    }
+  }
+
+  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 testButtonsWithCanaryBazel() throws {
+    if let canaryBazelURL = fakeBazelWorkspace.canaryBazelURL {
+      XCTAssert(fileManager.fileExists(atPath: canaryBazelURL.path), "Bazel canary is missing.")
+      bazelURL = canaryBazelURL
+      let buttonsProjectPath = "src/TulsiEndToEndTests/Resources/Buttons.tulsiproj"
+      let xcodeProjectURL = generateXcodeProject(tulsiProject: buttonsProjectPath,
+                                                 config: "Buttons")
+      testXcodeProject(xcodeProjectURL, scheme: "ButtonsLogicTests")
+    }
+  }
+
+  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.")
+  }
+}
+
diff --git a/src/TulsiEndToEndTests/TulsiEndToEndTest.swift b/src/TulsiEndToEndTests/TulsiEndToEndTest.swift
index f8606ff..5a70346 100644
--- a/src/TulsiEndToEndTests/TulsiEndToEndTest.swift
+++ b/src/TulsiEndToEndTests/TulsiEndToEndTest.swift
@@ -18,40 +18,17 @@
 @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.
+// Parent class for end to end tests that generate an xcodeproj with the Tulsi binary and verify the
+// generated xcodeproj by running the projects unit tests.
 class TulsiEndToEndTest: BazelIntegrationTestCase {
   let fileManager = FileManager.default
+  var runfilesWorkspaceURL: URL! = nil
 
   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.")
-    }
+    super.continueAfterFailure = false
+    runfilesWorkspaceURL = fakeBazelWorkspace.runfilesWorkspaceURL
+    XCTAssertNotNil(runfilesWorkspaceURL, "runfilesWorkspaceURL must be not be nil after setup.")
 
     // Unzip the Tulsi.app bundle to the temp space.
     let semaphore = DispatchSemaphore(value: 0)
@@ -71,6 +48,28 @@
     _ = semaphore.wait(timeout: DispatchTime.distantFuture)
   }
 
+  // Takes a short path to data files and adds them to the fake Bazel workspace.
+  func copyDataToFakeWorkspace(_ path: String) -> Bool {
+    let sourceURL = runfilesWorkspaceURL.appendingPathComponent(path, isDirectory: false)
+    let destURL = workspaceRootURL.appendingPathComponent(path, isDirectory: false)
+    do {
+      if(!fileManager.fileExists(atPath: sourceURL.path)) {
+        XCTFail("Source file  \(sourceURL.path) does not exist.")
+      }
+      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
+    }
+  }
+
+  // Runs the Tulsi binary with the given Tulsi project and config to generate an Xcode project.
   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.")
@@ -105,6 +104,7 @@
     let filename = TulsiGeneratorConfig.sanitizeFilename("\(config).xcodeproj")
     let xcodeProjectURL = workspaceRootURL.appendingPathComponent(filename, isDirectory: true)
 
+    // Remove Xcode project after each test method.
     addTeardownBlock {
       do {
         if self.fileManager.fileExists(atPath: xcodeProjectURL.path) {
@@ -119,8 +119,8 @@
     return xcodeProjectURL
   }
 
+  // Runs Xcode tests on the given Xcode project and scheme.
   func testXcodeProject(_ xcodeProjectURL: URL, scheme: String) {
-    // Run Xcode tests.
     let semaphore = DispatchSemaphore(value: 0)
     let xcodeTest = TulsiProcessRunner.createProcess("/usr/bin/xcodebuild",
                                                        arguments: ["test",
@@ -144,32 +144,6 @@
     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 testButtonsWithCanaryBazel() throws {
-    if let canaryBazelURL = fakeBazelWorkspace.canaryBazelURL {
-      XCTAssert(fileManager.fileExists(atPath: canaryBazelURL.path), "Bazel canary is missing.")
-      bazelURL = canaryBazelURL
-      let buttonsProjectPath = "src/TulsiEndToEndTests/Resources/Buttons.tulsiproj"
-      let xcodeProjectURL = generateXcodeProject(tulsiProject: buttonsProjectPath,
-                                                 config: "Buttons")
-      testXcodeProject(xcodeProjectURL, scheme: "ButtonsLogicTests")
-    }
-  }
-
-  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 {
@@ -177,7 +151,15 @@
   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 rootPath = sourceURL.path
+      if let rootAttributes = try? self.attributesOfItem(atPath: rootPath) {
+        if rootAttributes[FileAttributeKey.type] as? FileAttributeType == FileAttributeType.typeSymbolicLink {
+          let resolvedRootPath = try self.destinationOfSymbolicLink(atPath: rootPath)
+          try self.copyItem(atPath: resolvedRootPath, toPath: destURL.path)
+        } else {
+          try self.copyItem(at: sourceURL, to: destURL)
+        }
+      }
 
       let path = destURL.path
       if let paths = self.subpaths(atPath: path) {