Skip to content

Commit

Permalink
Add support for selective testing for Xcode non-generated projects (t…
Browse files Browse the repository at this point in the history
…uist#7287)

* Add support for selective testing for Xcode projects not generated with Tuist

* Update XcodeGraph

* Update reference from xctest-dynamic-overlay to swift-issue-reporting in Package.resolved

* Add xcode_project_with_tests to a list of fixtures

* Add log for which test modules are skipped

* Add Tuist.swift for the xcode_project_with_tests fixture

* Override acceptance test case arguments for XcodeBuildCommand

* Add logs for which targets were skippped only when some were
  • Loading branch information
fortmarek authored Feb 10, 2025
1 parent a64c4fa commit 003a460
Show file tree
Hide file tree
Showing 39 changed files with 2,042 additions and 93 deletions.
31 changes: 20 additions & 11 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "b0472b9536122cb752e6688cb17b4063f73451c1c52c46a6c61d7a40a6d51677",
"originHash" : "1e4d907dfd7f7c760087efd4410e7861f779556f8a1990ea6836b33ba13a4f54",
"pins" : [
{
"identity" : "aexml",
Expand Down Expand Up @@ -33,8 +33,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/tuist/Command.git",
"state" : {
"revision" : "27270402bfb9cd65f6a8b83bdf59941a8c9ae368",
"version" : "0.8.0"
"revision" : "9d03a95faa94b961edc1cf2c5f4379b0108ee97a",
"version" : "0.12.1"
}
},
{
Expand Down Expand Up @@ -91,13 +91,22 @@
"version" : "1.1.3"
}
},
{
"identity" : "machokit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/p-x9/MachOKit",
"state" : {
"revision" : "518e8e1aca7ee64b87b08ecec5f7cad2a63b8efd",
"version" : "0.28.0"
}
},
{
"identity" : "mockable",
"kind" : "remoteSourceControl",
"location" : "https://github.com/Kolos65/Mockable.git",
"state" : {
"revision" : "a9e5e1d222035567069ed6fff8429c327229b5f6",
"version" : "0.0.11"
"revision" : "203336d0ccb7ff03a8a03db54a4fa18fc2b0c771",
"version" : "0.3.0"
}
},
{
Expand Down Expand Up @@ -276,8 +285,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-service-context",
"state" : {
"revision" : "0c62c5b4601d6c125050b5c3a97f20cce881d32b",
"version" : "1.1.0"
"revision" : "8946c930cae601452149e45d31d8ddfac973c3c7",
"version" : "1.2.0"
}
},
{
Expand Down Expand Up @@ -330,17 +339,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/tuist/XcodeGraph.git",
"state" : {
"revision" : "c533cf2543dc41428f0118d4f0df5c890307918a",
"version" : "1.3.2"
"revision" : "decf6cc311e8dd238c87501c369b05827bca6a48",
"version" : "1.5.14"
}
},
{
"identity" : "xcodeproj",
"kind" : "remoteSourceControl",
"location" : "https://github.com/tuist/XcodeProj",
"state" : {
"revision" : "6e971133653f069b7699d5fb081e5db1e5f81559",
"version" : "8.26.1"
"revision" : "6f90427e172da66336739801c84b9cef3e17367b",
"version" : "8.26.6"
}
},
{
Expand Down
11 changes: 5 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ let targets: [Target] = [
"TuistCache",
.product(name: "Command", package: "Command"),
.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime"),
.product(name: "XcodeGraphMapper", package: "XcodeGraph"),
.byName(name: "AnyCodable"),
],
swiftSettings: [
Expand Down Expand Up @@ -497,10 +498,10 @@ let package = Package(
.package(url: "https://github.com/tuist/GraphViz.git", exact: "0.4.2"),
.package(url: "https://github.com/SwiftGen/StencilSwiftKit", exact: "2.10.1"),
.package(url: "https://github.com/SwiftGen/SwiftGen", exact: "6.6.2"),
.package(url: "https://github.com/tuist/XcodeProj", exact: "8.26.1"),
.package(url: "https://github.com/tuist/XcodeProj", .upToNextMajor(from: "8.26.1")),
.package(url: "https://github.com/cpisciotta/xcbeautify", .upToNextMajor(from: "2.20.0")),
.package(url: "https://github.com/krzysztofzablocki/Difference.git", from: "1.0.2"),
.package(url: "https://github.com/Kolos65/Mockable.git", exact: "0.0.11"),
.package(url: "https://github.com/Kolos65/Mockable.git", .upToNextMajor(from: "0.0.11")),
.package(
url: "https://github.com/apple/swift-openapi-runtime", .upToNextMajor(from: "1.5.0")
),
Expand All @@ -511,11 +512,9 @@ let package = Package(
url: "https://github.com/apple/swift-openapi-urlsession", .upToNextMajor(from: "1.0.2")
),
.package(url: "https://github.com/tuist/Path", .upToNextMajor(from: "0.3.0")),
.package(
url: "https://github.com/tuist/XcodeGraph.git", exact: "1.3.2"
),
.package(url: "https://github.com/tuist/XcodeGraph.git", exact: "1.5.14"),
.package(url: "https://github.com/tuist/FileSystem.git", .upToNextMajor(from: "0.7.0")),
.package(url: "https://github.com/tuist/Command.git", exact: "0.8.0"),
.package(url: "https://github.com/tuist/Command.git", .upToNextMajor(from: "0.8.0")),
.package(url: "https://github.com/sparkle-project/Sparkle.git", from: "2.6.4"),
.package(url: "https://github.com/apple/swift-collections", .upToNextMajor(from: "1.1.4")),
.package(url: "https://github.com/apple/swift-service-context", .upToNextMajor(from: "1.0.0")),
Expand Down
3 changes: 3 additions & 0 deletions Sources/TuistAcceptanceTesting/TuistAcceptanceFixtures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ public enum TuistAcceptanceFixtures {
case workspaceWithInlineFileHeaderTemplate
case xcodeApp
case xcodeProjectWithRegistryAndAlamofire
case xcodeProjectWithTests
case appWithExecutableNonLocalDependencies
case appWithGeneratedSources
case custom(String)
Expand Down Expand Up @@ -302,6 +303,8 @@ public enum TuistAcceptanceFixtures {
return "xcode_app"
case .xcodeProjectWithRegistryAndAlamofire:
return "xcode_project_with_registry_and_alamofire"
case .xcodeProjectWithTests:
return "xcode_project_with_tests"
case .appWithExecutableNonLocalDependencies:
return "app_with_executable_non_local_dependencies"
case .appWithGeneratedSources:
Expand Down
9 changes: 9 additions & 0 deletions Sources/TuistAcceptanceTesting/TuistAcceptanceTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,15 @@ open class TuistAcceptanceTestCase: XCTestCase {
.first(where: { $0.extension == "xcworkspace" })
}

public func run(_ command: XcodeBuildCommand.Type, _ arguments: String...) async throws {
try await run(command, arguments)
}

public func run(_ command: XcodeBuildCommand.Type, _ arguments: [String] = []) async throws {
let parsedCommand = try command.parse(arguments)
try await parsedCommand.run()
}

public func run(_ command: (some AsyncParsableCommand).Type, _ arguments: String...) async throws {
try await run(command, Array(arguments))
}
Expand Down
96 changes: 50 additions & 46 deletions Sources/TuistAutomation/XcodeBuild/XcodeBuildController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,26 @@ public final class XcodeBuildController: XcodeBuildControlling {
arguments: [XcodeBuildArgument],
passthroughXcodeBuildArguments: [String]
) async throws {
var command = ["/usr/bin/xcrun", "xcodebuild"]
let extraArguments = arguments.flatMap(\.arguments)
var arguments: [String] = []

// Action
if clean {
command.append("clean")
arguments.append("clean")
}
command.append("build")
arguments.append("build")

// Scheme
command.append(contentsOf: ["-scheme", scheme])
arguments.append(contentsOf: ["-scheme", scheme])

// Target
command.append(contentsOf: target.xcodebuildArguments)
arguments.append(contentsOf: target.xcodebuildArguments)

// Arguments
command.append(contentsOf: arguments.flatMap(\.arguments))
arguments.append(contentsOf: extraArguments)

// Passthrough arguments
command.append(contentsOf: passthroughXcodeBuildArguments)
arguments.append(contentsOf: passthroughXcodeBuildArguments)

// Destination
switch destination {
Expand All @@ -72,21 +73,21 @@ public final class XcodeBuildController: XcodeBuildControlling {
if rosetta {
value += ["arch=x86_64"]
}
command.append(contentsOf: ["-destination", value.joined(separator: ",")])
arguments.append(contentsOf: ["-destination", value.joined(separator: ",")])
case .mac:
command.append(contentsOf: ["-destination", simulatorController.macOSDestination()])
arguments.append(contentsOf: ["-destination", simulatorController.macOSDestination()])
case .macCatalyst:
command.append(contentsOf: ["-destination", simulatorController.macOSDestination(catalyst: true)])
arguments.append(contentsOf: ["-destination", simulatorController.macOSDestination(catalyst: true)])
case nil:
break
}

// Derived data path
if let derivedDataPath {
command.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
arguments.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
}

try await run(command: command)
try await run(arguments: arguments)
}

public func test(
Expand All @@ -104,29 +105,30 @@ public final class XcodeBuildController: XcodeBuildControlling {
testPlanConfiguration: TestPlanConfiguration?,
passthroughXcodeBuildArguments: [String]
) async throws {
var command = ["/usr/bin/xcrun", "xcodebuild"]
let extraArguments = arguments.flatMap(\.arguments)
var arguments: [String] = []

// Action
if clean {
command.append("clean")
arguments.append("clean")
}
command.append("test")
arguments.append("test")

// Scheme
command.append(contentsOf: ["-scheme", scheme])
arguments.append(contentsOf: ["-scheme", scheme])

// Target
command.append(contentsOf: target.xcodebuildArguments)
arguments.append(contentsOf: target.xcodebuildArguments)

// Arguments
command.append(contentsOf: arguments.flatMap(\.arguments))
arguments.append(contentsOf: extraArguments)

// Passthrough arguments
command.append(contentsOf: passthroughXcodeBuildArguments)
arguments.append(contentsOf: passthroughXcodeBuildArguments)

// Retry On Failure
if retryCount > 0 {
command.append(contentsOf: XcodeBuildArgument.retryCount(retryCount).arguments)
arguments.append(contentsOf: XcodeBuildArgument.retryCount(retryCount).arguments)
}

// Destination
Expand All @@ -136,45 +138,45 @@ public final class XcodeBuildController: XcodeBuildControlling {
if rosetta {
value += ["arch=x86_64"]
}
command.append(contentsOf: ["-destination", value.joined(separator: ",")])
arguments.append(contentsOf: ["-destination", value.joined(separator: ",")])
case .mac:
command.append(contentsOf: ["-destination", simulatorController.macOSDestination()])
arguments.append(contentsOf: ["-destination", simulatorController.macOSDestination()])
case .macCatalyst:
command.append(contentsOf: ["-destination", simulatorController.macOSDestination(catalyst: true)])
arguments.append(contentsOf: ["-destination", simulatorController.macOSDestination(catalyst: true)])
case nil:
break
}

// Derived data path
if let derivedDataPath {
command.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
arguments.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
}

// Result bundle path
if let resultBundlePath {
command.append(contentsOf: ["-resultBundlePath", resultBundlePath.pathString])
arguments.append(contentsOf: ["-resultBundlePath", resultBundlePath.pathString])
}

for test in testTargets {
command.append(contentsOf: ["-only-testing", test.description])
arguments.append(contentsOf: ["-only-testing", test.description])
}

for test in skipTestTargets {
command.append(contentsOf: ["-skip-testing", test.description])
arguments.append(contentsOf: ["-skip-testing", test.description])
}

if let testPlanConfiguration {
command.append(contentsOf: ["-testPlan", testPlanConfiguration.testPlan])
arguments.append(contentsOf: ["-testPlan", testPlanConfiguration.testPlan])
for configuration in testPlanConfiguration.configurations {
command.append(contentsOf: ["-only-test-configuration", configuration])
arguments.append(contentsOf: ["-only-test-configuration", configuration])
}

for configuration in testPlanConfiguration.skipConfigurations {
command.append(contentsOf: ["-skip-test-configuration", configuration])
arguments.append(contentsOf: ["-skip-test-configuration", configuration])
}
}

try await run(command: command)
try await run(arguments: arguments)
}

public func archive(
Expand All @@ -185,44 +187,44 @@ public final class XcodeBuildController: XcodeBuildControlling {
arguments: [XcodeBuildArgument],
derivedDataPath: AbsolutePath?
) async throws {
var command = ["/usr/bin/xcrun", "xcodebuild"]
let extraArguments = arguments.flatMap(\.arguments)
var arguments: [String] = []

// Action
if clean {
command.append("clean")
arguments.append("clean")
}
command.append("archive")
arguments.append("archive")

// Scheme
command.append(contentsOf: ["-scheme", scheme])
arguments.append(contentsOf: ["-scheme", scheme])

// Target
command.append(contentsOf: target.xcodebuildArguments)
arguments.append(contentsOf: target.xcodebuildArguments)

// Archive path
command.append(contentsOf: ["-archivePath", archivePath.pathString])
arguments.append(contentsOf: ["-archivePath", archivePath.pathString])

// Derived data path
if let derivedDataPath {
command.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
arguments.append(contentsOf: ["-derivedDataPath", derivedDataPath.pathString])
}

// Arguments
command.append(contentsOf: arguments.flatMap(\.arguments))
arguments.append(contentsOf: extraArguments)

try await run(command: command)
try await run(arguments: arguments)
}

public func createXCFramework(
arguments: [String],
output: AbsolutePath
) async throws {
var command = ["/usr/bin/xcrun", "xcodebuild", "-create-xcframework"]
command.append(contentsOf: arguments)
command.append(contentsOf: ["-output", output.pathString])
command.append("-allow-internal-distribution")
var arguments = ["-create-xcframework"] + arguments
arguments.append(contentsOf: ["-output", output.pathString])
arguments.append("-allow-internal-distribution")

try await run(command: command)
try await run(arguments: arguments)
}

enum ShowBuildSettingsError: Error {
Expand Down Expand Up @@ -298,7 +300,7 @@ public final class XcodeBuildController: XcodeBuildControlling {
return buildSettingsByTargetName
}

fileprivate func run(command: [String]) async throws {
public func run(arguments: [String]) async throws {
let logger = ServiceContext.current?.logger

func format(_ bytes: [UInt8]) -> String {
Expand All @@ -321,6 +323,8 @@ public final class XcodeBuildController: XcodeBuildControlling {
}
}

let command = ["/usr/bin/xcrun", "xcodebuild"] + arguments

logger?.debug("Running xcodebuild command: \(command.joined(separator: " "))")

try system.run(command,
Expand Down
15 changes: 15 additions & 0 deletions Sources/TuistCache/SelectiveTestingServicing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation
import Mockable
import TuistCore
import XcodeGraph

@Mockable
public protocol SelectiveTestingServicing {
/// - Returns: Tests that are cached.
func cachedTests(
scheme: Scheme,
graph: Graph,
selectiveTestingHashes: [GraphTarget: String],
selectiveTestingCacheItems: [CacheItem]
) async throws -> [TestIdentifier]
}
Loading

0 comments on commit 003a460

Please sign in to comment.