Skip to content

Commit 0ddf05c

Browse files
authored
restore generate-linux-main for backwards compatibility (#3207) (#3211)
motivation: users made the point that while deprecating generate-linux-main makes sense, we should not deprecate it too quickly so they can support SwiftPM < 5.1 more easily changes: * restore LinuxMainGenerator command * resotre plumbing to call LinuxMainGenerator on --generate-linux-main while retaining the deprecation warning * add test to test both deprecation warning and that the generated code is created rdar://72160848
1 parent e9605e1 commit 0ddf05c

File tree

4 files changed

+280
-2
lines changed

4 files changed

+280
-2
lines changed

Sources/Commands/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library(Commands
1010
APIDigester.swift
1111
Describe.swift
1212
Error.swift
13+
GenerateLinuxMain.swift
1314
MultiRootSupport.swift
1415
Options.swift
1516
show-dependencies.swift
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/*
2+
This source file is part of the Swift.org open source project
3+
4+
Copyright 2015 - 2018 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See http://swift.org/LICENSE.txt for license information
8+
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
*/
10+
11+
import Basics
12+
import PackageGraph
13+
import PackageModel
14+
import TSCBasic
15+
16+
/// A utility for generating test entries on linux.
17+
///
18+
/// This uses input from macOS's test discovery and generates
19+
/// corelibs-xctest compatible test manifests.
20+
///
21+
/// this functionality is deprecated as of 12/2020
22+
/// We are keeping it here for transition purposes
23+
/// This class is to be removed in future releases
24+
final class LinuxMainGenerator {
25+
26+
enum Error: Swift.Error {
27+
case noTestTargets
28+
}
29+
30+
/// The package graph we're working on.
31+
let graph: PackageGraph
32+
33+
/// The test suites that we need to write.
34+
let testSuites: [TestSuite]
35+
36+
init(graph: PackageGraph, testSuites: [TestSuite]) {
37+
self.graph = graph
38+
self.testSuites = testSuites
39+
}
40+
41+
/// Generate the XCTestManifests.swift and LinuxMain.swift for the package.
42+
func generate() throws {
43+
// Create the module struct from input.
44+
//
45+
// This converts the input test suite into a structure that
46+
// is more suitable for generating linux test entries.
47+
let modulesBuilder = ModulesBuilder()
48+
for suite in testSuites {
49+
modulesBuilder.add(suite.tests)
50+
}
51+
let modules = modulesBuilder.build().sorted(by: { $0.name < $1.name })
52+
53+
// Generate manifest file for each test module we got from XCTest discovery.
54+
for module in modules {
55+
guard let target = graph.reachableTargets.first(where: { $0.c99name == module.name }) else {
56+
print("warning: did not find target '\(module.name)'")
57+
continue
58+
}
59+
assert(target.type == .test, "Unexpected target type \(target.type) for \(target)")
60+
61+
// Write the manifest file for this module.
62+
let testManifest = target.sources.root.appending(component: "XCTestManifests.swift")
63+
let stream = try LocalFileOutputByteStream(testManifest)
64+
65+
stream <<< "#if !canImport(ObjectiveC)" <<< "\n"
66+
stream <<< "import XCTest" <<< "\n"
67+
for klass in module.classes.lazy.sorted(by: { $0.name < $1.name }) {
68+
stream <<< "\n"
69+
stream <<< "extension " <<< klass.name <<< " {" <<< "\n"
70+
stream <<< indent(4) <<< "// DO NOT MODIFY: This is autogenerated, use:\n"
71+
stream <<< indent(4) <<< "// `swift test --generate-linuxmain`\n"
72+
stream <<< indent(4) <<< "// to regenerate.\n"
73+
stream <<< indent(4) <<< "static let __allTests__\(klass.name) = [" <<< "\n"
74+
for method in klass.methods {
75+
stream <<< indent(8) <<< "(\"\(method)\", \(method))," <<< "\n"
76+
}
77+
stream <<< indent(4) <<< "]" <<< "\n"
78+
stream <<< "}" <<< "\n"
79+
}
80+
81+
stream <<<
82+
"""
83+
84+
public func __allTests() -> [XCTestCaseEntry] {
85+
return [
86+
87+
"""
88+
89+
for klass in module.classes {
90+
stream <<< indent(8) <<< "testCase(" <<< klass.name <<< ".__allTests__\(klass.name))," <<< "\n"
91+
}
92+
93+
stream <<< """
94+
]
95+
}
96+
#endif
97+
98+
"""
99+
stream.flush()
100+
}
101+
102+
/// Write LinuxMain.swift file.
103+
guard let testTarget = graph.reachableProducts.first(where: { $0.type == .test })?.targets.first else {
104+
throw Error.noTestTargets
105+
}
106+
guard let linuxMainFileName = SwiftTarget.testManifestNames.first(where: { $0.lowercased().hasPrefix("linux") }) else {
107+
throw InternalError("Unknown linux main file name")
108+
}
109+
let linuxMain = testTarget.sources.root.parentDirectory.appending(components: linuxMainFileName)
110+
111+
let stream = try LocalFileOutputByteStream(linuxMain)
112+
stream <<< "import XCTest" <<< "\n\n"
113+
for module in modules {
114+
stream <<< "import " <<< module.name <<< "\n"
115+
}
116+
stream <<< "\n"
117+
stream <<< "var tests = [XCTestCaseEntry]()" <<< "\n"
118+
for module in modules {
119+
stream <<< "tests += \(module.name).__allTests()" <<< "\n"
120+
}
121+
stream <<< "\n"
122+
stream <<< "XCTMain(tests)" <<< "\n"
123+
stream.flush()
124+
}
125+
126+
private func indent(_ spaces: Int) -> ByteStreamable {
127+
return Format.asRepeating(string: " ", count: spaces)
128+
}
129+
}
130+
131+
// MARK: - Internal data structure for LinuxMainGenerator.
132+
133+
private struct Module {
134+
struct Class {
135+
let name: String
136+
let methods: [String]
137+
}
138+
let name: String
139+
let classes: [Class]
140+
}
141+
142+
private final class ModulesBuilder {
143+
144+
final class ModuleBuilder {
145+
let name: String
146+
var classes: [ClassBuilder]
147+
148+
init(_ name: String) {
149+
self.name = name
150+
self.classes = []
151+
}
152+
153+
func build() -> Module {
154+
return Module(name: name, classes: classes.map({ $0.build() }))
155+
}
156+
}
157+
158+
final class ClassBuilder {
159+
let name: String
160+
var methods: [String]
161+
162+
init(_ name: String) {
163+
self.name = name
164+
self.methods = []
165+
}
166+
167+
func build() -> Module.Class {
168+
return .init(name: name, methods: methods)
169+
}
170+
}
171+
172+
/// The built modules.
173+
private var modules: [ModuleBuilder] = []
174+
175+
func add(_ cases: [TestSuite.TestCase]) {
176+
for testCase in cases {
177+
let (module, theKlass) = testCase.name.spm_split(around: ".")
178+
guard let klass = theKlass else {
179+
// Ignore the classes that have zero tests.
180+
if testCase.tests.isEmpty {
181+
continue
182+
}
183+
fatalError("unreachable \(testCase.name)")
184+
}
185+
for method in testCase.tests {
186+
add(module, klass, method)
187+
}
188+
}
189+
}
190+
191+
private func add(_ moduleName: String, _ klassName: String, _ methodName: String) {
192+
// Find or create the module.
193+
let module: ModuleBuilder
194+
if let theModule = modules.first(where: { $0.name == moduleName }) {
195+
module = theModule
196+
} else {
197+
module = ModuleBuilder(moduleName)
198+
modules.append(module)
199+
}
200+
201+
// Find or create the class.
202+
let klass: ClassBuilder
203+
if let theKlass = module.classes.first(where: { $0.name == klassName }) {
204+
klass = theKlass
205+
} else {
206+
klass = ClassBuilder(klassName)
207+
module.classes.append(klass)
208+
}
209+
210+
// Finally, append the method to the class.
211+
klass.methods.append(methodName)
212+
}
213+
214+
func build() -> [Module] {
215+
return modules.map({ $0.build() })
216+
}
217+
}

Sources/Commands/SwiftTestTool.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,19 @@ public struct SwiftTestTool: SwiftCommand {
231231
print(codeCovAsJSONPath(buildParameters: buildParameters, packageName: rootManifest.name))
232232

233233
case .generateLinuxMain:
234-
return // warning emitted by validateArguments
234+
// this functionality is deprecated as of 12/2020
235+
// but we are keeping it here for transition purposes
236+
// to be removed in future releases
237+
// deprecation warning is emitted by validateArguments
238+
#if os(Linux)
239+
swiftTool.diagnostics.emit(warning: "can't discover tests on Linux; please use this option on macOS instead")
240+
#endif
241+
let graph = try swiftTool.loadPackageGraph()
242+
let testProducts = try buildTestsIfNeeded(swiftTool: swiftTool)
243+
let testSuites = try getTestSuites(in: testProducts, swiftTool: swiftTool)
244+
let allTestSuites = testSuites.values.flatMap { $0 }
245+
let generator = LinuxMainGenerator(graph: graph, testSuites: allTestSuites)
246+
try generator.generate()
235247

236248
case .runSerial:
237249
let toolchain = try swiftTool.getToolchain()

Tests/FunctionalTests/MiscellaneousTests.swift

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,14 +506,62 @@ class MiscellaneousTestCase: XCTestCase {
506506
XCTAssertMatch(stderr, .contains("warning: '--enable-test-discovery' option is deprecated"))
507507
}
508508
}
509-
509+
510510
func testGenerateLinuxMainDeprecation() {
511511
fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in
512512
let (_, stderr) = try SwiftPMProduct.SwiftTest.execute(["--generate-linuxmain"], packagePath: path)
513+
// test deprecation warning
513514
XCTAssertMatch(stderr, .contains("warning: '--generate-linuxmain' option is deprecated"))
514515
}
515516
}
516517

518+
func testGenerateLinuxMain() {
519+
#if os(macOS)
520+
fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in
521+
_ = try SwiftPMProduct.SwiftTest.execute(["--generate-linuxmain"], packagePath: path)
522+
523+
// Check LinuxMain
524+
let linuxMain = path.appending(components: "Tests", "LinuxMain.swift")
525+
XCTAssertEqual(try localFileSystem.readFileContents(linuxMain), """
526+
import XCTest
527+
528+
import SimpleTests
529+
530+
var tests = [XCTestCaseEntry]()
531+
tests += SimpleTests.__allTests()
532+
533+
XCTMain(tests)
534+
535+
""")
536+
537+
// Check test manifest
538+
let testManifest = path.appending(components: "Tests", "SimpleTests", "XCTestManifests.swift")
539+
XCTAssertEqual(try localFileSystem.readFileContents(testManifest), """
540+
#if !canImport(ObjectiveC)
541+
import XCTest
542+
543+
extension SimpleTests {
544+
// DO NOT MODIFY: This is autogenerated, use:
545+
// `swift test --generate-linuxmain`
546+
// to regenerate.
547+
static let __allTests__SimpleTests = [
548+
("test_Example2", test_Example2),
549+
("testExample1", testExample1),
550+
]
551+
}
552+
553+
public func __allTests() -> [XCTestCaseEntry] {
554+
return [
555+
testCase(SimpleTests.__allTests__SimpleTests),
556+
]
557+
}
558+
#endif
559+
560+
""")
561+
}
562+
#endif
563+
}
564+
517565
func testErrorMessageWhenTestLinksExecutable() {
518566
fixture(name: "Miscellaneous/ExeTest") { prefix in
519567
do {

0 commit comments

Comments
 (0)