diff --git a/CHANGELOG.md b/CHANGELOG.md
index b20925e2..606cc3d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
- Added `weak` linking setting for dependencies [#411](https://github.com/yonaskolb/XcodeGen/pull/411) @alvarhansen
- Added `info` to targets for generating an `Info.plist` [#415](https://github.com/yonaskolb/XcodeGen/pull/415) @yonaskolb
- Added `entitlements` to targets for generating an `.entitlement` file [#415](https://github.com/yonaskolb/XcodeGen/pull/415) @yonaskolb
+- Added `parallelizable` and `randomExecutionOrder` to `Scheme` test targets in an expanded form [#434](https://github.com/yonaskolb/XcodeGen/pull/434) @yonaskolb
- Validate incorrect config setting definitions [#431](https://github.com/yonaskolb/XcodeGen/pull/431) @yonaskolb
- Automatically set project `SDKROOT` if there is only a single platform within the project [#433](https://github.com/yonaskolb/XcodeGen/pull/433) @yonaskolb
diff --git a/Docs/ProjectSpec.md b/Docs/ProjectSpec.md
index fcb565a9..2cc75280 100644
--- a/Docs/ProjectSpec.md
+++ b/Docs/ProjectSpec.md
@@ -454,7 +454,7 @@ targets:
This is a convenience used to automatically generate schemes for a target based on different configs or included tests. If you want more control check out the top level [Scheme](#scheme).
- [x] **configVariants**: **[String]** - This generates a scheme for each entry, using configs that contain the name with debug and release variants. This is useful for having different environment schemes.
-- [ ] **testTargets**: **[String]** - a list of test targets that should be included in the scheme. These will be added to the build targets and the test entries
+- [ ] **testTargets**: **[[Test Target](#test-target)]** - a list of test targets that should be included in the scheme. These will be added to the build targets and the test entries. Each entry can either be a simple string, or a [Test Target](#test-target)
- [ ] **gatherCoverageData**: **Bool** - a boolean that indicates if this scheme should gather coverage data. This defaults to false
- [ ] **commandLineArguments**: **[String:Bool]** - a dictionary from the argument name (`String`) to if it is enabled (`Bool`). These arguments will be added to the Test, Profile and Run scheme actions
- [ ] **environmentVariables**: **[[Environment Variable](#environment-variable)]** or **[String:String]** - environment variables for Run, Test and Profile scheme actions. When passing a dictionary, every key-value entry maps to a corresponding variable that is enabled.
@@ -578,7 +578,12 @@ A multiline script can be written using the various YAML multiline methods, for
### Test Action
- [ ] **gatherCoverageData**: **Bool** - a boolean that indicates if this scheme should gather coverage data. This defaults to false
-- [ ] **targets**: **[String]** - a list of targets to test
+- [ ] **targets**: **[[Test Target](#test-target)]** - a list of targets to test. Each entry can either be a simple string, or a [Test Target](#test-target)
+
+#### Test Target
+- [x] **name**: **String** - The name of the target
+- [ ] **parallelizable**: **Bool** - Whether to run tests in parallel. Defaults to false
+- [ ] **randomExecutionOrder**: **Bool** - Whether to run tests in a random order. Defaults to false
### Archive Action
diff --git a/Sources/ProjectSpec/Scheme.swift b/Sources/ProjectSpec/Scheme.swift
index 1bd83431..278aa979 100644
--- a/Sources/ProjectSpec/Scheme.swift
+++ b/Sources/ProjectSpec/Scheme.swift
@@ -89,15 +89,40 @@ public struct Scheme: Equatable {
public var config: String?
public var gatherCoverageData: Bool
public var commandLineArguments: [String: Bool]
- public var targets: [String]
+ public var targets: [TestTarget]
public var preActions: [ExecutionAction]
public var postActions: [ExecutionAction]
public var environmentVariables: [XCScheme.EnvironmentVariable]
+
+ public struct TestTarget: Equatable, ExpressibleByStringLiteral {
+ public let name: String
+ public var randomExecutionOrder: Bool
+ public var parallelizable: Bool
+
+ public init(
+ name: String,
+ randomExecutionOrder: Bool = false,
+ parallelizable: Bool = false
+ ) {
+ self.name = name
+ self.randomExecutionOrder = randomExecutionOrder
+ self.parallelizable = parallelizable
+ }
+
+ public init(stringLiteral value: String) {
+ name = value
+ randomExecutionOrder = false
+ parallelizable = false
+ }
+ }
+
public init(
config: String,
gatherCoverageData: Bool = false,
+ randomExecutionOrder: Bool = false,
+ parallelizable: Bool = false,
commandLineArguments: [String: Bool] = [:],
- targets: [String] = [],
+ targets: [TestTarget] = [],
preActions: [ExecutionAction] = [],
postActions: [ExecutionAction] = [],
environmentVariables: [XCScheme.EnvironmentVariable] = []
@@ -210,13 +235,34 @@ extension Scheme.Test: JSONObjectConvertible {
config = jsonDictionary.json(atKeyPath: "config")
gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? false
commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:]
- targets = jsonDictionary.json(atKeyPath: "targets") ?? []
+ if let targets = jsonDictionary["targets"] as? [Any] {
+ self.targets = try targets.compactMap { target in
+ if let string = target as? String {
+ return TestTarget(name: string)
+ } else if let dictionary = target as? JSONDictionary {
+ return try TestTarget(jsonDictionary: dictionary)
+ } else {
+ return nil
+ }
+ }
+ } else {
+ targets = []
+ }
preActions = jsonDictionary.json(atKeyPath: "preActions") ?? []
postActions = jsonDictionary.json(atKeyPath: "postActions") ?? []
environmentVariables = try XCScheme.EnvironmentVariable.parseAll(jsonDictionary: jsonDictionary)
}
}
+extension Scheme.Test.TestTarget: JSONObjectConvertible {
+
+ public init(jsonDictionary: JSONDictionary) throws {
+ name = try jsonDictionary.json(atKeyPath: "name")
+ randomExecutionOrder = jsonDictionary.json(atKeyPath: "randomExecutionOrder") ?? false
+ parallelizable = jsonDictionary.json(atKeyPath: "parallelizable") ?? false
+ }
+}
+
extension Scheme.Profile: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
diff --git a/Sources/ProjectSpec/SpecValidation.swift b/Sources/ProjectSpec/SpecValidation.swift
index 2cae3791..793e4783 100644
--- a/Sources/ProjectSpec/SpecValidation.swift
+++ b/Sources/ProjectSpec/SpecValidation.swift
@@ -110,8 +110,8 @@ extension Project {
}
for testTarget in scheme.testTargets {
- if getTarget(testTarget) == nil {
- errors.append(.invalidTargetSchemeTest(target: target.name, testTarget: testTarget))
+ if getTarget(testTarget.name) == nil {
+ errors.append(.invalidTargetSchemeTest(target: target.name, testTarget: testTarget.name))
}
}
}
diff --git a/Sources/ProjectSpec/TargetScheme.swift b/Sources/ProjectSpec/TargetScheme.swift
index 50d49f88..69f7bc48 100644
--- a/Sources/ProjectSpec/TargetScheme.swift
+++ b/Sources/ProjectSpec/TargetScheme.swift
@@ -3,7 +3,7 @@ import JSONUtilities
import xcodeproj
public struct TargetScheme: Equatable {
- public var testTargets: [String]
+ public var testTargets: [Scheme.Test.TestTarget]
public var configVariants: [String]
public var gatherCoverageData: Bool
public var commandLineArguments: [String: Bool]
@@ -12,7 +12,7 @@ public struct TargetScheme: Equatable {
public var postActions: [Scheme.ExecutionAction]
public init(
- testTargets: [String] = [],
+ testTargets: [Scheme.Test.TestTarget] = [],
configVariants: [String] = [],
gatherCoverageData: Bool = false,
commandLineArguments: [String: Bool] = [:],
@@ -33,7 +33,19 @@ public struct TargetScheme: Equatable {
extension TargetScheme: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
- testTargets = jsonDictionary.json(atKeyPath: "testTargets") ?? []
+ if let targets = jsonDictionary["testTargets"] as? [Any] {
+ self.testTargets = try targets.compactMap { target in
+ if let string = target as? String {
+ return .init(name: string)
+ } else if let dictionary = target as? JSONDictionary {
+ return try .init(jsonDictionary: dictionary)
+ } else {
+ return nil
+ }
+ }
+ } else {
+ testTargets = []
+ }
configVariants = jsonDictionary.json(atKeyPath: "configVariants") ?? []
gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? false
commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:]
diff --git a/Sources/XcodeGenKit/SchemeGenerator.swift b/Sources/XcodeGenKit/SchemeGenerator.swift
index 921a2276..c868cb5a 100644
--- a/Sources/XcodeGenKit/SchemeGenerator.swift
+++ b/Sources/XcodeGenKit/SchemeGenerator.swift
@@ -95,9 +95,9 @@ public class SchemeGenerator {
return XCScheme.BuildAction.Entry(buildableReference: buildableReference, buildFor: buildTarget.buildTypes)
}
- let testTargetNames = scheme.test?.targets ?? []
- let testBuildTargets = testTargetNames.map {
- Scheme.BuildTarget(target: $0, buildTypes: BuildType.testOnly)
+ let testTargets = scheme.test?.targets ?? []
+ let testBuildTargets = testTargets.map {
+ Scheme.BuildTarget(target: $0.name, buildTypes: BuildType.testOnly)
}
let testBuildTargetEntries = testBuildTargets.map(getBuildEntry)
@@ -128,8 +128,11 @@ public class SchemeGenerator {
buildImplicitDependencies: scheme.build.buildImplicitDependencies
)
- let testables = testBuildTargetEntries.map {
- XCScheme.TestableReference(skipped: false, buildableReference: $0.buildableReference)
+ let testables = zip(testTargets, testBuildTargetEntries).map { testTarget, testBuilEntries in
+ XCScheme.TestableReference(skipped: false,
+ parallelizable: testTarget.parallelizable,
+ randomExecutionOrdering: testTarget.randomExecutionOrder,
+ buildableReference: testBuilEntries.buildableReference)
}
let testCommandLineArgs = scheme.test.map { XCScheme.CommandLineArguments($0.commandLineArguments) }
diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_Scheme.xcscheme b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_Scheme.xcscheme
new file mode 100644
index 00000000..4fb7309b
--- /dev/null
+++ b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_Scheme.xcscheme
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme
index 3b33dfb3..6901dafc 100644
--- a/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme
+++ b/Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/Framework.xcscheme
@@ -44,6 +44,7 @@
buildConfiguration = "Production Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
@@ -56,6 +57,8 @@
ReferencedContainer = "container:Project.xcodeproj">
+
+
diff --git a/Tests/Fixtures/TestProject/project.yml b/Tests/Fixtures/TestProject/project.yml
index 8a25909a..3b96d522 100644
--- a/Tests/Fixtures/TestProject/project.yml
+++ b/Tests/Fixtures/TestProject/project.yml
@@ -217,6 +217,19 @@ schemes:
commandLineArguments:
argument: YES
argument.with.dot: YES
+ test:
+ gatherCoverageData: true
+ App_Scheme:
+ build:
+ targets:
+ App_iOS: all
+ test:
+ gatherCoverageData: true
+ targets:
+ - App_iOS_UITests
+ - name: App_iOS_Tests
+ parallelizable: true
+ randomExecutionOrder: true
targetTemplates:
MyTemplate:
scheme: {}
diff --git a/Tests/XcodeGenKitTests/SpecLoadingTests.swift b/Tests/XcodeGenKitTests/SpecLoadingTests.swift
index e6c731f4..59eb720c 100644
--- a/Tests/XcodeGenKitTests/SpecLoadingTests.swift
+++ b/Tests/XcodeGenKitTests/SpecLoadingTests.swift
@@ -253,7 +253,7 @@ class SpecLoadingTests: XCTestCase {
$0.it("parses target schemes") {
var targetDictionary = validTarget
targetDictionary["scheme"] = [
- "testTargets": ["t1", "t2"],
+ "testTargets": ["t1", ["name": "t2"]],
"configVariants": ["dev", "app-store"],
"commandLineArguments": [
"ENV1": true,
@@ -312,6 +312,18 @@ class SpecLoadingTests: XCTestCase {
],
],
],
+ "test": [
+ "config": "debug",
+ "targets": [
+ "Target1",
+ [
+ "name": "Target2",
+ "parallelizable": true,
+ "randomExecutionOrder": true,
+ ],
+ ],
+ "gatherCoverageData": true,
+ ]
]
let scheme = try Scheme(name: "Scheme", jsonDictionary: schemeDictionary)
let expectedTargets: [Scheme.BuildTarget] = [
@@ -330,6 +342,16 @@ class SpecLoadingTests: XCTestCase {
try expect(scheme.build.parallelizeBuild) == false
try expect(scheme.build.buildImplicitDependencies) == false
+
+ let expectedTest = Scheme.Test(config: "debug",
+ gatherCoverageData: true,
+ targets: [
+ "Target1",
+ Scheme.Test.TestTarget(name: "Target2",
+ randomExecutionOrder: true,
+ parallelizable: true)
+ ])
+ try expect(scheme.test) == expectedTest
}
$0.it("parses schemes variables") {