Skip to content

Commit 37217a5

Browse files
committed
Tests: Migrate PackageCommandTests to Swift Testing and Augment
Migrate the `PackageCommandTests` to Swift Testing and augment the suite to run against the Native and SwiftBuild build system, in addition to the `debug` and `release` build configurations. Relates to: #8997 issue: rdar://157669245
1 parent b9f52a7 commit 37217a5

13 files changed

+6076
-3892
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.build
2+
.test
23
.index-build
34
DerivedData
45
/.previous-build

Sources/PackageModel/Toolchain+SupportedFeatures.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public enum SwiftCompilerFeature {
7070
}
7171

7272
extension Toolchain {
73-
public var supportesSupportedFeatures: Bool {
73+
public var supportsSupportedFeatures: Bool {
7474
guard let features = try? swiftCompilerSupportedFeatures else {
7575
return false
7676
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import enum PackageModel.BuildConfiguration
14+
15+
extension BuildConfiguration {
16+
17+
public var buildFor: String {
18+
switch self {
19+
case .debug:
20+
return "debugging"
21+
case .release:
22+
return "production"
23+
}
24+
}
25+
}

Sources/_InternalTestSupport/BuildSystemProvider+Supported.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import struct SPMBuildCore.BuildSystemProvider
14-
14+
import enum PackageModel.BuildConfiguration
1515

1616
public var SupportedBuildSystemOnAllPlatforms: [BuildSystemProvider.Kind] = BuildSystemProvider.Kind.allCases.filter { $0 != .xcode }
1717

@@ -22,3 +22,16 @@ public var SupportedBuildSystemOnPlatform: [BuildSystemProvider.Kind] {
2222
SupportedBuildSystemOnAllPlatforms
2323
#endif
2424
}
25+
26+
public struct BuildData {
27+
public let buildSystem: BuildSystemProvider.Kind
28+
public let config: BuildConfiguration
29+
}
30+
31+
public func getBuildData(for buildSystems: [BuildSystemProvider.Kind]) -> [BuildData] {
32+
buildSystems.flatMap { buildSystem in
33+
BuildConfiguration.allCases.compactMap { config in
34+
return BuildData(buildSystem: buildSystem, config: config)
35+
}
36+
}
37+
}

Sources/_InternalTestSupport/SwiftPMProduct.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,12 @@ extension SwiftPM {
127127

128128
// Unset the internal env variable that allows skipping certain tests.
129129
environment["_SWIFTPM_SKIP_TESTS_LIST"] = nil
130-
environment["SWIFTPM_EXEC_NAME"] = self.executableName
131130

132131
for (key, value) in env ?? [:] {
133132
environment[key] = value
134133
}
135134

136-
var completeArgs = [xctestBinaryPath.pathString]
135+
var completeArgs = [Self.xctestBinaryPath(for: RelativePath(self.executableName)).pathString]
137136
if let packagePath = packagePath {
138137
completeArgs += ["--package-path", packagePath.pathString]
139138
}

Sources/_InternalTestSupport/SwiftTesting+Helpers.swift

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,63 @@ public func expectFileExists(
2323
)
2424
}
2525

26+
public func expectFileDoesNotExists(
27+
at fixturePath: AbsolutePath,
28+
_ comment: Comment? = nil,
29+
sourceLocation: SourceLocation = #_sourceLocation,
30+
) {
31+
let commentPrefix =
32+
if let comment {
33+
"\(comment): "
34+
} else {
35+
""
36+
}
37+
#expect(
38+
!localFileSystem.exists(fixturePath),
39+
"\(commentPrefix)\(fixturePath) does not exist",
40+
sourceLocation: sourceLocation,
41+
)
42+
}
43+
44+
public func expectFileIsExecutable(
45+
at fixturePath: AbsolutePath,
46+
_ comment: Comment? = nil,
47+
sourceLocation: SourceLocation = #_sourceLocation,
48+
) {
49+
let commentPrefix =
50+
if let comment {
51+
"\(comment): "
52+
} else {
53+
""
54+
}
55+
#expect(
56+
localFileSystem.isExecutableFile(fixturePath),
57+
"\(commentPrefix)\(fixturePath) does not exist",
58+
sourceLocation: sourceLocation,
59+
)
60+
}
61+
62+
public func expectDirectoryExists(
63+
at path: AbsolutePath,
64+
sourceLocation: SourceLocation = #_sourceLocation,
65+
) {
66+
#expect(
67+
localFileSystem.isDirectory(path),
68+
"Expected directory doesn't exist: \(path)",
69+
sourceLocation: sourceLocation,
70+
)
71+
}
72+
73+
public func expectDirectoryDoesNotExist(
74+
at path: AbsolutePath,
75+
sourceLocation: SourceLocation = #_sourceLocation,
76+
) {
77+
#expect(
78+
!localFileSystem.isDirectory(path),
79+
"Directory exists unexpectedly: \(path)",
80+
sourceLocation: sourceLocation,
81+
)
82+
}
2683

2784
public func expectThrowsCommandExecutionError<T>(
2885
_ expression: @autoclosure () async throws -> T,
@@ -32,8 +89,9 @@ public func expectThrowsCommandExecutionError<T>(
3289
) async {
3390
await expectAsyncThrowsError(try await expression(), message(), sourceLocation: sourceLocation) { error in
3491
guard case SwiftPMError.executionFailure(let processError, let stdout, let stderr) = error,
35-
case AsyncProcessResult.Error.nonZeroExit(let processResult) = processError,
36-
processResult.exitStatus != .terminated(code: 0) else {
92+
case AsyncProcessResult.Error.nonZeroExit(let processResult) = processError,
93+
processResult.exitStatus != .terminated(code: 0)
94+
else {
3795
Issue.record("Unexpected error type: \(error.interpolationDescription)", sourceLocation: sourceLocation)
3896
return
3997
}
@@ -50,7 +108,10 @@ public func expectAsyncThrowsError<T>(
50108
) async {
51109
do {
52110
_ = try await expression()
53-
Issue.record(message() ?? "Expected an error, which did not not.", sourceLocation: sourceLocation)
111+
Issue.record(
112+
message() ?? "Expected an error, which did not occur.",
113+
sourceLocation: sourceLocation,
114+
)
54115
} catch {
55116
errorHandler(error)
56117
}

Sources/_InternalTestSupport/SwiftTesting+Tags.swift

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ extension Tag.TestSize {
2525
extension Tag.Feature {
2626
public enum Command {}
2727
public enum PackageType {}
28+
public enum ProductType {}
29+
public enum TargetType {}
2830

2931
@Tag public static var CodeCoverage: Tag
3032
@Tag public static var Mirror: Tag
33+
@Tag public static var NetRc: Tag
3134
@Tag public static var Resource: Tag
3235
@Tag public static var SpecialCharacters: Tag
3336
@Tag public static var Traits: Tag
@@ -43,14 +46,31 @@ extension Tag.Feature.Command {
4346
}
4447

4548
extension Tag.Feature.Command.Package {
49+
@Tag public static var General: Tag
50+
@Tag public static var AddDependency: Tag
51+
@Tag public static var AddProduct: Tag
52+
@Tag public static var ArchiveSource: Tag
53+
@Tag public static var AddSetting: Tag
54+
@Tag public static var AddTarget: Tag
55+
@Tag public static var AddTargetDependency: Tag
56+
@Tag public static var BuildPlugin: Tag
57+
@Tag public static var Clean: Tag
58+
@Tag public static var CommandPlugin: Tag
59+
@Tag public static var CompletionTool: Tag
4660
@Tag public static var Config: Tag
47-
@Tag public static var Init: Tag
61+
@Tag public static var Describe: Tag
4862
@Tag public static var DumpPackage: Tag
4963
@Tag public static var DumpSymbolGraph: Tag
64+
@Tag public static var Edit: Tag
65+
@Tag public static var Init: Tag
66+
@Tag public static var Migrate: Tag
5067
@Tag public static var Plugin: Tag
5168
@Tag public static var Reset: Tag
69+
@Tag public static var Resolve: Tag
5270
@Tag public static var ShowDependencies: Tag
71+
@Tag public static var ShowExecutables: Tag
5372
@Tag public static var ToolsVersion: Tag
73+
@Tag public static var Unedit: Tag
5474
@Tag public static var Update: Tag
5575
}
5676

@@ -63,6 +83,19 @@ extension Tag.Feature.Command.PackageRegistry {
6383
@Tag public static var Unset: Tag
6484
}
6585

86+
extension Tag.Feature.TargetType {
87+
@Tag public static var Executable: Tag
88+
@Tag public static var Library: Tag
89+
@Tag public static var Macro: Tag
90+
}
91+
92+
extension Tag.Feature.ProductType {
93+
@Tag public static var DynamicLibrary: Tag
94+
@Tag public static var Executable: Tag
95+
@Tag public static var Library: Tag
96+
@Tag public static var Plugin: Tag
97+
@Tag public static var StaticLibrary: Tag
98+
}
6699
extension Tag.Feature.PackageType {
67100
@Tag public static var Library: Tag
68101
@Tag public static var Executable: Tag

Sources/_InternalTestSupport/SwiftTesting+TraitConditional.swift

Lines changed: 80 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
/*
32
This source file is part of the Swift.org open source project
43

@@ -15,7 +14,7 @@ import class PackageModel.UserToolchain
1514
import DriverSupport
1615
import Basics
1716
import Testing
18-
import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported
17+
import TSCclibc // for SPM_posix_spawn_file_actions_addchdir_np_supported
1918

2019
extension Trait where Self == Testing.ConditionTrait {
2120
/// Skip test if the host operating system does not match the running OS.
@@ -39,6 +38,72 @@ extension Trait where Self == Testing.ConditionTrait {
3938
}
4039
}
4140

41+
/// Enabled only if 'llvm-profdata' is available
42+
public static var requiresLLVMProfData: Self {
43+
disabled("skipping test because the `llvm-profdata` tool isn't available") {
44+
let toolPath = try (try? UserToolchain.default)!.getLLVMProf()
45+
return toolPath == nil
46+
}
47+
}
48+
49+
/// Enabled only if 'llvm-cov' is available
50+
public static var requiresLLVMCov: Self {
51+
disabled("skipping test because the `llvm-cov` tool isn't available") {
52+
let toolPath = try (try? UserToolchain.default)!.getLLVMCov()
53+
return toolPath == nil
54+
}
55+
}
56+
57+
/// Enabled only if 'swift-symbolgraph-extract' is available
58+
public static var requiresSymbolgraphExtract: Self {
59+
disabled("skipping test because the `swift-symbolgraph-extract` tools isn't available") {
60+
let toolPath = try (try? UserToolchain.default)!.getSymbolGraphExtract()
61+
return toolPath == nil
62+
}
63+
}
64+
65+
/// Enabled only is stdlib is supported by the toolchain
66+
public static var requiresStdlibSupport: Self {
67+
enabled("skipping because static stdlib is not supported by the toolchain") {
68+
let args = try [
69+
UserToolchain.default.swiftCompilerPath.pathString,
70+
"-static-stdlib", "-emit-executable", "-o", "/dev/null", "-",
71+
]
72+
let process = AsyncProcess(arguments: args)
73+
let stdin = try process.launch()
74+
stdin.write(sequence: "".utf8)
75+
try stdin.close()
76+
let result = try await process.waitUntilExit()
77+
78+
return result.exitStatus == .terminated(code: 0)
79+
}
80+
}
81+
82+
// Enabled if the toolchain has supported features
83+
public static var supportsSupportedFeatures: Self {
84+
enabled("skipping because test environment compiler doesn't support `-print-supported-features`") {
85+
(try? UserToolchain.default)!.supportsSupportedFeatures
86+
}
87+
}
88+
89+
/// Skip of the executable is not available
90+
public static func requires(executable: String) -> Self {
91+
let message: Comment?
92+
let isToolAvailable: Bool
93+
do {
94+
try _requiresTools(executable)
95+
isToolAvailable = true
96+
message = nil
97+
} catch (let AsyncProcessResult.Error.nonZeroExit(result)) {
98+
isToolAvailable = false
99+
message = "Skipping as tool \(executable) is not found in the path. (\(result.description))"
100+
} catch {
101+
isToolAvailable = false
102+
message = "Skipping. Unable to determine if tool exists. Error: \(error) "
103+
}
104+
return enabled(if: isToolAvailable, message)
105+
}
106+
42107
/// Enaled only if marcros are built as dylibs
43108
public static var requiresBuildingMacrosAsDylibs: Self {
44109
enabled("test is only supported if `BUILD_MACROS_AS_DYLIBS` is set") {
@@ -71,16 +136,16 @@ extension Trait where Self == Testing.ConditionTrait {
71136
/// Ensure platform support working directory
72137
public static var requiresWorkingDirectorySupport: Self {
73138
enabled("working directory not supported on this platform") {
74-
#if !os(Windows)
75-
// needed for archiving
76-
if SPM_posix_spawn_file_actions_addchdir_np_supported() {
139+
#if !os(Windows)
140+
// needed for archiving
141+
if SPM_posix_spawn_file_actions_addchdir_np_supported() {
142+
return true
143+
} else {
144+
return false
145+
}
146+
#else
77147
return true
78-
} else {
79-
return false
80-
}
81-
#else
82-
return true
83-
#endif
148+
#endif
84149
}
85150
}
86151

@@ -118,9 +183,9 @@ extension Trait where Self == Testing.ConditionTrait {
118183
public static func skipIfXcodeBuilt() -> Self {
119184
disabled("Tests built by Xcode") {
120185
#if Xcode
121-
true
186+
true
122187
#else
123-
false
188+
false
124189
#endif
125190
}
126191
}
@@ -129,9 +194,9 @@ extension Trait where Self == Testing.ConditionTrait {
129194
public static var requireSwift6_2: Self {
130195
enabled("This test requires Swift 6.2, or newer.") {
131196
#if compiler(>=6.2)
132-
true
197+
true
133198
#else
134-
false
199+
false
135200
#endif
136201
}
137202
}

0 commit comments

Comments
 (0)