Customize settings for testing

It's often the case that we want to give tests a privileged access to the code-under-test. For example, in Swift we have -enable-testing to cause internal symbols to be visible outside the module.

I'm trying to achieve something similar in a C target. Suppose we have a public function foo and an "internal" function fooHelper. In this listing, we emit a prototype and a symbol that can be called from a test. But ordinarily, we emit neither prototype nor symbol:

//header.h
int foo();
#if TESTING
int fooHelper();
#endif

//file.c
#if TESTING
#define MAYBE_STATIC
#else
#define MAYBE_STATIC static
#endif
MAYBE_STATIC int fooHelper() { /* */ }

int foo() { 
    fooHelper() 
    /* */ 
}

Now the problem becomes how to convince SwiftPM to emit such a TESTING.

The obvious way is to configure cSettings in Package.swift, perhaps conditioned on .when(configuration:.debug). However, this isn't correct: a) downstream packages which may build for debug, do not want the TESTING, b) performance tests, which are built for release, do want the TESTING.

The other obvious way is to pass -Xcc TESTING at the cli, however I'm not sure how to get that to play nice with Xcode, which is the practical development workflow.

The non-obvious way is to create multiple targets and keep SwiftPM from being aware they contain the same sourcefiles by clever use of symlinks.

It would improve this situation considerably if any of:

  1. SwiftPM / Xcode emitted a cflag + swiftflag for testing
  2. There were a way to create and use a custom configuration, which we could then pass to .when to enable this setting
  3. there were a way to figure out if we are the toplevel package, or a dependency. While this is not exactly a testing flag, it's pretty close for library code
  4. There were a real way to declare multiple targets on the same set of sources, one of which contains the setting and is nominated as a test dependency, the other one for public consumption.
2 Likes

I think this sounds like the way to go for me. It seems reasonable to offer an API that allows making configuration conditional on the package being a root package or not. There's a slight complication which is that Xcode workspaces are essentially "multi-root", so a package could be both, but it seems OK to treat it as only a root package for the purpose of such an API.

Other use cases for such an API I could think of would be depending on additional packages that bring development tools which wouldn't be needed if the package was used as a dependency or even running additional build steps, e.g. linters, once we have something like the extensible build tools proposal.

How would you feel about emitting -DTOPLEVEL for the compiler if the target is in the toplevel package? Then we can conditionally compile #if TOPLEVEL.