Pitch: Test Build Introspection

As far as I know, there is currently no way for Swift code to detect whether or not it is building for or running in a test context, but there are cases in which this is useful information to have:

  • You may want to define test-specific code in an executable or package that is compiled out of your build products.
  • You may want to define code that produces different results depending on if it's being run in tests or not. For example, you may want to ensure a dependency never hits a live server during a test run.

While there are ways of approximating such a check, they are a bit fraught:

  • We can use DEBUG and RELEASE to prevent test code from compiling into release products, but this requires us to regularly build for release to ensure we aren’t accessing debug-only code from release code. Worse, using debug vs. release for this purpose prohibits us from running tests in release mode.
  • We can use the process info (arguments, environment) to check for XCTest-specific configuration to determine if XCTest is running, but these environment variables are private details that differ depending on how XCTest is being run (swift test vs. xcodebuild test), could change at any time, and in some cases aren’t even set (SwiftWasm’s process info is wiped clean).

If we look to Rust, it provides cfg(test), which can be used to annotate code as being test-only and queried to determine if code is running in a test configuration.

I’d like to propose Swift introduces a similar mechanism that is enabled in swift test runs, though I’d like to gather a bit of feedback on what such a syntax should look like.

20 Likes

I think you might be able to get most of what you want using the forthcoming macro system. Idk if the compiler gets the test commandline arg/has some other way of knowing, but presumably it would be pretty easy to expose that to the macro system if it isn't already.

The only issue that I see with this as a solution, is that some dependencies might be used only for testing, and idk if SPM has support for that.

As far as the syntax, imo macros are a great solution for this even if they're not the mechanism that's eventually used to implement it.

There could be different stories between runtime check and compile-time config, right?
I guess the former may adopt if ... {} while the latter may adopt #if ... #endif.
I mean compile-time config will require rebuilding when swift test is executed even just after swift build is done. :disappointed:

I think there could be a story for runtime/compile-time being different, sure. And on the one hand while it may seem unfortunate that running a test could cause an incremental build or rebuild, a person that is not actively running tests could also have a faster build cycle because their test helpers don't have to build.

Now that we have the package ACL, a testing ACL seems to be a natural next step. It could be used to define types and functions only visible to test targets, and combined with the other ACLs to offer public, package, and internal test functionality. Given much of the capability is built out for package, it may actually be faster to implement than a completely new solution.

2 Likes