Skip to content

Commit

Permalink
deprecate LinuxMain.swift
Browse files Browse the repository at this point in the history
motivation: Swift 5.1 introduced the --enable-test-discovery flag which instructs SwiftPM to discover tests instead of relying on a test manifest file named LinuxMain.swift. This functionality has been pretty well adopted and we are making it the default

changes:
* deprecate --enable-test-discovery flag
* deprecate --generate-linuxmain flag
* automatically detect tests on non-darwin platforms
* support "XCTMain.swift", "LinuxMain.swift" as escape hatch when test discovery is not appropriate. This files take priority.
* `package init` no longet generate LinuxMain.swift
* add and adjust tests
  • Loading branch information
tomerd committed Nov 18, 2020
1 parent 2817ec5 commit bd30771
Show file tree
Hide file tree
Showing 30 changed files with 376 additions and 568 deletions.
10 changes: 10 additions & 0 deletions Fixtures/Miscellaneous/TestDiscovery/Simple/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// swift-tools-version:4.2
import PackageDescription

let package = Package(
name: "Simple",
targets: [
.target(name: "Simple"),
.testTarget(name: "SimpleTests", dependencies: ["Simple"]),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
struct Simple {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import XCTest
@testable import Simple

class SimpleTests: XCTestCase {

func testExample1() {
}

func test_Example2() {
}

func testExample3(arg: String) {
}

func nontest() {
}
}
28 changes: 28 additions & 0 deletions Fixtures/Miscellaneous/TestDiscovery/hello world/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "hello world",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "hello world",
targets: ["hello world"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "hello world",
dependencies: []),
.testTarget(
name: "hello world tests",
dependencies: ["hello world"]),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
struct hello_world {
var text = "Hello, World!"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import XCTest
@testable import hello_world

final class hello_worldTests: XCTestCase {
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(hello_world().text, "Hello, World!")
}
}
4 changes: 2 additions & 2 deletions Sources/Build/BuildDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ final class TestDiscoveryCommand: CustomLLBuildCommand {
}

let outputs = tool.outputs.compactMap{ try? AbsolutePath(validating: $0.name) }
let testsByModule = Dictionary(grouping: tests, by: { $0.module })
let testsByModule = Dictionary(grouping: tests, by: { $0.module.spm_mangledToC99ExtendedIdentifier() })

func isMainFile(_ path: AbsolutePath) -> Bool {
return path.basename == "main.swift"
Expand All @@ -109,7 +109,7 @@ final class TestDiscoveryCommand: CustomLLBuildCommand {

// FIXME: This is relying on implementation detail of the output but passing the
// the context all the way through is not worth it right now.
let module = file.basenameWithoutExt
let module = file.basenameWithoutExt.spm_mangledToC99ExtendedIdentifier()

guard let tests = testsByModule[module] else {
// This module has no tests so just write an empty file for it.
Expand Down
126 changes: 57 additions & 69 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,8 @@ extension BuildParameters {
case .auto:
if configuration == .debug {
addIndexStoreArguments = true
} else if enableTestDiscovery && target.type == .test {
// Test discovery requires an index store for the test targets
// to discover the tests
} else if target.type == .test {
// Test discovery requires an index store for the test target to discover the tests
addIndexStoreArguments = true
} else {
addIndexStoreArguments = false
Expand Down Expand Up @@ -963,15 +962,10 @@ public final class SwiftTargetBuildDescription {

/// Testing arguments according to the build configuration.
private var testingArguments: [String] {
switch buildParameters.configuration {
case .debug:
if buildParameters.enableTestability {
return ["-enable-testing"]
case .release:
if self.buildParameters.enableTestDiscovery {
return ["-enable-testing"]
} else {
return []
}
} else {
return []
}
}

Expand Down Expand Up @@ -1100,8 +1094,8 @@ public final class ProductBuildDescription {
// No arguments for static libraries.
return []
case .test:
// Test products are bundle on macOS, executable on linux.
if buildParameters.triple.isDarwin() {
// Test products are bundle when not using test manifest, executable otherwise.
if !buildParameters.useTestManifest {
args += ["-Xlinker", "-bundle"]
} else {
args += ["-emit-executable"]
Expand Down Expand Up @@ -1224,16 +1218,11 @@ public final class ProductBuildDescription {
public class BuildPlan {

public enum Error: Swift.Error, CustomStringConvertible, Equatable {
/// The linux main file is missing.
case missingLinuxMain

/// There is no buildable target in the graph.
case noBuildableTarget

public var description: String {
switch self {
case .missingLinuxMain:
return "missing LinuxMain.swift file in the Tests directory"
case .noBuildableTarget:
return "the package does not contain a buildable target"
}
Expand Down Expand Up @@ -1273,64 +1262,60 @@ public class BuildPlan {
/// Diagnostics Engine for emitting diagnostics.
let diagnostics: DiagnosticsEngine

private static func planLinuxMain(
private static func makeTestManifestTargets(
_ buildParameters: BuildParameters,
_ graph: PackageGraph
) throws -> [(ResolvedProduct, SwiftTargetBuildDescription)] {
guard !buildParameters.triple.isDarwin() else {
return []
) throws -> [(product: ResolvedProduct, targetBuildDescription: SwiftTargetBuildDescription)] {
guard buildParameters.useTestManifest else {
preconditionFailure("makeTestManifestTargets should not be used for build plan with useTestManifest set to false")
}

var result: [(ResolvedProduct, SwiftTargetBuildDescription)] = []

for testProduct in graph.allProducts where testProduct.type == .test {
// Create the target description from the linux main if test discovery is off.
if !buildParameters.enableTestDiscovery {
guard let linuxMainTarget = testProduct.linuxMainTarget else {
throw Error.missingLinuxMain
}

// if test manifest exists, prefer that over test detection,
// this is designed as an escape hatch when test discovery is not appropriate
// and for backwards compatibility for projects that have existing test manifests (LinuxMain.swift)
if let testManifestTarget = testProduct.testManifestTarget {
let desc = try SwiftTargetBuildDescription(
target: linuxMainTarget,
target: testManifestTarget,
buildParameters: buildParameters,
isTestTarget: true
)

result.append((testProduct, desc))
continue
}

// We'll generate sources containing the test names as part of the build process.
let derivedTestListDir = buildParameters.buildPath.appending(components: "\(testProduct.name)Testlist.derived")
let mainFile = derivedTestListDir.appending(component: "main.swift")

var paths: [AbsolutePath] = []
paths.append(mainFile)
for testTarget in testProduct.targets {
let path = derivedTestListDir.appending(components: testTarget.name + ".swift")
paths.append(path)
}
} else {
// We'll generate sources containing the test names as part of the build process.
let derivedTestListDir = buildParameters.buildPath.appending(components: "\(testProduct.name).derived")
let mainFile = derivedTestListDir.appending(component: "main.swift")

var paths: [AbsolutePath] = []
paths.append(mainFile)
for testTarget in testProduct.targets {
let path = derivedTestListDir.appending(components: testTarget.name + ".swift")
paths.append(path)
}

let src = Sources(paths: paths, root: derivedTestListDir)
let src = Sources(paths: paths, root: derivedTestListDir)

let swiftTarget = SwiftTarget(
testDiscoverySrc: src,
name: testProduct.name,
dependencies: testProduct.underlyingProduct.targets.map { .target($0, conditions: []) }
)
let linuxMainTarget = ResolvedTarget(
target: swiftTarget,
dependencies: testProduct.targets.map { .target($0, conditions: []) }
)
let swiftTarget = SwiftTarget(
testDiscoverySrc: src,
name: testProduct.name,
dependencies: testProduct.underlyingProduct.targets.map { .target($0, conditions: []) }
)
let testManifestTarget = ResolvedTarget(
target: swiftTarget,
dependencies: testProduct.targets.map { .target($0, conditions: []) }
)

let target = try SwiftTargetBuildDescription(
target: linuxMainTarget,
buildParameters: buildParameters,
isTestTarget: true,
testDiscoveryTarget: true
)
let target = try SwiftTargetBuildDescription(
target: testManifestTarget,
buildParameters: buildParameters,
isTestTarget: true,
testDiscoveryTarget: true
)

result.append((testProduct, target))
result.append((testProduct, target))
}
}
return result
}
Expand Down Expand Up @@ -1389,11 +1374,13 @@ public class BuildPlan {
throw Diagnostics.fatalError
}

// Plan the linux main target.
let results = try Self.planLinuxMain(buildParameters, graph)
for result in results {
targetMap[result.1.target] = .swift(result.1)
linuxMainMap[result.0] = result.1.target
// Plan the test manifest target.
if buildParameters.useTestManifest {
let testManifestTargets = try Self.makeTestManifestTargets(buildParameters, graph)
for item in testManifestTargets {
targetMap[item.targetBuildDescription.target] = .swift(item.targetBuildDescription)
testManifestTargetsMap[item.product] = item.targetBuildDescription.target
}
}

var productMap: [ResolvedProduct: ProductBuildDescription] = [:]
Expand All @@ -1413,7 +1400,7 @@ public class BuildPlan {
try plan()
}

private var linuxMainMap: [ResolvedProduct: ResolvedTarget] = [:]
private var testManifestTargetsMap: [ResolvedProduct: ResolvedTarget] = [:]

static func validateDeploymentVersionOfProductDependency(
_ product: ResolvedProduct,
Expand Down Expand Up @@ -1603,9 +1590,10 @@ public class BuildPlan {
}
}

if !buildParameters.triple.isDarwin() {
if product.type == .test {
linuxMainMap[product].map{ staticTargets.append($0) }
// add test manifest targets
if buildParameters.useTestManifest {
if product.type == .test, let testManifestTarget = testManifestTargetsMap[product] {
staticTargets.append(testManifestTarget)
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Build/ManifestBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public class LLBuildManifestBuilder {
}
}

addTestFileGenerationCommand()
addTestManifestGenerationCommand()

// Create command for all products in the plan.
for (_, description) in plan.productMap {
Expand Down Expand Up @@ -727,7 +727,7 @@ extension LLBuildManifestBuilder {
// MARK:- Test File Generation

extension LLBuildManifestBuilder {
fileprivate func addTestFileGenerationCommand() {
fileprivate func addTestManifestGenerationCommand() {
for target in plan.targets {
guard case .swift(let target) = target,
target.isTestTarget,
Expand Down
1 change: 0 additions & 1 deletion Sources/Commands/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ add_library(Commands
APIDigester.swift
Describe.swift
Error.swift
GenerateLinuxMain.swift
MultiRootSupport.swift
Options.swift
show-dependencies.swift
Expand Down
Loading

0 comments on commit bd30771

Please sign in to comment.