recently, i’ve been working on upstreaming a totally-revamped JSON implementation to the swift-json project, an implementation which had previously been symlinked incessantly across many public and private repositories.
i figured this would be a great opportunity to adopt the new swift-testing library, as the project was previously using a testing framework i wrote myself long ago, which i was never really satisfied with. here are some of my first impressions of the swift-testing library.
Getting started documentation: A+
despite its early age, i think swift-testing has excellent “Getting started” documentation. i had zero experience using the library, but i was quickly able to pick up the basic patterns needed to use the library. the articles have good progressive disclosure, and i did not feel like i had a hard time understanding how the library is meant to be used. this reaction is atypical of my usual experience with Swift packages, so i felt it was worth highlighting.
one thing i think could be improved is the documentation should really be targeted towards Swift 5.10 users instead of nightly toolchain users, who probably make up a tiny fraction of the library’s potential audience. i often felt like the critical information i needed in order to use the library on Swift 5.10 was buried in asides and footnotes, and the articles were primarily addressed towards nightly users.
test writing workflow: C
in my opinion, this is not the fault of the swift-testing library, but i mention it anyway because it has a huge impact on anyone using the library: swift-testing depends on macros, macros depend on SwiftSyntax, and waiting for SwiftSyntax to resolve/clone/build sucks.
these are not new problems, they have been well-known for nearly a year now, and it has been independently-assessed that they are unlikely to be addressed under current language leadership.
because i was migrating away from a testing framework that did not use macros, i noticed a dramatic regression in how quickly i was able to iterate while writing tests.
again, i do not think this is the fault of the library, nor do i think the library should stop using macros. it is just an acknowledgement that using the library is going to surprisingly painful for those unaccustomed to building SwiftSyntax for test targets.
i should also note that if you already are using macros in your project, you will have a different probem, which is that swift-testing depends on version 509 and not 510, which will cause a dependency conflict. this conflict will be propogated to anyone using your project as well.
VSCode integrations: B
i don’t really have crazy expectations for tooling integrations in Swift, i’m generally happy as long as sourcekit-lsp works.
for reasons i don’t fully understand, compiling a project with swift build
does not compile the Testing
module in a way that is acceptable to sourcekit-lsp, so i always get the red squigglies with:
no such module 'Testing'sourcekitd
i can “fix” the problem by compiling Testing
explicitly:
$ swift build --target Testing
but for some reason, this makes it even worse for sourcekit-lsp, as it suddenly loses the ability to digest the attached macros.
unknown attribute 'Test'sourcekitd
the “no such module” error is less of an impediment, so my recommendation is to avoid compiling the Testing
module explicitly.
i found this very frustrating because mitigating the problem requires clearing the .build
directory, and if you do that, you have to compile SwiftSyntax all over again.
test running workflow: N/A
running tests did not work for me at all; it crashes and you have to wait for backtracing to complete to get the terminal back.
[342/342] Linking swift-jsonPackageTests.xctest
Build complete! (57.35s)
Test Suite 'All tests' started at 2024-03-12 23:40:08.230
Test Suite 'debug.xctest' started at 2024-03-12 23:40:08.232
Test Suite 'AllTests' started at 2024-03-12 23:40:08.232
Test Case 'AllTests.testAll' started at 2024-03-12 23:40:08.232
_Testing/Tag+Macro.swift:37: Precondition failed: Tags must be specified as members of the Tag type or a nested type in Tag.
*** Signal 4: Backtracing from 0x77c3b8fc961f...
done ***
*** Program crashed: Illegal instruction at 0x000077c3b8fc961f ***
Thread 0 "swift-jsonPacka":
0 0x000077c3b811181e ppoll + 174 in libc.so.6
Thread 1 crashed:
0 0x000077c3b8fc961f _assertionFailure(_:_:file:line:flags:) + 351 in libswiftCore.so
1 [ra] 0x00006213d87a8008 static Tag.__fromStaticMember(of:_:) + 1031 in swift-jsonPackageTests.xctest at /swift/swift-json/.build/checkouts/swift-testing/Sources/Testing/Traits/Tag+Macro.swift:37:5
2 [ra] 0x00006213d87aaa91 static Tag.red.getter + 48 in swift-jsonPackageTests.xctest at /tmp/swift-generated-sources/@__swiftmacro_8_Testing3TagV3redABfMa_.swift:3:19
3 [ra] 0x00006213d86d2be2 one-time initialization function for _predefinedTagColors + 49 in swift-jsonPackageTests.xctest at /swift/swift-json/.build/checkouts/swift-testing/Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift:93:8
4 [ra] 0x000077c3b92fce81 swift::threading_impl::once_slow(swift::threading_impl::once_t&, void (*)(void*), void*) + 160 in libswiftCore.so
5 [ra] 0x00006213d86d2df7 Event.ConsoleOutputRecorder._predefinedTagColors.unsafeMutableAddressor + 22 in swift-jsonPackageTests.xctest at /swift/swift-json/.build/checkouts/swift-testing/Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift:92:24
6 [ra] [thunk] 0x00006213d86d325b Event.ConsoleOutputRecorder.init(options:writingUsing:) + 282 in swift-jsonPackageTests.xctest at /swift/swift-json/.build/checkouts/swift-testing/Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift:120:50
7 [ra] 0x00006213d86ccc95 runTests(options:configuration:) + 148 in swift-jsonPackageTests.xctest at /swift/swift-json/.build/checkouts/swift-testing/Sources/Testing/Running/EntryPoint.swift:237:29
8 [async] 0x00006213d87e3850 static XCTestScaffold.runAllTests(hostedBy:) in swift-jsonPackageTests.xctest at /swift/swift-json/.build/checkouts/swift-testing/Sources/Testing/Running/XCTestScaffold.swift:189
9 [async] 0x00006213d86a60c0 AllTests.testAll() in swift-jsonPackageTests.xctest at /swift/swift-json/Sources/JSONTests/shims.swift:8
10 [async] 0x00006213d87e4900 implicit closure #2 in implicit closure #1 in variable initialization expression of static AllTests.__allTests__AllTests in swift-jsonPackageTests.xctest at /swift/swift-json/.build/x86_64-unknown-linux-gnu/debug/swift-jsonPackageDiscoveredTests.derived/JSONTests.swift:7
11 [async] [thunk] 0x00006213d87e4f00 partial apply for implicit closure #2 in implicit closure #1 in variable initialization expression of static AllTests.__allTests__AllTests in swift-jsonPackageTests.xctest at /swift/swift-json/<compiler-generated>
12 [async] [thunk] 0x00006213d87e4a20 thunk for @escaping @callee_guaranteed @async () -> () in swift-jsonPackageTests.xctest at /swift/swift-json/<compiler-generated>
13 [async] [thunk] 0x00006213d87e4e40 partial apply for thunk for @escaping @callee_guaranteed @async () -> () in swift-jsonPackageTests.xctest at /swift/swift-json/<compiler-generated>
14 [async] 0x000077c3b82e6e50 closure #1 in awaitUsingExpectation(_:) in libXCTest.so
15 [async] [thunk] 0x000077c3b82e76f0 partial apply for closure #1 in awaitUsingExpectation(_:) in libXCTest.so
16 [async] 0x000077c3b82e6fe0 specialized thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) in libXCTest.so
17 [async] [thunk] 0x000077c3b82e7d60 partial apply for specialized thunk for @escaping @callee_guaranteed @Sendable @async () -> (@out A) in libXCTest.so
18 [async] [system] 0x000077c3b8e3e790 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) in libswift_Concurrency.so
Thread 2:
0 0x000077c3b811d86e epoll_wait + 94 in libc.so.6
Thread 3:
0 0x000077c3b806b38a __futex_abstimed_wait_common + 202 in libc.so.6
Registers:
rax 0x000077c3b9268510 55 48 89 e5 48 85 ff 7e 26 48 b9 00 00 00 00 fe UH·åH·ÿ~&H¹····þ
rdx 0x00006213d881fe90 50 72 65 63 6f 6e 64 69 74 69 6f 6e 20 66 61 69 Precondition fai
rcx 0x000077c3b9268510 55 48 89 e5 48 85 ff 7e 26 48 b9 00 00 00 00 fe UH·åH·ÿ~&H¹····þ
rbx 0x000000077c3b0005 32149012485
rsi 0x80006213d8828250 9223479874231108176
rdi 0x000077c3b00008d0 03 00 01 00 04 00 03 00 02 00 01 00 01 00 01 00 ················
rbp 0x000077c3b5dc26f0 60 29 dc b5 c3 77 00 00 08 80 7a d8 13 62 00 00 `)ܵÃw····zØ·b··
rsp 0x000077c3b5dc2650 10 00 00 00 00 00 00 00 e8 fe ff ff ff ff ff ff ········èþÿÿÿÿÿÿ
r8 0x000077c3b00051e0 05 00 3b 7c 07 00 00 00 ad 6e 6a cd fe e3 53 00 ··;|····njÍþãS·
r9 0x000077c3b5dc23c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ················
r10 0x000077c3b81803e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ················
r11 0x0053e3fecd6a6ead 23613106574487213
r12 0x00006213d8828250 5f 54 65 73 74 69 6e 67 2f 54 61 67 2b 4d 61 63 _Testing/Tag+Mac
r13 0x000000000000004a 74
r14 0x80006213d8828250 9223479874231108176
r15 0x0000000000000013 19
rip 0x000077c3b8fc961f 0f 0b 48 83 ec 08 48 8d 05 64 35 35 00 48 8d 3d ··H·ì·H··d55·H·=
rflags 0x0000000000010286 SF PF
cs 0x0033 fs 0x0000 gs 0x0000
Images (15 omitted):
0x00006213d8613000–0x00006213d881251d <no build ID> swift-jsonPackageTests.xctest /swift/swift-json/.build/x86_64-unknown-linux-gnu/debug/swift-jsonPackageTests.xctest
0x000077c3b7fcf000–0x000077c3b816b04c a84b0b221620b4944c5940d3bed62056c7d7baa4 libc.so.6 /usr/lib64/libc.so.6
0x000077c3b82b7000–0x000077c3b8310390 <no build ID> libXCTest.so /usr/lib/swift/linux/libXCTest.so
0x000077c3b8de4000–0x000077c3b8e5a5b0 <no build ID> libswift_Concurrency.so /usr/lib/swift/linux/libswift_Concurrency.so
0x000077c3b8e65000–0x000077c3b947a2d0 <no build ID> libswiftCore.so /usr/lib/swift/linux/libswiftCore.so
Backtrace took 0.24s
error: Exited with unexpected signal code 4
this was puzzling because i never got far enough with the library to use any Tags.
i considered that this might be due to the XCTest behavior described in that section’s documentation, but the --disable-xctest
option mentioned there does not appear to exist at all, at least on Swift 5.10:
$ swift test --disable-xctest
error: Unknown option '--disable-xctest'
Usage: swift test <options> <subcommand>
See 'test -help' for more information.
for completeness, i also tried passing the suggested --enable-experimental-swift-testing
option, which also errored.
$ swift test --enable-experimental-swift-testing
error: Unknown option '--enable-experimental-swift-testing'
Usage: swift test <options> <subcommand>
See 'test -help' for more information.
i checked in the crashing code to a branch of the swift-json repo here: re-initial commit · tayloraswift/swift-json@c5fe88b · GitHub
conclusion
i am annoyed that i was unable to actually run a test after sinking some amount of effort into migrating to the new library, but i assume there is a rational explanation for why i am getting this crash and i am eager to find out what i am doing wrong there.
the issues with sourcekit-lsp and SwiftSyntax are more profound and i’m not sure what the library could possibly do to address them, as they seem to be “environmental effects” of the Swift ecosystem. i think swift-testing has the right idea and the right API patterns, but its progress and utility is severely limited by the state of Swift macros. i hope this writeup serves as some motivation for Swift project leadership to engage with developers limited by Swift macros.