Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support test target for local Swift Package #1169

Merged
merged 27 commits into from
Mar 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
81de1e5
support local Swift Package test case into test scheme
freddi-kit May 5, 2021
2fb1d01
update test
freddi-kit May 5, 2021
70c0e0d
add test
freddi-kit May 5, 2021
f1f0df5
update CHABGELOG.md
freddi-kit May 5, 2021
5474cc5
Update CHANGELOG.md
freddi-kit May 5, 2021
a58383e
revert resolved package test
freddi-kit May 6, 2021
b4b8635
Update Sources/XcodeGenKit/SchemeGenerator.swift
freddi-kit May 6, 2021
fc1ec31
make TargetReference convert from new JSON format
freddi-kit May 7, 2021
9022364
add .package for location of target reference
freddi-kit May 13, 2021
68c29dc
Merge branch 'test-local-spm' of github.com:yonaskolb/XcodeGen into t…
freddi-kit May 13, 2021
9de7623
receive target reference format at target of scheme
freddi-kit May 13, 2021
4ed02a4
update test
freddi-kit May 13, 2021
b8c8aab
update XcodeProj
freddi-kit May 25, 2021
9fd8d2e
Merge branch 'master' into test-local-spm
freddi-kit May 25, 2021
1cda325
add test and fix small bugs
freddi-kit May 25, 2021
f29af31
update docs
freddi-kit May 25, 2021
52d9d81
support multiple style of coverageTargets
freddi-kit May 25, 2021
86ca040
add edge case of parsing test targets
freddi-kit May 25, 2021
69d0072
fix docs
freddi-kit May 25, 2021
6bfa5dc
Update Docs/ProjectSpec.md
freddi-kit Jun 18, 2021
1d746d5
create TestableTargetReference for not making API complex
freddi-kit Oct 20, 2021
771edd4
fix code format
freddi-kit Oct 20, 2021
53a9c34
Merge branch 'master' into test-local-spm
freddi-kit Oct 20, 2021
487d25c
fix parameter name to Testable Target Reference
freddi-kit Oct 20, 2021
66ff567
support directly writing key of Testable Target Reference
freddi-kit Feb 2, 2022
a969634
Merge branch 'master' into test-local-spm
freddi-kit Feb 2, 2022
54ab164
fix compile error in build
freddi-kit Feb 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Change Log

## Next Version
#### Added
- Support test target for local Swift Package [#1074](https://github.com/yonaskolb/XcodeGen/pull/1074) @freddi-kit

### Added

Expand Down
22 changes: 20 additions & 2 deletions Docs/ProjectSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -824,21 +824,35 @@ 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
- [ ] **coverageTargets**: **[String]** - a list of targets to gather code coverage. Each entry can either be a simple string, or a string using [Project Reference](#project-reference)
- [ ] **coverageTargets**: **[[Testable Target Reference](#testable-target-reference)]** - a list of targets to gather code coverage. Each entry can also either be a simple string, a string using [Project Reference](#project-reference) or [Testable Target Reference](#testable-target-reference)
- [ ] **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)
- [ ] **customLLDBInit**: **String** - the absolute path to the custom `.lldbinit` file
- [ ] **captureScreenshotsAutomatically**: **Bool** - indicates whether screenshots should be captured automatically while UI Testing. This defaults to true.
- [ ] **deleteScreenshotsWhenEachTestSucceeds**: **Bool** - whether successful UI tests should cause automatically-captured screenshots to be deleted. If `captureScreenshotsAutomatically` is false, this value is ignored. This defaults to true.

#### Test Target
- [x] **name**: **String** - The name of the target
A target can be one of a 2 types:

- **name**: **String** - The name of the target.
- **target**: **[Testable Target Reference](#testable-target-reference)** - The information of the target. You can specify more detailed information than `name:`.

As syntax suger, you can also specify **[Testable Target Reference](#testable-target-reference)** without `target`.

#### Other Parameters

- [ ] **parallelizable**: **Bool** - Whether to run tests in parallel. Defaults to false
- [ ] **randomExecutionOrder**: **Bool** - Whether to run tests in a random order. Defaults to false
- [ ] **location**: **String** - GPX file or predefined value for simulating location. See [Simulate Location](#simulate-location) for location examples.
- [ ] **skipped**: **Bool** - Whether to skip all of the test target tests. Defaults to false
- [ ] **skippedTests**: **[String]** - List of tests in the test target to skip. Defaults to empty
- [ ] **selectedTests**: **[String]** - List of tests in the test target to whitelist and select. Defaults to empty. This will override `skippedTests` if provided

#### Testable Target Reference
A Testable Target Reference can be one of 3 types:
- `package: {local-swift-package-name}/{target-name}`: Name of local swift package and its target.
- `local: {target-name}`: Name of local target.
- `project: {project-reference-name}/{target-name}`: Name of local swift package and its target.

### Archive Action

- [ ] **customArchiveName**: **String** - the custom name to give to the archive
Expand Down Expand Up @@ -898,12 +912,16 @@ schemes:
coverageTargets:
- MyTarget1
- ExternalTarget/OtherTarget1
- package: LocalPackage/TestTarget
targets:
- Tester1
- name: Tester2
parallelizable: true
randomExecutionOrder: true
skippedTests: [Test/testExample()]
- package: APIClient/APIClientTests
parallelizable: true
randomExecutionOrder: true
environmentVariables:
- variable: TEST_ENV_VAR
value: VALUE
Expand Down
4 changes: 4 additions & 0 deletions Sources/ProjectSpec/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ public struct Project: BuildSettingsContainer {
targetsMap[targetName]
}

public func getPackage(_ packageName: String) -> SwiftPackage? {
packages[packageName]
}

public func getAggregateTarget(_ targetName: String) -> AggregateTarget? {
aggregateTargetsMap[targetName]
}
Expand Down
47 changes: 36 additions & 11 deletions Sources/ProjectSpec/Scheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public struct Scheme: Equatable {

public var config: String?
public var gatherCoverageData: Bool
public var coverageTargets: [TargetReference]
public var coverageTargets: [TestableTargetReference]
public var disableMainThreadChecker: Bool
public var commandLineArguments: [String: Bool]
public var targets: [TestTarget]
Expand All @@ -189,7 +189,7 @@ public struct Scheme: Equatable {
public static let parallelizableDefault = false

public var name: String { targetReference.name }
public let targetReference: TargetReference
public let targetReference: TestableTargetReference
public var randomExecutionOrder: Bool
public var parallelizable: Bool
public var location: String?
Expand All @@ -198,7 +198,7 @@ public struct Scheme: Equatable {
public var selectedTests: [String]

public init(
targetReference: TargetReference,
targetReference: TestableTargetReference,
randomExecutionOrder: Bool = randomExecutionOrderDefault,
parallelizable: Bool = parallelizableDefault,
location: String? = nil,
Expand All @@ -217,7 +217,7 @@ public struct Scheme: Equatable {

public init(stringLiteral value: String) {
do {
targetReference = try TargetReference(value)
targetReference = try TestableTargetReference(value)
randomExecutionOrder = false
parallelizable = false
location = nil
Expand All @@ -233,7 +233,7 @@ public struct Scheme: Equatable {
public init(
config: String,
gatherCoverageData: Bool = gatherCoverageDataDefault,
coverageTargets: [TargetReference] = [],
coverageTargets: [TestableTargetReference] = [],
disableMainThreadChecker: Bool = disableMainThreadCheckerDefault,
randomExecutionOrder: Bool = false,
parallelizable: Bool = false,
Expand Down Expand Up @@ -331,10 +331,10 @@ public struct Scheme: Equatable {
}

public struct BuildTarget: Equatable, Hashable {
public var target: TargetReference
public var target: TestableTargetReference
public var buildTypes: [BuildType]

public init(target: TargetReference, buildTypes: [BuildType] = BuildType.all) {
public init(target: TestableTargetReference, buildTypes: [BuildType] = BuildType.all) {
self.target = target
self.buildTypes = buildTypes
}
Expand Down Expand Up @@ -465,13 +465,28 @@ extension Scheme.Test: JSONObjectConvertible {
public init(jsonDictionary: JSONDictionary) throws {
config = jsonDictionary.json(atKeyPath: "config")
gatherCoverageData = jsonDictionary.json(atKeyPath: "gatherCoverageData") ?? Scheme.Test.gatherCoverageDataDefault
coverageTargets = try (jsonDictionary.json(atKeyPath: "coverageTargets") ?? []).map { try TargetReference($0) }

if let coverages = jsonDictionary["coverageTargets"] as? [Any] {
coverageTargets = try coverages.compactMap { target in
if let string = target as? String {
return try TestableTargetReference(string)
} else if let dictionary = target as? JSONDictionary,
let target: TestableTargetReference = try? .init(jsonDictionary: dictionary) {
return target
} else {
return nil
}
}
} else {
coverageTargets = []
}

disableMainThreadChecker = jsonDictionary.json(atKeyPath: "disableMainThreadChecker") ?? Scheme.Test.disableMainThreadCheckerDefault
commandLineArguments = jsonDictionary.json(atKeyPath: "commandLineArguments") ?? [:]
if let targets = jsonDictionary["targets"] as? [Any] {
self.targets = try targets.compactMap { target in
if let string = target as? String {
return try TestTarget(targetReference: TargetReference(string))
return try TestTarget(targetReference: TestableTargetReference(string))
} else if let dictionary = target as? JSONDictionary {
return try TestTarget(jsonDictionary: dictionary)
} else {
Expand Down Expand Up @@ -538,7 +553,17 @@ extension Scheme.Test: JSONEncodable {
extension Scheme.Test.TestTarget: JSONObjectConvertible {

public init(jsonDictionary: JSONDictionary) throws {
targetReference = try TargetReference(jsonDictionary.json(atKeyPath: "name"))
if let name: String = jsonDictionary.json(atKeyPath: "name") {
targetReference = try TestableTargetReference(name)
} else if let local: String = jsonDictionary.json(atKeyPath: "local") {
self.targetReference = TestableTargetReference.local(local)
} else if let project: String = jsonDictionary.json(atKeyPath: "project") {
self.targetReference = TestableTargetReference.project(project)
} else if let package: String = jsonDictionary.json(atKeyPath: "package") {
self.targetReference = TestableTargetReference.package(package)
} else {
self.targetReference = try jsonDictionary.json(atKeyPath: "target")
}
randomExecutionOrder = jsonDictionary.json(atKeyPath: "randomExecutionOrder") ?? Scheme.Test.TestTarget.randomExecutionOrderDefault
parallelizable = jsonDictionary.json(atKeyPath: "parallelizable") ?? Scheme.Test.TestTarget.parallelizableDefault
location = jsonDictionary.json(atKeyPath: "location") ?? nil
Expand Down Expand Up @@ -694,7 +719,7 @@ extension Scheme.Build: JSONObjectConvertible {
} else {
buildTypes = BuildType.all
}
let target = try TargetReference(targetRepr)
let target = try TestableTargetReference(targetRepr)
targets.append(Scheme.BuildTarget(target: target, buildTypes: buildTypes))
}
self.targets = targets.sorted { $0.target.name < $1.target.name }
Expand Down
18 changes: 18 additions & 0 deletions Sources/ProjectSpec/SpecValidation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ extension Project {

for testTarget in scheme.testTargets {
if getTarget(testTarget.name) == nil {
// For test case of local Swift Package
if case .package(let name) = testTarget.targetReference.location, getPackage(name) != nil {
continue
}
errors.append(.invalidTargetSchemeTest(target: target.name, testTarget: testTarget.name))
}
}
Expand Down Expand Up @@ -243,4 +247,18 @@ extension Project {
return nil
}
}

/// Returns a descriptive error if the given target reference was invalid otherwise `nil`.
private func validationError(for testableTargetReference: TestableTargetReference, in scheme: Scheme, action: String) -> SpecValidationError.ValidationError? {
switch testableTargetReference.location {
case .local where getProjectTarget(testableTargetReference.name) == nil:
return .invalidSchemeTarget(scheme: scheme.name, target: testableTargetReference.name, action: action)
case .project(let project) where getProjectReference(project) == nil:
return .invalidProjectReference(scheme: scheme.name, reference: project)
case .package(let package) where getPackage(package) == nil:
return .invalidLocalPackage(package)
case .local, .project, .package:
return nil
}
}
}
3 changes: 3 additions & 0 deletions Sources/ProjectSpec/SpecValidationError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public struct SpecValidationError: Error, CustomStringConvertible {
case invalidSchemeTarget(scheme: String, target: String, action: String)
case invalidSchemeConfig(scheme: String, config: String)
case invalidSwiftPackage(name: String, target: String)
case invalidPackageDependencyReference(name: String)
case invalidLocalPackage(String)
case invalidConfigFile(configFile: String, config: String)
case invalidBuildSettingConfig(String)
Expand Down Expand Up @@ -69,6 +70,8 @@ public struct SpecValidationError: Error, CustomStringConvertible {
return "Target \(target.quoted) has an invalid package dependency \(name.quoted)"
case let .invalidLocalPackage(path):
return "Invalid local package \(path.quoted)"
case let .invalidPackageDependencyReference(name):
return "Package reference \(name) must be specified as package dependency, not target"
case let .missingConfigForTargetScheme(target, configType):
return "Target \(target.quoted) is missing a config of type \(configType.rawValue) to generate its scheme"
case let .missingDefaultConfig(name):
Expand Down
4 changes: 2 additions & 2 deletions Sources/ProjectSpec/TargetReference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ extension TargetReference: CustomStringConvertible {
public var reference: String {
switch location {
case .local: return name
case .project(let projectPath):
return "\(projectPath)/\(name)"
case .project(let root):
return "\(root)/\(name)"
}
}

Expand Down
7 changes: 4 additions & 3 deletions Sources/ProjectSpec/TargetScheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ extension TargetScheme: JSONObjectConvertible {
if let targets = jsonDictionary["testTargets"] as? [Any] {
testTargets = try targets.compactMap { target in
if let string = target as? String {
return .init(targetReference: try TargetReference(string))
} else if let dictionary = target as? JSONDictionary {
return try .init(jsonDictionary: dictionary)
return .init(targetReference: try TestableTargetReference(string))
} else if let dictionary = target as? JSONDictionary,
let target: Scheme.Test.TestTarget = try? .init(jsonDictionary: dictionary) {
return target
} else {
return nil
}
Expand Down
112 changes: 112 additions & 0 deletions Sources/ProjectSpec/TestTargeReference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import Foundation
import JSONUtilities

public struct TestableTargetReference: Hashable {
public var name: String
public var location: Location

public var targetReference: TargetReference {
switch location {
case .local:
return TargetReference(name: name, location: .local)
case .project(let projectName):
return TargetReference(name: name, location: .project(projectName))
case .package:
fatalError("Package target is only available for testable")
}
}

public enum Location: Hashable {
case local
case project(String)
case package(String)
}

public init(name: String, location: Location) {
self.name = name
self.location = location
}
}

extension TestableTargetReference {
public init(_ string: String) throws {
let paths = string.split(separator: "/")
switch paths.count {
case 2:
location = .project(String(paths[0]))
name = String(paths[1])
case 1:
location = .local
name = String(paths[0])
default:
throw SpecParsingError.invalidTargetReference(string)
}
}

public static func local(_ name: String) -> TestableTargetReference {
TestableTargetReference(name: name, location: .local)
}

public static func project(_ name: String) -> TestableTargetReference {
let paths = name.split(separator: "/")
return TestableTargetReference(name: String(paths[1]), location: .project(String(paths[0])))
}

public static func package(_ name: String) -> TestableTargetReference {
let paths = name.split(separator: "/")
return TestableTargetReference(name: String(paths[1]), location: .package(String(paths[0])))
}
}

extension TestableTargetReference: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
try! self.init(value)
}
}

extension TestableTargetReference: CustomStringConvertible {
public var reference: String {
switch location {
case .local: return name
case .project(let root), .package(let root):
return "\(root)/\(name)"
}
}

public var description: String {
reference
}
}

extension TestableTargetReference: JSONObjectConvertible {

public init(jsonDictionary: JSONDictionary) throws {
if let project: String = jsonDictionary.json(atKeyPath: "project") {
let paths = project.split(separator: "/")
name = String(paths[1])
location = .project(String(paths[0]))
} else if let project: String = jsonDictionary.json(atKeyPath: "package") {
let paths = project.split(separator: "/")
name = String(paths[1])
location = .package(String(paths[0]))
} else {
name = try jsonDictionary.json(atKeyPath: "local")
location = .local
}
}
}

extension TestableTargetReference: JSONEncodable {
public func toJSONValue() -> Any {
var dictionary: JSONDictionary = [:]
switch self.location {
case .package(let packageName):
dictionary["package"] = "\(packageName)/\(name)"
case .project(let projectName):
dictionary["project"] = "\(projectName)/\(name)"
case .local:
dictionary["local"] = name
}
return dictionary
}
}
Loading