Can I test a package without building every single target?

Objective

I am looking to run tests defined in a test target by exclusively building the test target and its declared dependencies. This means skipping targets not explicitly defined as dependencies in that test target's definition.

Context

Given the following Package.swift:

let package = Package(
    name: "MyPackage",
    products: [
        // We define a single product that depends on a single target.
        .library(name: "MyPackage", targets: ["MyPackage"]),
    ],
    dependencies: [
        // This dependency is not supported on the target platform.
        .package(url: "dependency", from: "1.0.0"),
    ],
    targets: [
        // I am looking to build only these two:
        .target(name: "MyPackage", dependencies: []),
        .testTarget(name: "MyPackageTests", dependencies: ["MyPackage"]),

        // This executable target is the only one with the unsupported dependency.
        .executableTarget(name: "Unsupported", dependencies: [
            "MyPackage", 
            .product(name: "Dependency", package: "dependency")
        ]),
    ]
)

I want to run the tests in the MyPackageTests target without building the Unsupported target, since the dependency package won't build on my platform.

Things I've Tried

$ swift build --target MyPackageTests
$ swift test --skip-build

[...]
Failure: No test bundle found at path `/Users/user/MyPackage/.build/arm64-apple-macosx/debug/MyPackagePackageTests.xctest`
[...] (full error available on request)
$ swift build --target MyPackageTests
$ swift test --test-product MyPackageTests
error: no tests found; create a target in the 'Tests' directory
$ swift build --target MyPackageTests
$ swift test --test-product MyPackage
Building for debugging...
warning: '--product' cannot be used with the automatic product 'MyPackage'; building the default target instead
(Builds the whole project including Unsupported, and fails)

You can run a subset of the tests with --filter, but it will only filter the test execution, not the build.

Until targets can be restricted by platform (a sorely needed feature), the standard answer to that is to make the dependency conditional, and surround each file with #if to make the target effectively empty. That way it does build successfully even though it is building essentially nothing.

2 Likes

I ran into this issue once again and decided to try fix things within SwiftPM. While browsing the codebase I discovered a hidden --test-product command line option and figured out how to use it. The value of --test-product is used when constructing the build subset (which in this case is either a specific product, or every single target in the package). After a little bit of digging I found that the name of the generated test product for a package is the value of the package's name field with PackageTests added on the end. Here's an example invocation for my package called swift-cross-ui.

swift test --test-product swift-cross-uiPackageTests

swift test on its own fails due to trying to build the WinUIBackend target on my Mac, and with --test-product ... it succeeds.

4 Likes