From e8feba21ef2b505aeedccaa7812812d58d1b7fb4 Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Sat, 3 Nov 2018 19:04:28 +1100 Subject: [PATCH 1/2] add TestTarget with parallelizable and randomExecutionOrder --- Docs/ProjectSpec.md | 9 +- Sources/ProjectSpec/Scheme.swift | 52 +++++++- Sources/ProjectSpec/SpecValidation.swift | 4 +- Sources/ProjectSpec/TargetScheme.swift | 18 ++- Sources/XcodeGenKit/SchemeGenerator.swift | 13 +- .../xcschemes/App_Scheme.xcscheme | 116 ++++++++++++++++++ .../xcshareddata/xcschemes/Framework.xcscheme | 3 + Tests/Fixtures/TestProject/project.yml | 13 ++ Tests/XcodeGenKitTests/SpecLoadingTests.swift | 24 +++- 9 files changed, 236 insertions(+), 16 deletions(-) create mode 100644 Tests/Fixtures/TestProject/Project.xcodeproj/xcshareddata/xcschemes/App_Scheme.xcscheme 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") { From e8f2ce0d0c4e1113d033b2a74ebb57a68d86f8f6 Mon Sep 17 00:00:00 2001 From: Yonas Kolb Date: Sat, 3 Nov 2018 19:11:47 +1100 Subject: [PATCH 2/2] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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