Skip to content

Commit fb5f684

Browse files
committed
Implement SE-0129 to update the test naming conventions. Instead of
automatically and silently appending a `TestSuite` suffix, tests are now expected to have a `Tests` suffix in the module name. Right now we make sure there are no tests under `Sources` and no non-tests under `Tests`, but we expect to lift that restriction in the future to allow test support libraries etc. This commit also updates SwiftPM's own tests to follow the new naming conventions.
1 parent 96aa6fd commit fb5f684

File tree

106 files changed

+261
-82
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+261
-82
lines changed

Fixtures/Miscellaneous/PackageWithInvalidTargets/Tests/LinuxMain.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import XCTest
2-
@testable import MyAppTestSuite
2+
@testable import MyAppTests
33

44
XCTMain([
55
testCase(MyAppTests.allTests),

Fixtures/Miscellaneous/SwiftPMXCTestHelper/Tests/ObjcTest/Objc.m renamed to Fixtures/Miscellaneous/SwiftPMXCTestHelper/Tests/ObjCTests/ObjCTests.m

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
@import XCTest;
22

3-
@interface Objc: XCTestCase
3+
@interface ObjCTests: XCTestCase
44
@end
55

6-
@implementation Objc
6+
@implementation ObjCTests
77

88
- (void)testThisThing {
99
}

Sources/Commands/init.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ final class InitPackage {
114114
guard exists(sources) == false else {
115115
return
116116
}
117-
print("Creating Sources/")
117+
print("Creating \(sources.relative(to: rootd).asString)/")
118118
try makeDirectories(sources)
119119

120120
let sourceFileName = (mode == .executable) ? "main.swift" : "\(typeName).swift"
@@ -160,7 +160,7 @@ final class InitPackage {
160160
guard exists(tests) == false else {
161161
return
162162
}
163-
print("Creating Tests/")
163+
print("Creating \(tests.relative(to: rootd).asString)/")
164164
try makeDirectories(tests)
165165

166166
// Only libraries are testable for now.
@@ -173,16 +173,16 @@ final class InitPackage {
173173
private func writeLinuxMain(testsPath: AbsolutePath) throws {
174174
try writePackageFile(testsPath.appending(component: "LinuxMain.swift")) { stream in
175175
stream <<< "import XCTest\n"
176-
stream <<< "@testable import \(moduleName)TestSuite\n\n"
176+
stream <<< "@testable import \(moduleName)Tests\n\n"
177177
stream <<< "XCTMain([\n"
178178
stream <<< " testCase(\(typeName)Tests.allTests),\n"
179179
stream <<< "])\n"
180180
}
181181
}
182182

183183
private func writeTestFileStubs(testsPath: AbsolutePath) throws {
184-
let testModule = testsPath.appending(RelativePath(pkgname))
185-
print("Creating Tests/\(pkgname)/")
184+
let testModule = testsPath.appending(RelativePath(pkgname + Module.testModuleNameSuffix))
185+
print("Creating \(testModule.relative(to: rootd).asString)/")
186186
try makeDirectories(testModule)
187187

188188
try writePackageFile(testModule.appending(RelativePath("\(moduleName)Tests.swift"))) { stream in

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,25 +78,45 @@ extension ModuleError.InvalidLayoutType: FixableError {
7878
}
7979

8080
extension Module {
81-
/// An error in the organization of an individual module.
81+
82+
/// An error in the organization or configuration of an individual module.
8283
enum Error: Swift.Error {
84+
85+
/// The module's name is invalid.
86+
case invalidName(path: String, name: String, problem: ModuleNameProblem)
87+
enum ModuleNameProblem {
88+
/// Empty module name.
89+
case emptyName
90+
/// Test module doesn't have a "Tests" suffix.
91+
case noTestSuffix
92+
/// Non-test module does have a "Tests" suffix.
93+
case hasTestSuffix
94+
}
95+
96+
/// The module contains no source code at all.
8397
case noSources(String)
98+
99+
/// The module contains an invalid mix of languages (e.g. both Swift and C).
84100
case mixedSources(String)
85101
}
86102
}
87103

88104
extension Module.Error: FixableError {
89105
var error: String {
90106
switch self {
91-
case .noSources(let path):
107+
case .invalidName(let path, let name, let problem):
108+
return "the module at \(path) has an invalid name ('\(name)'): \(problem.error)"
109+
case .noSources(let path):
92110
return "the module at \(path) does not contain any source files"
93-
case .mixedSources(let path):
111+
case .mixedSources(let path):
94112
return "the module at \(path) contains mixed language source files"
95113
}
96114
}
97115

98116
var fix: String? {
99117
switch self {
118+
case .invalidName(_, _, let problem):
119+
return "rename the module (\(problem.fix))"
100120
case .noSources(_):
101121
return "either remove the module folder, or add a source file to the module"
102122
case .mixedSources(_):
@@ -105,6 +125,30 @@ extension Module.Error: FixableError {
105125
}
106126
}
107127

128+
extension Module.Error.ModuleNameProblem : FixableError {
129+
var error: String {
130+
switch self {
131+
case .emptyName:
132+
return "the module name is empty"
133+
case .noTestSuffix:
134+
return "the name of a test module has no ‘Tests’ suffix"
135+
case .hasTestSuffix:
136+
return "the name of a non-test module has a ‘Tests’ suffix"
137+
}
138+
}
139+
var fix: String? {
140+
switch self {
141+
case .emptyName:
142+
return "give the module a name"
143+
case .noTestSuffix:
144+
return "add a ‘Tests’ suffix"
145+
case .hasTestSuffix:
146+
return "remove the ‘Tests’ suffix"
147+
}
148+
}
149+
}
150+
151+
108152
extension Product {
109153
/// An error in a product definition.
110154
enum Error: Swift.Error {
@@ -341,8 +385,26 @@ public struct PackageBuilder {
341385
return modules
342386
}
343387

388+
/// Private function that checks whether a module name is valid. This method doesn't return anything, but rather, if there's a problem, it throws an error describing what the problem is.
389+
// FIXME: We will eventually be loosening this restriction to allow test-only libraries etc
390+
private func validateModuleName(_ path: AbsolutePath, _ name: String, isTest: Bool) throws {
391+
if name.isEmpty {
392+
throw Module.Error.invalidName(path: path.asString, name: name, problem: .emptyName)
393+
}
394+
if name.hasSuffix(Module.testModuleNameSuffix) && !isTest {
395+
throw Module.Error.invalidName(path: path.asString, name: name, problem: .hasTestSuffix)
396+
}
397+
if !name.hasSuffix(Module.testModuleNameSuffix) && isTest {
398+
throw Module.Error.invalidName(path: path.asString, name: name, problem: .noTestSuffix)
399+
}
400+
}
401+
344402
/// Private function that constructs a single Module object for the moduel at `path`, having the name `name`. If `isTest` is true, the module is constructed as a test module; if false, it is a regular module.
345403
private func createModule(_ path: AbsolutePath, name: String, isTest: Bool) throws -> Module {
404+
405+
// Validate the module name. This function will throw an error if it detects a problem.
406+
try validateModuleName(path, name, isTest: isTest)
407+
346408
// Find all the files under the module path.
347409
let walked = try walk(path, fileSystem: fileSystem, recursing: shouldConsiderDirectory).map{ $0 }
348410

Sources/PackageModel/Module.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public class Module: ModuleProtocol {
4949
// FIXME: This should probably be rolled into the type.
5050
public let isTest: Bool
5151

52-
private let testModuleNameSuffix = "TestSuite"
52+
/// Suffix that's expected for test modules.
53+
public static let testModuleNameSuffix = "Tests"
5354

5455
/// The "type" of module.
5556
public let type: ModuleType
@@ -58,8 +59,7 @@ public class Module: ModuleProtocol {
5859
public let sources: Sources
5960

6061
public init(name: String, type: ModuleType, sources: Sources, isTest: Bool = false) throws {
61-
// Append TestSuite to name if its a test module.
62-
self.name = name + (isTest ? testModuleNameSuffix : "")
62+
self.name = name
6363
self.type = type
6464
self.sources = sources
6565
self.dependencies = []
@@ -79,8 +79,8 @@ public class Module: ModuleProtocol {
7979
guard isTest else {
8080
fatalError("\(self.dynamicType) should be a test module to access basename.")
8181
}
82-
precondition(name.hasSuffix(testModuleNameSuffix))
83-
return name[name.startIndex..<name.index(name.endIndex, offsetBy: -testModuleNameSuffix.characters.count)]
82+
precondition(name.hasSuffix(Module.testModuleNameSuffix))
83+
return name[name.startIndex..<name.index(name.endIndex, offsetBy: -Module.testModuleNameSuffix.characters.count)]
8484
}
8585
}
8686

File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)