Skip to content

Commit 2737eea

Browse files
authored
Merge pull request #494 from ddunbar/SR-1135
[Commands] Fix `swift-test` to always build using the correct manifest.
2 parents fae6d5e + 8be6faf commit 2737eea

File tree

5 files changed

+100
-90
lines changed

5 files changed

+100
-90
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Note: This is in reverse chronological order, so newer entries are added to the top.
2+
3+
Swift 3.0
4+
---------
5+
6+
* It is no longer necessary to run `swift build` before running `swift test` (it
7+
will always regenerates the build manifest when necessary). In addition, it
8+
now accepts (and requires) the same `-Xcc`, etc. options as are used with
9+
`swift build`.
10+
11+
* The `Package` initializer now requires the `name:` parameter.

Sources/Build/Configuration.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
public enum Configuration {
1212
case debug, release
1313

14-
var dirname: String {
14+
public var dirname: String {
1515
switch self {
1616
case .debug: return "debug"
1717
case .release: return "release"

Sources/Commands/SwiftBuildTool.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import Get
1414
import PackageLoading
1515
import PackageModel
1616
import Utility
17-
import Xcodeproj
1817

1918
#if HasCustomVersionString
2019
import VersionInfo

Sources/Commands/SwiftTestTool.swift

Lines changed: 59 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ See http://swift.org/CONTRIBUTORS.txt for Swift project authors
1111
import class Foundation.ProcessInfo
1212

1313
import Basic
14+
import Build
1415
import Utility
1516

1617
#if HasCustomVersionString
@@ -21,8 +22,9 @@ import func POSIX.chdir
2122
import func POSIX.exit
2223

2324
private enum TestError: Swift.Error {
24-
case testsExecutableNotFound
2525
case invalidListTestJSONData
26+
case multipleTestProducts
27+
case testsExecutableNotFound
2628
}
2729

2830
extension TestError: CustomStringConvertible {
@@ -32,6 +34,8 @@ extension TestError: CustomStringConvertible {
3234
return "no tests found to execute, create a module in your `Tests' directory"
3335
case .invalidListTestJSONData:
3436
return "Invalid list test JSON structure."
37+
case .multipleTestProducts:
38+
return "cannot test packages with multiple test products defined"
3539
}
3640
}
3741
}
@@ -75,11 +79,16 @@ private func ==(lhs: Mode, rhs: Mode) -> Bool {
7579
return lhs.description == rhs.description
7680
}
7781

82+
// FIXME: Merge this with the `swift-build` arguments.
7883
private enum TestToolFlag: Argument {
84+
case xcc(String)
85+
case xld(String)
86+
case xswiftc(String)
7987
case chdir(AbsolutePath)
8088
case buildPath(AbsolutePath)
8189
case colorMode(ColorWrap.Mode)
8290
case skipBuild
91+
case ignoreDependencies
8392
case verbose(Int)
8493

8594
init?(argument: String, pop: () -> String?) throws {
@@ -95,6 +104,12 @@ private enum TestToolFlag: Argument {
95104
self = .verbose(1)
96105
case "--skip-build":
97106
self = .skipBuild
107+
case "-Xcc":
108+
self = try .xcc(forcePop())
109+
case "-Xlinker":
110+
self = try .xld(forcePop())
111+
case "-Xswiftc":
112+
self = try .xswiftc(forcePop())
98113
case "--build-path":
99114
self = try .buildPath(AbsolutePath(forcePop(), relativeTo: currentWorkingDirectory))
100115
case "--color":
@@ -103,6 +118,8 @@ private enum TestToolFlag: Argument {
103118
throw OptionParserError.invalidUsage("invalid color mode: \(rawValue)")
104119
}
105120
self = .colorMode(mode)
121+
case "--ignore-dependencies":
122+
self = .ignoreDependencies
106123
default:
107124
return nil
108125
}
@@ -113,6 +130,8 @@ private class TestToolOptions: Options {
113130
var verbosity: Int = 0
114131
var buildTests: Bool = true
115132
var colorMode: ColorWrap.Mode = .Auto
133+
var flags = BuildFlags()
134+
var ignoreDependencies: Bool = false
116135
}
117136

118137
/// swift-test tool namespace
@@ -133,7 +152,7 @@ public struct SwiftTestTool: SwiftTool {
133152
if let dir = opts.chdir {
134153
try chdir(dir.asString)
135154
}
136-
155+
137156
switch mode {
138157
case .usage:
139158
usage()
@@ -146,7 +165,7 @@ public struct SwiftTestTool: SwiftTool {
146165
#endif
147166

148167
case .listTests:
149-
let testPath = try determineTestPath(opts: opts)
168+
let testPath = try buildTestsIfNeeded(opts)
150169
let testSuites = try getTestSuites(path: testPath)
151170
// Print the tests.
152171
for testSuite in testSuites {
@@ -158,9 +177,7 @@ public struct SwiftTestTool: SwiftTool {
158177
}
159178

160179
case .run(let specifier):
161-
try buildTestsIfNeeded(opts)
162-
let testPath = try determineTestPath(opts: opts)
163-
180+
let testPath = try buildTestsIfNeeded(opts)
164181
let success = test(path: testPath, xctestArg: specifier)
165182
exit(success ? 0 : 1)
166183
}
@@ -172,57 +189,40 @@ public struct SwiftTestTool: SwiftTool {
172189
}
173190
}
174191

175-
private let configuration = "debug" //FIXME should swift-test support configuration option?
176-
177192
/// Builds the "test" target if enabled in options.
178-
private func buildTestsIfNeeded(_ opts: TestToolOptions) throws {
179-
let yamlPath = opts.path.build.appending(RelativePath("\(configuration).yaml"))
193+
///
194+
/// - Returns: The path to the test binary.
195+
private func buildTestsIfNeeded(_ opts: TestToolOptions) throws -> AbsolutePath {
196+
let graph = try loadPackage(at: opts.path.root, ignoreDependencies: opts.ignoreDependencies)
180197
if opts.buildTests {
181-
try build(yamlPath: yamlPath, target: "test")
198+
let yaml = try describe(opts.path.build, configuration, graph, flags: opts.flags, toolchain: UserToolchain())
199+
try build(yamlPath: yaml, target: "test")
182200
}
183-
}
184-
185-
/// Locates the XCTest bundle on OSX and XCTest executable on Linux.
186-
/// First check if <build_path>/debug/<PackageName>Tests.xctest is present, otherwise
187-
/// walk the build folder and look for folder/file ending with `.xctest`.
188-
///
189-
/// - Parameters:
190-
/// - opts: Options object created by parsing the commandline arguments.
191-
///
192-
/// - Throws: TestError
193-
///
194-
/// - Returns: Path to XCTest bundle (OSX) or executable (Linux).
195-
private func determineTestPath(opts: Options) throws -> AbsolutePath {
196-
197-
//FIXME better, ideally without parsing manifest since
198-
// that makes us depend on the whole Manifest system
199-
200-
let packageName = opts.path.root.basename //FIXME probably not true
201-
let maybePath = opts.path.build.appending(RelativePath(configuration)).appending(RelativePath("\(packageName)Tests.xctest"))
202-
203-
let possibleTestPath: AbsolutePath
204-
205-
if exists(maybePath) {
206-
possibleTestPath = maybePath
207-
} else {
208-
let possiblePaths = try walk(opts.path.build).filter {
209-
$0.basename != "Package.xctest" && // this was our hardcoded name, may still exist if no clean
210-
$0.suffix == ".xctest"
201+
202+
// See the logic in `PackageLoading`'s `PackageExtensions.swift`.
203+
//
204+
// FIXME: We should also check if the package has any test
205+
// modules, which isn't trivial (yet).
206+
let testProducts = graph.products.filter{
207+
if case .Test = $0.type {
208+
return true
209+
} else {
210+
return false
211211
}
212-
213-
guard let path = possiblePaths.first else {
214-
throw TestError.testsExecutableNotFound
215-
}
216-
217-
possibleTestPath = path
218212
}
219-
220-
guard isValidTestPath(possibleTestPath) else {
213+
if testProducts.count == 0 {
221214
throw TestError.testsExecutableNotFound
215+
} else if testProducts.count > 1 {
216+
throw TestError.multipleTestProducts
217+
} else {
218+
return opts.path.build.appending(RelativePath(configuration.dirname)).appending(component: testProducts[0].name + ".xctest")
222219
}
223-
return possibleTestPath
224220
}
225221

222+
// FIXME: We need to support testing in other build configurations, but need
223+
// to solve the testability problem first.
224+
private let configuration = Build.Configuration.debug
225+
226226
private func usage(_ print: (String) -> Void = { print($0) }) {
227227
// .........10.........20.........30.........40.........50.........60.........70..
228228
print("OVERVIEW: Build and run tests")
@@ -238,6 +238,9 @@ public struct SwiftTestTool: SwiftTool {
238238
print(" --color <mode> Specify color mode (auto|always|never) [default: auto]")
239239
print(" -v, --verbose Increase verbosity of informational output")
240240
print(" --skip-build Skip building the test target")
241+
print(" -Xcc <flag> Pass flag through to all C compiler invocations")
242+
print(" -Xlinker <flag> Pass flag through to all linker invocations")
243+
print(" -Xswiftc <flag> Pass flag through to all Swift compiler invocations")
241244
print("")
242245
print("NOTE: Use `swift package` to perform other functions on packages")
243246
}
@@ -252,12 +255,20 @@ public struct SwiftTestTool: SwiftTool {
252255
opts.chdir = path
253256
case .verbose(let amount):
254257
opts.verbosity += amount
258+
case .xcc(let value):
259+
opts.flags.cCompilerFlags.append(value)
260+
case .xld(let value):
261+
opts.flags.linkerFlags.append(value)
262+
case .xswiftc(let value):
263+
opts.flags.swiftCompilerFlags.append(value)
255264
case .buildPath(let buildPath):
256265
opts.path.build = buildPath
257266
case .colorMode(let mode):
258267
opts.colorMode = mode
259268
case .skipBuild:
260269
opts.buildTests = false
270+
case .ignoreDependencies:
271+
opts.ignoreDependencies = true
261272
}
262273
}
263274

@@ -272,8 +283,6 @@ public struct SwiftTestTool: SwiftTool {
272283
///
273284
/// - Returns: True if execution exited with return code 0.
274285
private func test(path: AbsolutePath, xctestArg: String? = nil) -> Bool {
275-
precondition(isValidTestPath(path))
276-
277286
var args: [String] = []
278287
#if os(macOS)
279288
args = ["xcrun", "xctest"]
@@ -330,9 +339,6 @@ public struct SwiftTestTool: SwiftTool {
330339
///
331340
/// - Returns: Array of TestSuite
332341
private func getTestSuites(path: AbsolutePath) throws -> [TestSuite] {
333-
// Make sure tests are present.
334-
guard isValidTestPath(path) else { throw TestError.testsExecutableNotFound }
335-
336342
// Run the correct tool.
337343
#if os(macOS)
338344
let tempFile = try TemporaryFile()
@@ -349,14 +355,6 @@ public struct SwiftTestTool: SwiftTool {
349355
}
350356
}
351357

352-
private func isValidTestPath(_ path: AbsolutePath) -> Bool {
353-
#if os(macOS)
354-
return isDirectory(path) // ${foo}.xctest is dir on OSX
355-
#else
356-
return isFile(path) // otherwise ${foo}.xctest is executable file
357-
#endif
358-
}
359-
360358
/// A struct to hold the XCTestSuite data.
361359
struct TestSuite {
362360

Utilities/bootstrap

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -775,53 +775,58 @@ def main():
775775
make_fake_toolchain()
776776

777777
# Build the package manager with itself.
778-
779-
env_cmd = ["env", "SWIFT_EXEC=" + os.path.join(bindir, "swiftc"),
780-
"SWIFT_BUILD_PATH=" + build_path]
781-
if args.sysroot:
782-
env_cmd.append("SYSROOT=" + args.sysroot)
783778

784-
cmd = [bootstrapped_product]
779+
# Compute the build flags, needed for swift-build and swift-test.
780+
build_flags = []
781+
785782
# We need to embed an RPATH so swift-{build,test} can find the core
786783
# libraries.
787784
if platform.system() == 'Linux':
788785
embed_rpath = "$ORIGIN/../lib/swift/linux"
789786
else:
790787
embed_rpath = "@executable_path/../lib/swift/macosx"
791-
cmd.extend(["-Xlinker", "-rpath", "-Xlinker", embed_rpath])
788+
build_flags.extend(["-Xlinker", "-rpath", "-Xlinker", embed_rpath])
792789
if args.verbose:
793-
cmd.append("-v")
790+
build_flags.append("-v")
794791

795792
# If appropriate, add flags for a custom version string:
796793
if args.version_str:
797794
incdir = os.path.join(sandbox_path, "inc")
798-
cmd.extend(["-Xswiftc", "-I{}".format(incdir)])
799-
cmd.extend(["-Xswiftc", "-DHasCustomVersionString"])
795+
build_flags.extend(["-Xswiftc", "-I{}".format(incdir)])
796+
build_flags.extend(["-Xswiftc", "-DHasCustomVersionString"])
800797

801798
if args.foundation_path:
802799
core_foundation_path = os.path.join(
803800
args.foundation_path, "usr", "lib", "swift")
804801
# Tell the linker where to look for XCTest, but autolinking
805802
# knows to pass -lXCTest.
806-
cmd.extend(["-Xlinker", "-L", "-Xlinker", args.foundation_path])
803+
build_flags.extend(["-Xlinker", "-L", "-Xlinker", args.foundation_path])
807804
# Add an RPATH, so that the tests can be run directly.
808-
cmd.extend(["-Xlinker", "-rpath", "-Xlinker", args.foundation_path])
809-
cmd.extend(["-Xswiftc", "-I{}".format(args.foundation_path)])
810-
cmd.extend(["-Xswiftc", "-I{}".format(core_foundation_path)])
805+
build_flags.extend(["-Xlinker", "-rpath", "-Xlinker",
806+
args.foundation_path])
807+
build_flags.extend(["-Xswiftc", "-I{}".format(args.foundation_path)])
808+
build_flags.extend(["-Xswiftc", "-I{}".format(core_foundation_path)])
811809

812810
if args.xctest_path:
813811
# Tell the linker where to look for XCTest, but autolinking
814812
# knows to pass -lXCTest.
815-
cmd.extend(["-Xlinker", "-L", "-Xlinker", args.xctest_path])
813+
build_flags.extend(["-Xlinker", "-L", "-Xlinker", args.xctest_path])
816814
# Add an RPATH, so that the tests can be run directly.
817-
cmd.extend(["-Xlinker", "-rpath", "-Xlinker", args.xctest_path])
818-
cmd.extend(["-Xswiftc", "-I{}".format(args.xctest_path)])
815+
build_flags.extend(["-Xlinker", "-rpath", "-Xlinker", args.xctest_path])
816+
build_flags.extend(["-Xswiftc", "-I{}".format(args.xctest_path)])
819817

820818
if args.libdispatch_build_dir:
821-
cmd.extend(["-Xswiftc", "-I{}".format(os.path.join(args.libdispatch_build_dir,
822-
"src"))])
823-
cmd.extend(["-Xswiftc", "-I{}".format(args.libdispatch_source_dir)])
824-
cmd.extend(["-Xcc", "-fblocks"])
819+
build_flags.extend(["-Xswiftc", "-I{}".format(
820+
os.path.join(args.libdispatch_build_dir, "src"))])
821+
build_flags.extend(["-Xswiftc", "-I{}".format(
822+
args.libdispatch_source_dir)])
823+
build_flags.extend(["-Xcc", "-fblocks"])
824+
825+
env_cmd = ["env", "SWIFT_EXEC=" + os.path.join(bindir, "swiftc"),
826+
"SWIFT_BUILD_PATH=" + build_path]
827+
if args.sysroot:
828+
env_cmd.append("SYSROOT=" + args.sysroot)
829+
cmd = env_cmd + [bootstrapped_product] + build_flags
825830

826831
if args.release:
827832
cmd.extend(["--configuration", "release"])
@@ -832,8 +837,6 @@ def main():
832837
if not args.release:
833838
cmd.extend(["--build-tests"])
834839

835-
cmd = env_cmd + cmd
836-
837840
note("building self-hosted 'swift-build': %s" % (
838841
' '.join(cmd),))
839842
result = subprocess.call(cmd, cwd=g_project_root)
@@ -848,14 +851,13 @@ def main():
848851
swift_package_path = os.path.join(build_path, conf, "swift-package")
849852
swift_build_path = os.path.join(build_path, conf, "swift-build")
850853
swift_test_path = os.path.join(build_path, conf, "swift-test")
851-
swiftpm_xctest_helper_path = os.path.join(build_path, conf, "swiftpm-xctest-helper")
854+
swiftpm_xctest_helper_path = os.path.join(
855+
build_path, conf, "swiftpm-xctest-helper")
852856

853857
# If testing, run each of the test bundles.
854858
if "test" in build_actions:
855859
# Construct the test environment.
856-
env_cmd = ["env", "SWIFT_EXEC=" + os.path.join(bindir, "swiftc"),
857-
"SWIFT_BUILD_PATH=" + build_path]
858-
cmd = env_cmd + [swift_test_path]
860+
cmd = env_cmd + [swift_test_path] + build_flags
859861
result = subprocess.call(cmd, cwd=g_project_root)
860862
if result != 0:
861863
error("tests failed with exit status %d" % (result,))

0 commit comments

Comments
 (0)