Skip to content

[Commands] Add LinuxMainGenerator #1485

New issue

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

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

Already on GitHub? # to your account

Merged
merged 1 commit into from
Feb 9, 2018
Merged
Changes from all commits
Commits
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
11 changes: 10 additions & 1 deletion Fixtures/Miscellaneous/ParallelTestsPkg/Package.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
// swift-tools-version:4.0
import PackageDescription

let package = Package(
name: "ParallelTestsPkg"
name: "ParallelTestsPkg",
targets: [
.target(
name: "ParallelTestsPkg",
dependencies: []),
.testTarget(
name: "ParallelTestsPkgTests",
dependencies: ["ParallelTestsPkg"]),
]
)
6 changes: 0 additions & 6 deletions Fixtures/Miscellaneous/ParallelTestsPkg/Tests/LinuxMain.swift

This file was deleted.

Original file line number Diff line number Diff line change
@@ -6,10 +6,4 @@ class ParallelTestsFailureTests: XCTestCase {
func testSureFailure() {
XCTFail("Giving up is the only sure way to fail.")
}

static var allTests : [(String, (ParallelTestsFailureTests) -> () throws -> Void)] {
return [
("testSureFailure", testSureFailure),
]
}
}
Original file line number Diff line number Diff line change
@@ -10,11 +10,4 @@ class ParallelTestsTests: XCTestCase {
func testExample2() {
XCTAssertEqual(ParallelTests().bool, false)
}

static var allTests : [(String, (ParallelTestsTests) -> () throws -> Void)] {
return [
("testExample1", testExample1),
("testExample2", testExample2),
]
}
}
202 changes: 202 additions & 0 deletions Sources/Commands/GenerateLinuxMain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
This source file is part of the Swift.org open source project

Copyright 2015 - 2018 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import Basic
import PackageGraph
import PackageModel

/// A utility for generating test entries on linux.
///
/// This uses input from macOS's test discovery and generates
/// corelibs-xctest compatible test manifests.
final class LinuxMainGenerator {

enum Error: Swift.Error {
case noTestTargets
}

/// The package graph we're working on.
let graph: PackageGraph

/// The test suites that we need to write.
let testSuites: [TestSuite]

init(graph: PackageGraph, testSuites: [TestSuite]) {
self.graph = graph
self.testSuites = testSuites
}

/// Generate the XCTestManifests.swift and LinuxMain.swift for the package.
func generate() throws {
// Create the module struct from input.
//
// This converts the input test suite into a structure that
// is more suitable for generating linux test entries.
let modulesBuilder = ModulesBuilder()
for suite in testSuites {
modulesBuilder.add(suite.tests)
}
let modules = modulesBuilder.build()

// Generate manifest file for each test module we got from XCTest discovery.
for module in modules.lazy.sorted(by: { $0.name < $1.name }) {
guard let target = graph.reachableTargets.first(where: { $0.c99name == module.name }) else {
print("warning: did not file target '\(module.name)'")
continue
}
assert(target.type == .test, "Unexpected target type \(target.type) for \(target)")

// Write the manifest file for this module.
let testManifest = target.sources.root.appending(component: "XCTestManifests.swift")
let stream = try LocalFileOutputByteStream(testManifest)

stream <<< "import XCTest" <<< "\n"
for klass in module.classes.lazy.sorted(by: { $0.name < $1.name }) {
stream <<< "\n"
stream <<< "extension " <<< klass.name <<< " {" <<< "\n"
stream <<< indent(4) <<< "static let __allTests = [" <<< "\n"
for method in klass.methods {
stream <<< indent(8) <<< "(\"\(method)\", \(method))," <<< "\n"
}
stream <<< indent(4) <<< "]" <<< "\n"
stream <<< "}" <<< "\n"
}

stream <<<
"""

#if !os(macOS)
public func __allTests() -> [XCTestCaseEntry] {
return [

"""

for klass in module.classes {
stream <<< indent(8) <<< "testCase(" <<< klass.name <<< ".__allTests)," <<< "\n"
}

stream <<< """
]
}
#endif

"""
stream.flush()
}

/// Write LinuxMain.swift file.
guard let testTarget = graph.reachableProducts.first(where: { $0.type == .test })?.targets.first else {
throw Error.noTestTargets
}
let linuxMain = testTarget.sources.root.parentDirectory.appending(components: SwiftTarget.linuxMainBasename)

let stream = try LocalFileOutputByteStream(linuxMain)
stream <<< "import XCTest" <<< "\n\n"
for module in modules {
stream <<< "import " <<< module.name <<< "\n"
}
stream <<< "\n"
stream <<< "var tests = [XCTestCaseEntry]()" <<< "\n"
for module in modules {
stream <<< "tests += \(module.name).__allTests()" <<< "\n"
}
stream <<< "\n"
stream <<< "XCTMain(tests)" <<< "\n"
stream.flush()
}

private func indent(_ spaces: Int) -> ByteStreamable {
return Format.asRepeating(string: " ", count: spaces)
}
}

// MARK: - Internal data structure for LinuxMainGenerator.

private struct Module {
struct Class {
let name: String
let methods: [String]
}
let name: String
let classes: [Class]
}

private final class ModulesBuilder {

final class ModuleBuilder {
let name: String
var classes: [ClassBuilder]

init(_ name: String) {
self.name = name
self.classes = []
}

func build() -> Module {
return Module(name: name, classes: classes.map({ $0.build() }))
}
}

final class ClassBuilder {
let name: String
var methods: [String]

init(_ name: String) {
self.name = name
self.methods = []
}

func build() -> Module.Class {
return .init(name: name, methods: methods)
}
}

/// The built modules.
private var modules: [ModuleBuilder] = []

func add(_ cases: [TestSuite.TestCase]) {
for testCase in cases {
let (module, theKlass) = testCase.name.split(around: ".")
guard let klass = theKlass else {
fatalError("Unsupported test case name \(testCase.name)")
}
for method in testCase.tests {
add(module, klass, method)
}
}
}

private func add(_ moduleName: String, _ klassName: String, _ methodName: String) {
// Find or create the module.
let module: ModuleBuilder
if let theModule = modules.first(where: { $0.name == moduleName }) {
module = theModule
} else {
module = ModuleBuilder(moduleName)
modules.append(module)
}

// Find or create the class.
let klass: ClassBuilder
if let theKlass = module.classes.first(where: { $0.name == klassName }) {
klass = theKlass
} else {
klass = ClassBuilder(klassName)
module.classes.append(klass)
}

// Finally, append the method to the class.
klass.methods.append(methodName)
}

func build() -> [Module] {
return modules.map({ $0.build() })
}
}
34 changes: 29 additions & 5 deletions Sources/Commands/SwiftTestTool.swift
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ import class Foundation.ProcessInfo
import Basic
import Build
import Utility
import PackageGraph

import func POSIX.exit

@@ -73,6 +74,10 @@ public class TestToolOptions: ToolOptions {
return .listTests
}

if shouldGenerateLinuxMain {
return .generateLinuxMain
}

return .runSerial
}

@@ -85,6 +90,9 @@ public class TestToolOptions: ToolOptions {
/// List the tests and exit.
var shouldListTests = false

/// Generate LinuxMain entries and exit.
var shouldGenerateLinuxMain = false

var testCaseSpecifier: TestCaseSpecifier = .none
}

@@ -103,6 +111,7 @@ public enum TestCaseSpecifier {
public enum TestMode {
case version
case listTests
case generateLinuxMain
case runSerial
case runParallel
}
@@ -121,13 +130,14 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
}

override func runImpl() throws {
let graph = try loadPackageGraph()

switch options.mode {
case .version:
print(Versioning.currentVersion.completeDisplayString)

case .listTests:
let testPath = try buildTestsIfNeeded(options)
let testPath = try buildTestsIfNeeded(options, graph: graph)
let testSuites = try getTestSuites(path: testPath)
let tests = testSuites.filteredTests(specifier: options.testCaseSpecifier)

@@ -136,8 +146,17 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
print(test.specifier)
}

case .generateLinuxMain:
#if os(Linux)
warning(message: "can't discover new tests on Linux; please use this option on macOS instead")
#endif
let testPath = try buildTestsIfNeeded(options, graph: graph)
let testSuites = try getTestSuites(path: testPath)
let generator = LinuxMainGenerator(graph: graph, testSuites: testSuites)
try generator.generate()

case .runSerial:
let testPath = try buildTestsIfNeeded(options)
let testPath = try buildTestsIfNeeded(options, graph: graph)
var ranSuccessfully = true

switch options.testCaseSpecifier {
@@ -172,7 +191,7 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
}

case .runParallel:
let testPath = try buildTestsIfNeeded(options)
let testPath = try buildTestsIfNeeded(options, graph: graph)
let testSuites = try getTestSuites(path: testPath)
let tests = testSuites.filteredTests(specifier: options.testCaseSpecifier)

@@ -195,8 +214,8 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
/// Builds the "test" target if enabled in options.
///
/// - Returns: The path to the test binary.
private func buildTestsIfNeeded(_ options: TestToolOptions) throws -> AbsolutePath {
let buildPlan = try BuildPlan(buildParameters: self.buildParameters(), graph: loadPackageGraph())
private func buildTestsIfNeeded(_ options: TestToolOptions, graph: PackageGraph) throws -> AbsolutePath {
let buildPlan = try BuildPlan(buildParameters: self.buildParameters(), graph: graph)
if options.shouldBuildTests {
try build(plan: buildPlan, subset: .allIncludingTests)
}
@@ -225,6 +244,11 @@ public class SwiftTestTool: SwiftTool<TestToolOptions> {
usage: "Lists test methods in specifier format"),
to: { $0.shouldListTests = $1 })

binder.bind(
option: parser.add(option: "--generate-linuxmain", kind: Bool.self,
usage: "Generate LinuxMain.swift entries for the package"),
to: { $0.shouldGenerateLinuxMain = $1 })

binder.bind(
option: parser.add(option: "--parallel", kind: Bool.self,
usage: "Run the tests in parallel."),
Loading