I am using CMake with my Swift -> Zephyr project and have some code I'd like to test using Swift Testing. It's code that is pure Swift and does not contain any hardware or Zephyr-specific dependencies. My idea was to put this code into a separate module from the "Firmware" module I have, and in this separate module be able to import that into a "Tests" module to then write some tests for my code:
Cool! I need to get better at looking at the repo first before asking questions...
However, I'm curious if the Testing module that is included with Swift 6.1 can be used directly instead of having to include the swift-testing package. I'll give it a try either way!
This does need to be separate from the Zephyr CMakeLists.txt since that one is set to compile for armv7em-none-none-eabi. The Testing module is not available for embedded, of course.
My next step is to create my core library and include that to start testing the code. But, this is a good start.
Note the callout immediately under that sample code:
Warning
The entry point is expected to change to an entry point designed for other build systems prior to the initial stable release of Swift Testing.
We've implemented that, we just haven't updated this particular documentation. Assuming you've built Swift Testing from source and can import SPI, you'll do something like this instead:
@_spi(ForToolsIntegrationOnly) import Testing
import YourCStandardLibraryNameHere /* Darwin, Glibc, etc. */
let entryPoint = Testing.ABI.v0.entryPoint
@main struct Runner {
static func main() async throws {
let result = try await entryPoint(nil) { _ in }
exit(result ? EXIT_SUCCESS : EXIT_FAILURE)
}
}
If you do not have access to SPI symbols, you can load a reference to this function from the C function swt_abiv0_getEntryPoint(). This approach currently requires a tiny bit more ceremony:
import Testing
import YourCStandardLibraryNameHere /* Darwin, Glibc, etc. */
@_extern(c, "swt_abiv0_getEntryPoint")
func swt_abiv0_getEntryPoint() -> UnsafeRawPointer
let entryPoint = unsafeBitCast(
swt_abiv0_getEntryPoint(),
to: (@convention(thin) @Sendable (
UnsafeRawBufferPointer?,
@escaping @Sendable (UnsafeRawBufferPointer) -> Void
) async throws -> Bool).self
)
/* struct Runner same as before */
Note that @_extern(c) is an experimental language feature; if you can't use it, you can use dlsym() or equivalent instead to dynamically look up swt_abiv0_getEntryPoint(), or if you have a C module, declare it extern there.
Wow, seems kind of hacky compare to the solution from the CMake guide. Why not just provide an official entry point that can be used for CMake instead of requiring Swift Testing to be built from source or using an experimental language feature?
Either way my goal is to not have to compile Swift Testing from source but to use the Testing module in Swift 6.1. I did try to use the 2nd solution with -enable-experimental-feature Extern but as I said...it seems kind of hacky to me...
Well yes, it absolutely is hacky. We don't officially support using Swift Testing via CMake (except as part of the Swift toolchain build) in the first place, so you're already off the beaten path here. The expectation is that most developers will use a Swift package or an Xcode project.
That doesn't mean we could never support other workflows, but we don't today, which means we don't offer API in support of said workflows. The compromise is to provide the aforementioned ABI-stable entry point function for workflows that aren't SwiftPM- or Xcode-based and to promise that that function will remain exported and accessible for the long term.
I'm trying to get a very basic Swift test building with cmake + Ninja. I used the entrypoint code shown above, but I get this error -- any ideas on how to get past this?
macro expansion @Test:11:105: error: no type named '__TestContainer' in module 'Testing'
`- /Users/x/dev/cmake_swift_testing/test/MyTest.swift:20:6: note: expanded code originates here
18 | let result = Twiddle(3, 7)
19 | #expect(result == 20)
20 | }
+--- macro expansion @Test ------------------------------------------
| 9 |
|10 | @available(*, deprecated, message: "This type is an implementation detail of the testing library. Do not use it directly.")
|11 | enum $s10MyLibTests0aC0V12basicTwiddle4TestfMp_38__🟠$test_container__function__e0acabf6fMu_: Testing.__TestContainer {
| | `- error: no type named '__TestContainer' in module 'Testing'
|12 | static var __tests: [Testing.Test] {
|13 | get async {
+--------------------------------------------------------------------
21 | }
22 |
Sounds like you're unintentionally using the macro plugin from another build, e.g. the copy included with Xcode or your toolchain. We've changed the code emitted by @Test on the main branch.
The macro plugin you use must be built from the same source as the library target; mixing and matching them is not supported.
If you want a working example using the swift-oss 6.x toolchain, this find module works on both macOS and Linux. I wouldn't recommend using FetchContent to get swift-testing if you can use Swift 6.
Well I say it works, but it has a dependency on some cmake variables set by this file: