blob: dc4101cfdb0721fdc96fc5df3a5c7b1e43ee21d8 [file] [log] [blame]
/*
This source file is part of the Swift.org open source project
Copyright (c) 2014 - 2017 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 XCTest
import Basic
import PackageDescription4
import PackageDescription
import PackageModel
import Utility
@testable import PackageLoading
/// Tests for the handling of source layout conventions.
class PackageBuilderTests: XCTestCase {
func testDotFilesAreIgnored() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/.Bar.swift",
"/Foo.swift")
let name = "DotFilesAreIgnored"
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .library)
moduleResult.checkSources(root: "/", paths: "Foo.swift")
}
result.checkProduct(name) { _ in }
}
}
func testResolvesSingleSwiftLibraryModule() throws {
var fs = InMemoryFileSystem(emptyFiles:
"/Foo.swift")
let name = "SingleSwiftTarget"
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .library)
moduleResult.checkSources(root: "/", paths: "Foo.swift")
}
result.checkProduct(name) { _ in }
}
// Single swift target inside Sources.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo.swift",
"/Sources/Bar.swift")
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .library)
moduleResult.checkSources(root: "/Sources", paths: "Foo.swift", "Bar.swift")
}
result.checkProduct(name) { _ in }
}
// Single swift target inside its own directory.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/lib/Foo.swift",
"/Sources/lib/Bar.swift")
PackageBuilderTester(name, in: fs) { result in
result.checkModule("lib") { moduleResult in
moduleResult.check(c99name: "lib", type: .library)
moduleResult.checkSources(root: "/Sources/lib", paths: "Foo.swift", "Bar.swift")
}
result.checkProduct(name) { _ in }
}
}
func testResolvesSystemModulePackage() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/module.modulemap")
let name = "SystemModulePackage"
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .systemModule)
moduleResult.checkSources(root: "/")
}
}
}
func testResolvesSingleClangLibraryModule() throws {
var fs = InMemoryFileSystem(emptyFiles:
"/Foo.h",
"/Foo.c")
let name = "SingleClangModule"
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .library)
moduleResult.checkSources(root: "/", paths: "Foo.c")
}
result.checkProduct(name) { _ in }
}
// Single clang target inside Sources.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo.h",
"/Sources/Foo.c")
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .library)
moduleResult.checkSources(root: "/Sources", paths: "Foo.c")
}
result.checkProduct(name) { _ in }
}
// Single clang target inside its own directory.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/lib/Foo.h",
"/Sources/lib/Foo.c")
PackageBuilderTester(name, in: fs) { result in
result.checkModule("lib") { moduleResult in
moduleResult.check(c99name: "lib", type: .library)
moduleResult.checkSources(root: "/Sources/lib", paths: "Foo.c")
}
result.checkProduct(name) { _ in }
}
}
func testSingleExecutableSwiftTarget() throws {
// Single swift executable target.
var fs = InMemoryFileSystem(emptyFiles:
"/main.swift",
"/Bar.swift")
let name = "SingleExecutable"
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .executable)
moduleResult.checkSources(root: "/", paths: "main.swift", "Bar.swift")
moduleResult.check(swiftVersion: 3)
}
result.checkProduct(name) { _ in }
}
// Single swift executable target inside Sources.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/main.swift")
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .executable)
moduleResult.checkSources(root: "/Sources", paths: "main.swift")
}
result.checkProduct(name) { _ in }
}
// Single swift executable target inside its own directory.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/exec/main.swift")
PackageBuilderTester(name, in: fs) { result in
result.checkModule("exec") { moduleResult in
moduleResult.check(c99name: "exec", type: .executable)
moduleResult.checkSources(root: "/Sources/exec", paths: "main.swift")
}
result.checkProduct("exec") { _ in }
}
}
func testCompatibleSwiftVersions() throws {
typealias Package = PackageDescription.Package
// Single swift executable target.
let fs = InMemoryFileSystem(emptyFiles:
"/foo/main.swift"
)
var package = Package(name: "pkg", swiftLanguageVersions: [3, 4])
PackageBuilderTester(package, in: fs) { result in
result.checkModule("foo") { moduleResult in
moduleResult.check(swiftVersion: 4)
}
result.checkProduct("foo") { _ in }
}
package = Package(name: "pkg", swiftLanguageVersions: [3])
PackageBuilderTester(package, in: fs) { result in
result.checkModule("foo") { moduleResult in
moduleResult.check(swiftVersion: 3)
}
result.checkProduct("foo") { _ in }
}
package = Package(name: "pkg", swiftLanguageVersions: [4])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("package 'pkg' must support Swift 3 because its minimum tools version is 3")
}
package = Package(name: "pkg")
PackageBuilderTester(package, in: fs) { result in
result.checkModule("foo") { moduleResult in
moduleResult.check(swiftVersion: 3)
}
result.checkProduct("foo") { _ in }
}
}
func testSingleExecutableClangModule() throws {
// Single swift executable target.
var fs = InMemoryFileSystem(emptyFiles:
"/main.c",
"/Bar.c")
let name = "SingleExecutable"
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .executable)
moduleResult.checkSources(root: "/", paths: "main.c", "Bar.c")
}
result.checkProduct(name) { _ in }
}
// Single swift executable target inside Sources.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/main.cpp")
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .executable)
moduleResult.checkSources(root: "/Sources", paths: "main.cpp")
}
result.checkProduct(name) { _ in }
}
// Single swift executable target inside its own directory.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/c/main.c")
PackageBuilderTester(name, in: fs) { result in
result.checkModule("c") { moduleResult in
moduleResult.check(c99name: "c", type: .executable)
moduleResult.checkSources(root: "/Sources/c", paths: "main.c")
}
result.checkProduct("c") { _ in }
}
}
func testDotSwiftSuffixDirectory() throws {
var fs = InMemoryFileSystem(emptyFiles:
"/hello.swift/dummy",
"/main.swift",
"/Bar.swift")
let name = "pkg"
// FIXME: This fails currently, it is a bug.
#if false
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .executable)
moduleResult.checkSources(root: "/", paths: "main.swift", "Bar.swift")
}
}
#endif
fs = InMemoryFileSystem(emptyFiles:
"/hello.swift/dummy",
"/Sources/main.swift",
"/Sources/Bar.swift")
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .executable)
moduleResult.checkSources(root: "/Sources", paths: "main.swift", "Bar.swift")
}
result.checkProduct(name) { _ in }
}
fs = InMemoryFileSystem(emptyFiles:
"/Sources/exe/hello.swift/dummy",
"/Sources/exe/main.swift",
"/Sources/exe/Bar.swift")
PackageBuilderTester(name, in: fs) { result in
result.checkModule("exe") { moduleResult in
moduleResult.check(c99name: "exe", type: .executable)
moduleResult.checkSources(root: "/Sources/exe", paths: "main.swift", "Bar.swift")
}
result.checkProduct("exe") { _ in }
}
}
func testMultipleSwiftTargets() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/A/main.swift",
"/Sources/A/foo.swift",
"/Sources/B/main.swift",
"/Sources/C/Foo.swift")
PackageBuilderTester("MultipleModules", in: fs) { result in
result.checkModule("A") { moduleResult in
moduleResult.check(c99name: "A", type: .executable)
moduleResult.checkSources(root: "/Sources/A", paths: "main.swift", "foo.swift")
}
result.checkModule("B") { moduleResult in
moduleResult.check(c99name: "B", type: .executable)
moduleResult.checkSources(root: "/Sources/B", paths: "main.swift")
}
result.checkModule("C") { moduleResult in
moduleResult.check(c99name: "C", type: .library)
moduleResult.checkSources(root: "/Sources/C", paths: "Foo.swift")
}
result.checkProduct("A") { _ in }
result.checkProduct("B") { _ in }
result.checkProduct("MultipleModules") { _ in }
}
}
func testMultipleClangModules() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/A/main.c",
"/Sources/A/foo.h",
"/Sources/A/foo.c",
"/Sources/B/include/foo.h",
"/Sources/B/foo.c",
"/Sources/B/bar.c",
"/Sources/C/main.cpp")
PackageBuilderTester("MultipleModules", in: fs) { result in
result.checkModule("A") { moduleResult in
moduleResult.check(c99name: "A", type: .executable)
moduleResult.checkSources(root: "/Sources/A", paths: "main.c", "foo.c")
}
result.checkModule("B") { moduleResult in
moduleResult.check(c99name: "B", type: .library)
moduleResult.checkSources(root: "/Sources/B", paths: "foo.c", "bar.c")
}
result.checkModule("C") { moduleResult in
moduleResult.check(c99name: "C", type: .executable)
moduleResult.checkSources(root: "/Sources/C", paths: "main.cpp")
}
result.checkProduct("A") { _ in }
result.checkProduct("C") { _ in }
result.checkProduct("MultipleModules") { _ in }
}
}
func testTestsLayoutsv3() throws {
// We expect auto dependency between Foo and FooTests.
//
// Single target layout.
for singleModuleSource in ["/", "/Sources/", "/Sources/Foo/"].lazy.map(AbsolutePath.init) {
let fs = InMemoryFileSystem(emptyFiles:
singleModuleSource.appending(component: "Foo.swift").asString,
"/Tests/FooTests/FooTests.swift",
"/Tests/FooTests/BarTests.swift",
"/Tests/BarTests/BazTests.swift")
PackageBuilderTester(.v3(.init(name: "Foo")), in: fs) { result in
result.checkModule("Foo") { moduleResult in
moduleResult.check(c99name: "Foo", type: .library)
moduleResult.checkSources(root: singleModuleSource.asString, paths: "Foo.swift")
}
result.checkModule("FooTests") { moduleResult in
moduleResult.check(c99name: "FooTests", type: .test)
moduleResult.checkSources(root: "/Tests/FooTests", paths: "FooTests.swift", "BarTests.swift")
moduleResult.check(dependencies: ["Foo"])
}
result.checkModule("BarTests") { moduleResult in
moduleResult.check(c99name: "BarTests", type: .test)
moduleResult.checkSources(root: "/Tests/BarTests", paths: "BazTests.swift")
moduleResult.check(dependencies: [])
}
result.checkProduct("FooPackageTests") { _ in }
result.checkProduct("Foo") { _ in }
}
}
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/A/main.swift", // Swift exec
"/Sources/B/Foo.swift", // Swift lib
"/Sources/D/Foo.c", // Clang lib
"/Sources/E/main.c", // Clang exec
"/Tests/ATests/Foo.swift",
"/Tests/BTests/Foo.swift",
"/Tests/DTests/Foo.swift",
"/Tests/ETests/Foo.swift")
PackageBuilderTester(.v3(.init(name: "Foo")), in: fs) { result in
result.checkModule("A") { moduleResult in
moduleResult.check(c99name: "A", type: .executable)
moduleResult.checkSources(root: "/Sources/A", paths: "main.swift")
}
result.checkModule("B") { moduleResult in
moduleResult.check(c99name: "B", type: .library)
moduleResult.checkSources(root: "/Sources/B", paths: "Foo.swift")
}
result.checkModule("D") { moduleResult in
moduleResult.check(c99name: "D", type: .library)
moduleResult.checkSources(root: "/Sources/D", paths: "Foo.c")
}
result.checkModule("E") { moduleResult in
moduleResult.check(c99name: "E", type: .executable)
moduleResult.checkSources(root: "/Sources/E", paths: "main.c")
}
result.checkModule("ATests") { moduleResult in
moduleResult.check(c99name: "ATests", type: .test)
moduleResult.checkSources(root: "/Tests/ATests", paths: "Foo.swift")
moduleResult.check(dependencies: ["A"])
}
result.checkModule("BTests") { moduleResult in
moduleResult.check(c99name: "BTests", type: .test)
moduleResult.checkSources(root: "/Tests/BTests", paths: "Foo.swift")
moduleResult.check(dependencies: ["B"])
}
result.checkModule("DTests") { moduleResult in
moduleResult.check(c99name: "DTests", type: .test)
moduleResult.checkSources(root: "/Tests/DTests", paths: "Foo.swift")
moduleResult.check(dependencies: ["D"])
}
result.checkModule("ETests") { moduleResult in
moduleResult.check(c99name: "ETests", type: .test)
moduleResult.checkSources(root: "/Tests/ETests", paths: "Foo.swift")
moduleResult.check(dependencies: ["E"])
}
result.checkProduct("E") { _ in }
result.checkProduct("FooPackageTests") { _ in }
result.checkProduct("Foo") { _ in }
result.checkProduct("A") { _ in }
}
}
func testNoSources() throws {
PackageBuilderTester("NoSources", in: InMemoryFileSystem()) { result in
result.checkDiagnostic("target 'NoSources' in package 'NoSources' contains no valid source files")
}
}
func testMixedSources() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/main.swift",
"/Sources/main.c")
PackageBuilderTester("MixedSources", in: fs) { result in
result.checkDiagnostic("target at '/Sources' contains mixed language source files; feature not supported")
}
}
func testTwoModulesMixedLanguage() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/ModuleA/main.swift",
"/Sources/ModuleB/main.c",
"/Sources/ModuleB/foo.c")
PackageBuilderTester("MixedLanguage", in: fs) { result in
result.checkModule("ModuleA") { moduleResult in
moduleResult.check(c99name: "ModuleA", type: .executable)
moduleResult.checkSources(root: "/Sources/ModuleA", paths: "main.swift")
}
result.checkModule("ModuleB") { moduleResult in
moduleResult.check(c99name: "ModuleB", type: .executable)
moduleResult.checkSources(root: "/Sources/ModuleB", paths: "main.c", "foo.c")
}
result.checkProduct("ModuleA") { _ in }
result.checkProduct("ModuleB") { _ in }
}
}
func testCInTests() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/main.swift",
"/Tests/MyPackageTests/abc.c")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkModule("MyPackage") { moduleResult in
moduleResult.check(type: .executable)
moduleResult.checkSources(root: "/Sources", paths: "main.swift")
}
result.checkModule("MyPackageTests") { moduleResult in
moduleResult.check(type: .test)
moduleResult.checkSources(root: "/Tests/MyPackageTests", paths: "abc.c")
}
result.checkProduct("MyPackage") { _ in }
#if os(Linux)
result.checkDiagnostic("ignoring target 'MyPackageTests' in package 'MyPackage'; C language in tests is not yet supported")
#elseif os(macOS)
result.checkProduct("MyPackagePackageTests") { _ in }
#endif
}
}
func testValidSources() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/main.swift",
"/noExtension",
"/Package.swift",
"/.git/anchor",
"/.xcodeproj/anchor",
"/.playground/anchor",
"/Package.swift",
"/Packages/MyPackage/main.c")
let name = "pkg"
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(type: .executable)
moduleResult.checkSources(root: "/", paths: "main.swift")
}
result.checkProduct("pkg") { _ in }
}
}
func testCustomTargetDependencies() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo/Foo.swift",
"/Sources/Bar/Bar.swift",
"/Sources/Baz/Baz.swift")
// Direct.
var package = PackageDescription.Package(name: "pkg", targets: [.init(name: "Foo", dependencies: ["Bar"])])
PackageBuilderTester(package, in: fs) { result in
result.checkModule("Foo") { moduleResult in
moduleResult.check(c99name: "Foo", type: .library)
moduleResult.checkSources(root: "/Sources/Foo", paths: "Foo.swift")
moduleResult.check(dependencies: ["Bar"])
}
for target in ["Bar", "Baz"] {
result.checkModule(target) { moduleResult in
moduleResult.check(c99name: target, type: .library)
moduleResult.checkSources(root: "/Sources/\(target)", paths: "\(target).swift")
}
}
result.checkProduct("pkg") { _ in }
}
// Transitive.
package = PackageDescription.Package(
name: "pkg",
targets: [
.init(name: "Foo", dependencies: ["Bar"]),
.init(name: "Bar", dependencies: ["Baz"])
]
)
PackageBuilderTester(package, in: fs) { result in
result.checkModule("Foo") { moduleResult in
moduleResult.check(c99name: "Foo", type: .library)
moduleResult.checkSources(root: "/Sources/Foo", paths: "Foo.swift")
moduleResult.check(dependencies: ["Bar"])
}
result.checkModule("Bar") { moduleResult in
moduleResult.check(c99name: "Bar", type: .library)
moduleResult.checkSources(root: "/Sources/Bar", paths: "Bar.swift")
moduleResult.check(dependencies: ["Baz"])
}
result.checkModule("Baz") { moduleResult in
moduleResult.check(c99name: "Baz", type: .library)
moduleResult.checkSources(root: "/Sources/Baz", paths: "Baz.swift")
}
result.checkProduct("pkg") { _ in }
}
}
func testTargetDependencies2() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo/Foo.swift",
"/Sources/Bar/Bar.swift",
"/Sources/Baz/Baz.swift")
// We create a manifest which uses byName target dependencies.
let package = PackageDescription.Package(
name: "pkg",
targets: [
.init(
name: "Foo",
dependencies: ["Bar", "Baz", "Bam"]),
])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("could not find target(s): Bam; use the 'path' property in the Swift 4 manifest to set a custom target path")
}
}
func testTestTargetDependencies() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo/source.swift",
"/Sources/Bar/source.swift",
"/Tests/FooTests/source.swift"
)
let package = PackageDescription.Package(name: "pkg", targets: [.init(name: "FooTests", dependencies: ["Bar"])])
PackageBuilderTester(package, in: fs) { result in
result.checkModule("Foo") { moduleResult in
moduleResult.check(c99name: "Foo", type: .library)
moduleResult.checkSources(root: "/Sources/Foo", paths: "source.swift")
}
result.checkModule("Bar") { moduleResult in
moduleResult.check(c99name: "Bar", type: .library)
moduleResult.checkSources(root: "/Sources/Bar", paths: "source.swift")
}
result.checkModule("FooTests") { moduleResult in
moduleResult.check(c99name: "FooTests", type: .test)
moduleResult.checkSources(root: "/Tests/FooTests", paths: "source.swift")
moduleResult.check(dependencies: ["Bar"])
}
result.checkProduct("pkgPackageTests") { _ in }
result.checkProduct("pkg") { _ in }
}
}
func testInvalidTestTargets() throws {
// Test target in Sources/
var fs = InMemoryFileSystem(emptyFiles:
"/Sources/FooTests/source.swift")
PackageBuilderTester("TestsInSources", in: fs) { result in
result.checkDiagnostic("invalid target name at 'Sources/FooTests'; name of non-test targets cannot end in 'Tests'")
}
// Normal target in Tests/
fs = InMemoryFileSystem(emptyFiles:
"/Sources/main.swift",
"/Tests/Foo/source.swift")
PackageBuilderTester("TestsInSources", in: fs) { result in
result.checkDiagnostic("invalid target name at 'Tests/Foo'; name of test targets must end in 'Tests'")
}
}
func testLooseSourceFileInTestsDir() throws {
// Loose source file in Tests/
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/main.swift",
"/Tests/source.swift")
PackageBuilderTester("LooseSourceFileInTestsDir", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; found loose source files: /Tests/source.swift")
}
}
func testManifestTargetDeclErrors() throws {
// Reference a target which doesn't exist.
var fs = InMemoryFileSystem(emptyFiles:
"/Foo.swift")
var package = PackageDescription.Package(name: "pkg", targets: [.init(name: "Random")])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("could not find target(s): Random; use the 'path' property in the Swift 4 manifest to set a custom target path")
}
// Reference an invalid dependency.
package = PackageDescription.Package(name: "pkg", targets: [.init(name: "pkg", dependencies: ["Foo"])])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("could not find target(s): Foo; use the 'path' property in the Swift 4 manifest to set a custom target path")
}
// Reference self in dependencies.
package = PackageDescription.Package(name: "pkg", targets: [.init(name: "pkg", dependencies: ["pkg"])])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("cyclic dependency declaration found: pkg -> pkg")
}
fs = InMemoryFileSystem(emptyFiles:
"/Sources/pkg1/Foo.swift",
"/Sources/pkg2/Foo.swift",
"/Sources/pkg3/Foo.swift"
)
// Cyclic dependency.
package = PackageDescription.Package(name: "pkg", targets: [
.init(name: "pkg1", dependencies: ["pkg2"]),
.init(name: "pkg2", dependencies: ["pkg3"]),
.init(name: "pkg3", dependencies: ["pkg1"]),
])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("cyclic dependency declaration found: pkg1 -> pkg2 -> pkg3 -> pkg1")
}
package = PackageDescription.Package(name: "pkg", targets: [
.init(name: "pkg1", dependencies: ["pkg2"]),
.init(name: "pkg2", dependencies: ["pkg3"]),
.init(name: "pkg3", dependencies: ["pkg2"]),
])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("cyclic dependency declaration found: pkg1 -> pkg2 -> pkg3 -> pkg2")
}
// Executable as dependency.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/exec/main.swift",
"/Sources/lib/lib.swift")
package = PackageDescription.Package(name: "pkg", targets: [.init(name: "lib", dependencies: ["exec"])])
PackageBuilderTester(package, in: fs) { result in
result.checkModule("exec") { moduleResult in
moduleResult.check(c99name: "exec", type: .executable)
moduleResult.checkSources(root: "/Sources/exec", paths: "main.swift")
}
result.checkModule("lib") { moduleResult in
moduleResult.check(c99name: "lib", type: .library)
moduleResult.checkSources(root: "/Sources/lib", paths: "lib.swift")
}
result.checkProduct("exec") { _ in }
result.checkProduct("pkg") { _ in }
}
// Reference a target which doesn't have sources.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/pkg1/Foo.swift",
"/Sources/pkg2/readme.txt")
package = PackageDescription.Package(name: "pkg", targets: [.init(name: "pkg1", dependencies: ["pkg2"])])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("target 'pkg2' in package 'pkg' contains no valid source files")
result.checkModule("pkg1") { moduleResult in
moduleResult.check(c99name: "pkg1", type: .library)
moduleResult.checkSources(root: "/Sources/pkg1", paths: "Foo.swift")
}
result.checkProduct("pkg") { _ in }
}
}
func testTestsProduct() throws {
// Make sure product name and test target name are different in single target package.
var fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo.swift",
"/Tests/FooTests/Bar.swift")
PackageBuilderTester("Foo", in: fs) { result in
result.checkModule("Foo") { moduleResult in
moduleResult.check(c99name: "Foo", type: .library)
moduleResult.checkSources(root: "/Sources", paths: "Foo.swift")
}
result.checkModule("FooTests") { moduleResult in
moduleResult.check(c99name: "FooTests", type: .test)
moduleResult.checkSources(root: "/Tests/FooTests", paths: "Bar.swift")
}
result.checkProduct("FooPackageTests") { productResult in
productResult.check(type: .test, targets: ["FooTests"])
}
result.checkProduct("Foo") { _ in }
}
// Multi target tests package.
fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo/Foo.swift",
"/Sources/Bar/Bar.swift",
"/Tests/FooTests/Foo.swift",
"/Tests/BarTests/Bar.swift")
PackageBuilderTester("Foo", in: fs) { result in
result.checkModule("Foo") { moduleResult in
moduleResult.check(c99name: "Foo", type: .library)
moduleResult.checkSources(root: "/Sources/Foo", paths: "Foo.swift")
}
result.checkModule("Bar") { moduleResult in
moduleResult.check(c99name: "Bar", type: .library)
moduleResult.checkSources(root: "/Sources/Bar", paths: "Bar.swift")
}
result.checkModule("FooTests") { moduleResult in
moduleResult.check(c99name: "FooTests", type: .test)
moduleResult.checkSources(root: "/Tests/FooTests", paths: "Foo.swift")
}
result.checkModule("BarTests") { moduleResult in
moduleResult.check(c99name: "BarTests", type: .test)
moduleResult.checkSources(root: "/Tests/BarTests", paths: "Bar.swift")
}
result.checkProduct("FooPackageTests") { productResult in
productResult.check(type: .test, targets: ["BarTests", "FooTests"])
}
result.checkProduct("Foo") { _ in }
}
}
func testVersionSpecificManifests() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Package.swift",
"/Package@swift-999.swift",
"/Sources/Package.swift",
"/Sources/Package@swift-1.swift")
let name = "Foo"
PackageBuilderTester(name, in: fs) { result in
result.checkModule(name) { moduleResult in
moduleResult.check(c99name: name, type: .library)
moduleResult.checkSources(root: "/Sources", paths: "Package.swift", "Package@swift-1.swift")
}
result.checkProduct(name) { _ in }
}
}
func testMultipleRoots() throws {
var fs = InMemoryFileSystem(emptyFiles:
"/Foo.swift",
"/main.swift",
"/src/FooBarLib/FooBar.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; found loose source files: /Foo.swift, /main.swift")
}
fs = InMemoryFileSystem(emptyFiles:
"/Sources/BarExec/main.swift",
"/Sources/BarExec/Bar.swift",
"/src/FooBarLib/FooBar.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; multiple source roots found: /Sources, /src")
}
}
func testInvalidLayout1() throws {
/*
Package
├── main.swift <-- invalid
└── Sources
└── File2.swift
*/
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/Files2.swift",
"/main.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; found loose source files: /main.swift")
}
}
func testInvalidLayout2() throws {
/*
Package
├── main.swift <-- invalid
└── Bar
└── Sources
└── File2.swift
*/
// FIXME: We should allow this by not making targets at root and only inside Sources/.
let fs = InMemoryFileSystem(emptyFiles:
"/Bar/Sources/Files2.swift",
"/main.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; found loose source files: /main.swift")
}
}
func testInvalidLayout3() throws {
/*
Package
└── Sources
├── main.swift <-- Invalid
└── Bar
└── File2.swift
*/
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/main.swift",
"/Sources/Bar/File2.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; found loose source files: /Sources/main.swift")
}
}
func testInvalidLayout4() throws {
/*
Package
├── main.swift <-- Invalid
└── Sources
└── Bar
└── File2.swift
*/
let fs = InMemoryFileSystem(emptyFiles:
"/main.swift",
"/Sources/Bar/File2.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; found loose source files: /main.swift")
}
}
func testInvalidLayout5() throws {
/*
Package
├── File1.swift
└── Foo
└── Foo.swift <-- Invalid
*/
// for the simplest layout it is invalid to have any
// subdirectories. It is the compromise you make.
// the reason for this is mostly performance in
// determineTargets() but also we are saying: this
// layout is only for *very* simple projects.
let fs = InMemoryFileSystem(emptyFiles:
"/File1.swift",
"/Foo/Foo.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; found loose source files: /File1.swift")
}
}
func testInvalidLayout6() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/file.swift",
"/Sources/foo/foo.swift",
"/Sources/bar/bar.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; found loose source files: /Sources/file.swift")
}
}
func testModuleMapLayout() throws {
var fs = InMemoryFileSystem(emptyFiles:
"/Sources/clib/include/module.modulemap",
"/Sources/clib/include/clib.h",
"/Sources/clib/clib.c")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkModule("clib") { moduleResult in
moduleResult.check(c99name: "clib", type: .library)
moduleResult.checkSources(root: "/Sources/clib", paths: "clib.c")
}
result.checkProduct("MyPackage") { _ in }
}
fs = InMemoryFileSystem(emptyFiles:
"/Sources/module.modulemap",
"/Sources/foo.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; modulemap '/Sources/module.modulemap' should be inside the 'include' directory")
}
fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo/module.modulemap",
"/Sources/Foo/foo.swift",
"/Sources/Bar/bar.swift")
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("package has unsupported layout; modulemap '/Sources/Foo/module.modulemap' should be inside the 'include' directory")
}
}
func testNoSourcesInModule() throws {
var fs = InMemoryFileSystem()
try fs.createDirectory(AbsolutePath("/Sources/Module"), recursive: true)
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("target 'Module' in package 'MyPackage' contains no valid source files")
}
fs = InMemoryFileSystem(emptyFiles:
"/Sources/Module/foo.swift")
try fs.createDirectory(AbsolutePath("/Tests/ModuleTests"), recursive: true)
PackageBuilderTester("MyPackage", in: fs) { result in
result.checkDiagnostic("target 'ModuleTests' in package 'MyPackage' contains no valid source files")
result.checkModule("Module")
result.checkProduct("MyPackage") { _ in }
}
}
func testDuplicateTargets() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/A/main.swift",
"/Sources/A/foo.swift",
"/Sources/B/bar.swift",
"/Sources/C/baz.swift"
)
let package = PackageDescription.Package(name: "A",
targets: [Target(name: "A", dependencies: []),
Target(name: "B", dependencies: []),
Target(name: "A", dependencies: []),
Target(name: "B", dependencies: []),])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("duplicate targets found: A, B")
}
}
func testExcludes() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/Sources/A/main.swift",
"/Sources/A/foo.swift", // File will be excluded.
"/Sources/B/main.swift" // Dir will be excluded.
)
// Excluding everything.
var package = PackageDescription.Package(name: "pkg", exclude: ["."])
PackageBuilderTester(package, in: fs) { _ in }
// Test excluding a file and a directory.
package = PackageDescription.Package(name: "pkg", exclude: ["Sources/A/foo.swift", "Sources/B"])
PackageBuilderTester(package, in: fs) { result in
result.checkModule("A") { moduleResult in
moduleResult.check(type: .executable)
moduleResult.checkSources(root: "/Sources/A", paths: "main.swift")
}
result.checkProduct("A") { _ in }
}
}
func testInvalidManifestConfigForNonSystemModules() {
var fs = InMemoryFileSystem(emptyFiles:
"/Sources/main.swift"
)
var package = PackageDescription.Package(name: "pkg", pkgConfig: "foo")
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("configuration of package 'pkg' is invalid; the 'pkgConfig' property can only be used with a System Module Package")
}
fs = InMemoryFileSystem(emptyFiles:
"/Sources/Foo/main.c"
)
package = PackageDescription.Package(name: "pkg", providers: [.Brew("foo")])
PackageBuilderTester(package, in: fs) { result in
result.checkDiagnostic("configuration of package 'pkg' is invalid; the 'providers' property can only be used with a System Module Package")
}
}
static var allTests = [
("testCInTests", testCInTests),
("testCompatibleSwiftVersions", testCompatibleSwiftVersions),
("testCustomTargetDependencies", testCustomTargetDependencies),
("testDotFilesAreIgnored", testDotFilesAreIgnored),
("testDotSwiftSuffixDirectory", testDotSwiftSuffixDirectory),
("testExcludes", testExcludes),
("testInvalidLayout1", testInvalidLayout1),
("testInvalidLayout2", testInvalidLayout2),
("testInvalidLayout3", testInvalidLayout3),
("testInvalidLayout4", testInvalidLayout4),
("testInvalidLayout5", testInvalidLayout5),
("testInvalidLayout6", testInvalidLayout5),
("testInvalidManifestConfigForNonSystemModules", testInvalidManifestConfigForNonSystemModules),
("testInvalidTestTargets", testInvalidTestTargets),
("testLooseSourceFileInTestsDir", testLooseSourceFileInTestsDir),
("testManifestTargetDeclErrors", testManifestTargetDeclErrors),
("testMixedSources", testMixedSources),
("testModuleMapLayout", testModuleMapLayout),
("testMultipleClangModules", testMultipleClangModules),
("testMultipleRoots", testMultipleRoots),
("testMultipleSwiftTargets", testMultipleSwiftTargets),
("testNoSources", testNoSources),
("testNoSourcesInModule", testNoSourcesInModule),
("testResolvesSingleClangLibraryModule", testResolvesSingleClangLibraryModule),
("testResolvesSingleSwiftLibraryModule", testResolvesSingleSwiftLibraryModule),
("testResolvesSystemModulePackage", testResolvesSystemModulePackage),
("testSingleExecutableClangModule", testSingleExecutableClangModule),
("testSingleExecutableSwiftTarget", testSingleExecutableSwiftTarget),
("testTargetDependencies2", testTargetDependencies2),
("testTestTargetDependencies", testTestTargetDependencies),
("testTestsLayoutsv3", testTestsLayoutsv3),
("testTestsProduct", testTestsProduct),
("testTwoModulesMixedLanguage", testTwoModulesMixedLanguage),
("testValidSources", testValidSources),
("testVersionSpecificManifests", testVersionSpecificManifests),
("testDuplicateTargets", testDuplicateTargets),
]
}
extension PackageModel.Product: ObjectIdentifierProtocol {}
final class PackageBuilderTester {
private enum Result {
case package(PackageModel.Package)
case error(String)
}
/// Contains the result produced by PackageBuilder.
private let result: Result
/// Contains the diagnostics which have not been checked yet.
private var uncheckedDiagnostics = Set<String>()
/// Contains the targets which have not been checked yet.
private var uncheckedModules = Set<PackageModel.Target>()
/// Contains the products which have not been checked yet.
private var uncheckedProducts = Set<PackageModel.Product>()
@discardableResult
convenience init(
_ package: PackageDescription4.Package,
path: AbsolutePath = .root,
in fs: FileSystem,
file: StaticString = #file,
line: UInt = #line,
_ body: (PackageBuilderTester) -> Void
) {
self.init(.v4(package), path: path, in: fs, file: file, line: line, body)
}
@discardableResult
convenience init(
_ package: PackageDescription.Package,
path: AbsolutePath = .root,
in fs: FileSystem,
file: StaticString = #file,
line: UInt = #line,
_ body: (PackageBuilderTester) -> Void
) {
self.init(.v3(package), path: path, in: fs, file: file, line: line, body)
}
@discardableResult
convenience init(
_ name: String,
path: AbsolutePath = .root,
in fs: FileSystem,
file: StaticString = #file,
line: UInt = #line,
_ body: (PackageBuilderTester) -> Void
) {
self.init(PackageDescription.Package(name: name), path: path, in: fs, file: file, line: line, body)
}
@discardableResult
init(
_ package: Manifest.RawPackage,
path: AbsolutePath = .root,
shouldCreateMultipleTestProducts: Bool = false,
in fs: FileSystem,
file: StaticString = #file,
line: UInt = #line,
_ body: (PackageBuilderTester) -> Void
) {
let diagnostics = DiagnosticsEngine()
do {
let manifest = Manifest(path: path.appending(component: Manifest.filename), url: "", package: package, version: nil)
// FIXME: We should allow customizing root package boolean.
let builder = PackageBuilder(
manifest: manifest, path: path, fileSystem: fs, diagnostics: diagnostics,
isRootPackage: true, shouldCreateMultipleTestProducts: shouldCreateMultipleTestProducts)
let loadedPackage = try builder.construct()
result = .package(loadedPackage)
uncheckedModules = Set(loadedPackage.targets)
uncheckedProducts = Set(loadedPackage.products)
} catch {
let errorStr = String(describing: error)
result = .error(errorStr)
uncheckedDiagnostics.insert(errorStr)
}
uncheckedDiagnostics.formUnion(diagnostics.diagnostics.map({ $0.localizedDescription }))
body(self)
validateDiagnostics(file: file, line: line)
validateCheckedModules(file: file, line: line)
}
private func validateDiagnostics(file: StaticString, line: UInt) {
guard !uncheckedDiagnostics.isEmpty else { return }
XCTFail("Unchecked diagnostics: \(uncheckedDiagnostics)", file: file, line: line)
}
private func validateCheckedModules(file: StaticString, line: UInt) {
if !uncheckedModules.isEmpty {
XCTFail("Unchecked targets: \(uncheckedModules)", file: file, line: line)
}
if !uncheckedProducts.isEmpty {
XCTFail("Unchecked products: \(uncheckedProducts)", file: file, line: line)
}
}
func checkDiagnostic(_ str: String, file: StaticString = #file, line: UInt = #line) {
if uncheckedDiagnostics.contains(str) {
uncheckedDiagnostics.remove(str)
} else {
XCTFail("\(result) did not have error: \(str) or is already checked", file: file, line: line)
}
}
func checkPredefinedPaths(target: String, testTarget: String, file: StaticString = #file, line: UInt = #line) {
guard case .package(let package) = result else {
return XCTFail("Expected package did not load \(self)", file: file, line: line)
}
XCTAssertEqual(target, package.targetSearchPath.asString, file: file, line: line)
XCTAssertEqual(testTarget, package.testTargetSearchPath.asString, file: file, line: line)
}
func checkModule(_ name: String, file: StaticString = #file, line: UInt = #line, _ body: ((ModuleResult) -> Void)? = nil) {
guard case .package(let package) = result else {
return XCTFail("Expected package did not load \(self)", file: file, line: line)
}
guard let target = package.targets.first(where: {$0.name == name}) else {
return XCTFail("Module: \(name) not found", file: file, line: line)
}
uncheckedModules.remove(target)
body?(ModuleResult(target))
}
func checkProduct(_ name: String, file: StaticString = #file, line: UInt = #line, _ body: ((ProductResult) -> Void)? = nil) {
guard case .package(let package) = result else {
return XCTFail("Expected package did not load \(self)", file: file, line: line)
}
let foundProducts = package.products.filter{$0.name == name}
guard foundProducts.count == 1 else {
return XCTFail("Couldn't get the product: \(name). Found products \(foundProducts)", file: file, line: line)
}
uncheckedProducts.remove(foundProducts[0])
body?(ProductResult(foundProducts[0]))
}
final class ProductResult {
private let product: PackageModel.Product
init(_ product: PackageModel.Product) {
self.product = product
}
func check(type: PackageModel.ProductType, targets: [String], file: StaticString = #file, line: UInt = #line) {
XCTAssertEqual(product.type, type, file: file, line: line)
XCTAssertEqual(product.targets.map{$0.name}.sorted(), targets.sorted(), file: file, line: line)
}
func check(linuxMainPath: String, file: StaticString = #file, line: UInt = #line) {
XCTAssertEqual(product.linuxMain, AbsolutePath(linuxMainPath), file: file, line: line)
}
}
final class ModuleResult {
private let target: PackageModel.Target
fileprivate init(_ target: PackageModel.Target) {
self.target = target
}
func check(includeDir: String, file: StaticString = #file, line: UInt = #line) {
guard case let target as ClangTarget = target else {
return XCTFail("Include directory is being checked on a non clang target", file: file, line: line)
}
XCTAssertEqual(target.includeDir.asString, includeDir, file: file, line: line)
}
func check(c99name: String? = nil, type: PackageModel.Target.Kind? = nil, file: StaticString = #file, line: UInt = #line) {
if let c99name = c99name {
XCTAssertEqual(target.c99name, c99name, file: file, line: line)
}
if let type = type {
XCTAssertEqual(target.type, type, file: file, line: line)
}
}
func checkSources(root: String? = nil, sources paths: [String], file: StaticString = #file, line: UInt = #line) {
if let root = root {
XCTAssertEqual(target.sources.root, AbsolutePath(root), file: file, line: line)
}
let sources = Set(self.target.sources.relativePaths.map{$0.asString})
XCTAssertEqual(sources, Set(paths), "unexpected source files in \(target.name)", file: file, line: line)
}
func checkSources(root: String? = nil, paths: String..., file: StaticString = #file, line: UInt = #line) {
checkSources(root: root, sources: paths, file: file, line: line)
}
func check(dependencies depsToCheck: [String], file: StaticString = #file, line: UInt = #line) {
XCTAssertEqual(Set(depsToCheck), Set(target.dependencies.map{$0.name}), "unexpected dependencies in \(target.name)", file: file, line: line)
}
func check(productDeps depsToCheck: [(name: String, package: String?)], file: StaticString = #file, line: UInt = #line) {
guard depsToCheck.count == target.productDependencies.count else {
return XCTFail("Incorrect product dependencies", file: file, line: line)
}
for (idx, element) in depsToCheck.enumerated() {
let rhs = target.productDependencies[idx]
guard element.name == rhs.name && element.package == rhs.package else {
return XCTFail("Incorrect product dependencies", file: file, line: line)
}
}
}
func check(swiftVersion: Int, file: StaticString = #file, line: UInt = #line) {
guard case let swiftTarget as SwiftTarget = target else {
return XCTFail("\(target) is not a swift target", file: file, line: line)
}
XCTAssertEqual(swiftVersion, swiftTarget.swiftVersion, file: file, line: line)
}
}
}