Skip to content

Commit a90a3d1

Browse files
authored
Migrate from XCTest to Swift Testing (#382)
Migrating `swift-log` from XCTest to Swift Testing ### Motivation Swift Testing is a modern alternative to XCTest. Swift Server Ecosystem packages should serve as reference implementations for modern Swift features and best practices. ### Modifications - 1-to-1 migration of test suites, tests, and assertions to Swift Testing syntax with XCTest import removed - XCTest references removed from documentation - `.trace` log level added to relevant tests - `GlobalLoggerTest` suite serialized to handle global `LoggingSystem` access (XCTest runs tests serially by default; Swift Testing runs in parallel, so serializing this suite avoids requiring `swift test --no-parallel` across all CI and development environments) ### Result Successfully executes 52 tests with `swift test --disable-xctest`.
1 parent 2a4139e commit a90a3d1

File tree

10 files changed

+537
-421
lines changed

10 files changed

+537
-421
lines changed

CONTRIBUTING.md

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,34 +62,42 @@ We require that your commit messages match our template. The easiest way to do t
6262

6363
### Make sure Tests work on Linux
6464

65-
SwiftLog uses XCTest to run tests on both macOS and Linux. While the macOS version of XCTest is able to use the Objective-C runtime to discover tests at execution time, the Linux version is not.
66-
For this reason, whenever you add new tests **you have to run a script** that generates the hooks needed to run those tests on Linux, or our CI will complain that the tests are not all present on Linux. To do this, merely execute `ruby ./scripts/generate_linux_tests.rb` at the root of the package and check the changes it made.
65+
SwiftLog uses Swift Testing to run tests on all supported platforms.
6766

68-
### Run `./scripts/soundness.sh`
67+
### Run CI checks locally
6968

70-
The scripts directory contains a [soundness.sh script](https://github.com/apple/swift-log/blob/main/scripts/soundness.sh)
71-
that enforces additional checks, like license headers and formatting style.
69+
You can run the Github Actions workflows locally using
70+
[act](https://github.com/nektos/act). To run all the jobs that run on a pull
71+
request, use the following command:
7272

73-
Please make sure to `./scripts/soundness.sh` before pushing a change upstream, otherwise it is likely the PR validation will fail
74-
on minor changes such as a missing `self.` or similar formatting issues.
73+
```
74+
% act pull_request
75+
```
7576

76-
> The script also executes the above mentioned `generate_linux_tests.rb`.
77+
To run just a single job, use `workflow_call -j <job>`, and specify the inputs
78+
the job expects. For example, to run just shellcheck:
7779

78-
For frequent contributors, we recommend adding the script as a [git pre-push hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks), which you can do via executing the following command in the project root directory:
80+
```
81+
% act workflow_call -j soundness --input shell_check_enabled=true
82+
```
7983

80-
```bash
81-
cat << EOF > .git/hooks/pre-push
82-
#!/bin/bash
84+
To bind-mount the working directory to the container, rather than a copy, use
85+
`--bind`. For example, to run just the formatting, and have the results
86+
reflected in your working directory:
8387

84-
if [[ -f "scripts/soundness.sh" ]]; then
85-
scripts/soundness.sh
86-
fi
87-
EOF
88+
```
89+
% act --bind workflow_call -j soundness --input format_check_enabled=true
8890
```
8991

90-
Which makes the script execute, and only allow the `git push` to complete if the check has passed.
92+
If you'd like `act` to always run with certain flags, these can be be placed in
93+
an `.actrc` file either in the current working directory or your home
94+
directory, for example:
9195

92-
In the case of formatting issues, you can then `git add` the formatting changes, and attempt the push again.
96+
```
97+
--container-architecture=linux/amd64
98+
--remote-name upstream
99+
--action-offline-mode
100+
```
93101

94102
## How to contribute your work
95103

NOTICE.txt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,6 @@ components that this product depends on.
2626

2727
-------------------------------------------------------------------------------
2828

29-
This product contains a derivation of the Tony Stone's 'process_test_files.rb'.
30-
31-
* LICENSE (Apache License 2.0):
32-
* https://www.apache.org/licenses/LICENSE-2.0
33-
* HOMEPAGE:
34-
* https://codegists.com/snippet/ruby/generate_xctest_linux_runnerrb_tonystone_ruby
35-
36-
---
37-
3829
This product contains a derivation of the lock implementation and various
3930
scripts from SwiftNIO.
4031

Tests/LoggingTests/CompatibilityTest.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414

1515
// Not testable
1616
import Logging
17-
import XCTest
17+
import Testing
1818

19-
final class CompatibilityTest: XCTestCase {
19+
struct CompatibilityTest {
2020
@available(*, deprecated, message: "Testing deprecated functionality")
21-
func testAllLogLevelsWorkWithOldSchoolLogHandlerWorks() {
21+
@Test func allLogLevelsWorkWithOldSchoolLogHandlerWorks() {
2222
let testLogging = OldSchoolTestLogging()
2323

2424
var logger = Logger(label: "\(#function)", factory: { testLogging.make(label: $0) })

Tests/LoggingTests/GlobalLoggingTest.swift

Lines changed: 110 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Dispatch
16-
import XCTest
16+
import Testing
1717

1818
@testable import Logging
1919

20-
class GlobalLoggerTest: XCTestCase {
21-
func test1() throws {
20+
/// This is the only test suite allowed to use global LoggingSystem bootstrapping,
21+
/// tests in the suite are executed one by one
22+
@Suite(.serialized)
23+
struct GlobalLoggerTest {
24+
@Test func traceAndAbove() throws {
2225
// bootstrap with our test logging impl
2326
let logging = TestLogging()
2427
LoggingSystem.bootstrapInternal { logging.make(label: $0) }
2528

26-
// change test logging config to log traces and above
27-
logging.config.set(value: Logger.Level.debug)
29+
// change test logging config to log trace and above
30+
logging.config.set(value: Logger.Level.trace)
2831
// run our program
2932
Struct1().doSomething()
3033
// test results
@@ -40,12 +43,16 @@ class GlobalLoggerTest: XCTestCase {
4043
logging.history.assertExist(level: .debug, message: "Struct3::doSomethingElse::Local", metadata: ["baz": "qux"])
4144
logging.history.assertExist(level: .debug, message: "Struct3::doSomethingElse::end")
4245
logging.history.assertExist(level: .debug, message: "Struct3::doSomething::end")
46+
logging.history.assertExist(level: .trace, message: "Struct3::doSomething::end::lastLine")
4347
logging.history.assertExist(level: .debug, message: "Struct2::doSomethingElse::end")
48+
logging.history.assertExist(level: .trace, message: "Struct2::doSomethingElse::end::lastLine")
4449
logging.history.assertExist(level: .debug, message: "Struct1::doSomethingElse::end")
4550
logging.history.assertExist(level: .debug, message: "Struct1::doSomething::end")
51+
logging.history.assertExist(level: .trace, message: "Struct1::doSomething::end::lastLine")
52+
4653
}
4754

48-
func test2() throws {
55+
@Test func errorAndAbove() throws {
4956
// bootstrap with our test logging impl
5057
let logging = TestLogging()
5158
LoggingSystem.bootstrapInternal { logging.make(label: $0) }
@@ -75,12 +82,15 @@ class GlobalLoggerTest: XCTestCase {
7582
)
7683
logging.history.assertNotExist(level: .debug, message: "Struct3::doSomethingElse::end")
7784
logging.history.assertNotExist(level: .debug, message: "Struct3::doSomething::end")
85+
logging.history.assertNotExist(level: .trace, message: "Struct3::doSomething::end::lastLine")
7886
logging.history.assertNotExist(level: .debug, message: "Struct2::doSomethingElse::end")
87+
logging.history.assertNotExist(level: .trace, message: "Struct2::doSomethingElse::end::lastLine")
7988
logging.history.assertNotExist(level: .debug, message: "Struct1::doSomethingElse::end")
8089
logging.history.assertNotExist(level: .debug, message: "Struct1::doSomething::end")
90+
logging.history.assertNotExist(level: .trace, message: "Struct1::doSomething::end::lastLine")
8191
}
8292

83-
func test3() throws {
93+
@Test func warningAndAbove() throws {
8494
// bootstrap with our test logging impl
8595
let logging = TestLogging()
8696
LoggingSystem.bootstrapInternal { logging.make(label: $0) }
@@ -114,6 +124,96 @@ class GlobalLoggerTest: XCTestCase {
114124
}
115125
}
116126

127+
/// MetadataProvider tests relying on the global state
128+
extension GlobalLoggerTest {
129+
@Test func loggingMergesOneOffMetadataWithProvidedMetadataFromExplicitlyPassed() throws {
130+
let logging = TestLogging()
131+
132+
LoggingSystem.bootstrapInternal(
133+
{ logging.makeWithMetadataProvider(label: $0, metadataProvider: $1) },
134+
metadataProvider: .init {
135+
["common": "initial"]
136+
}
137+
)
138+
139+
let logger = Logger(
140+
label: #function,
141+
metadataProvider: .init {
142+
[
143+
"common": "provider",
144+
"provider": "42",
145+
]
146+
}
147+
)
148+
149+
logger.log(level: .info, "test", metadata: ["one-off": "42", "common": "one-off"])
150+
151+
logging.history.assertExist(
152+
level: .info,
153+
message: "test",
154+
metadata: ["common": "one-off", "one-off": "42", "provider": "42"]
155+
)
156+
}
157+
158+
@Test func loggerWithoutFactoryOverrideDefaultsToUsingLoggingSystemMetadataProvider() {
159+
let logging = TestLogging()
160+
LoggingSystem.bootstrapInternal(
161+
{ logging.makeWithMetadataProvider(label: $0, metadataProvider: $1) },
162+
metadataProvider: .init { ["provider": "42"] }
163+
)
164+
165+
let logger = Logger(label: #function)
166+
167+
logger.log(level: .info, "test", metadata: ["one-off": "42"])
168+
169+
logging.history.assertExist(
170+
level: .info,
171+
message: "test",
172+
metadata: ["provider": "42", "one-off": "42"]
173+
)
174+
}
175+
176+
@Test func loggerWithPredefinedLibraryMetadataProvider() {
177+
let logging = TestLogging()
178+
LoggingSystem.bootstrapInternal(
179+
{ logging.makeWithMetadataProvider(label: $0, metadataProvider: $1) },
180+
metadataProvider: .exampleProvider
181+
)
182+
183+
let logger = Logger(label: #function)
184+
185+
logger.log(level: .info, "test", metadata: ["one-off": "42"])
186+
187+
logging.history.assertExist(
188+
level: .info,
189+
message: "test",
190+
metadata: ["example": "example-value", "one-off": "42"]
191+
)
192+
}
193+
194+
@Test func loggerWithFactoryOverrideDefaultsToUsingLoggingSystemMetadataProvider() {
195+
let logging = TestLogging()
196+
LoggingSystem.bootstrapInternal(
197+
{ logging.makeWithMetadataProvider(label: $0, metadataProvider: $1) },
198+
metadataProvider: .init { ["provider": "42"] }
199+
)
200+
201+
let logger = Logger(
202+
label: #function,
203+
factory: { label in
204+
logging.makeWithMetadataProvider(label: label, metadataProvider: LoggingSystem.metadataProvider)
205+
}
206+
)
207+
logger.log(level: .info, "test", metadata: ["one-off": "42"])
208+
209+
logging.history.assertExist(
210+
level: .info,
211+
message: "test",
212+
metadata: ["provider": "42", "one-off": "42"]
213+
)
214+
}
215+
}
216+
117217
private struct Struct1 {
118218
private let logger = Logger(label: "GlobalLoggerTest::Struct1")
119219

@@ -127,6 +227,7 @@ private struct Struct1 {
127227
self.logger.debug("Struct1::doSomethingElse")
128228
Struct2().doSomething()
129229
self.logger.debug("Struct1::doSomethingElse::end")
230+
self.logger.trace("Struct1::doSomething::end::lastLine")
130231
}
131232
}
132233

@@ -143,6 +244,7 @@ private struct Struct2 {
143244
self.logger.info("Struct2::doSomethingElse")
144245
Struct3().doSomething()
145246
self.logger.debug("Struct2::doSomethingElse::end")
247+
self.logger.trace("Struct2::doSomethingElse::end::lastLine")
146248
}
147249
}
148250

@@ -154,6 +256,7 @@ private struct Struct3 {
154256
self.logger.error("Struct3::doSomething")
155257
self.doSomethingElse()
156258
self.logger.debug("Struct3::doSomething::end")
259+
self.logger.trace("Struct3::doSomething::end::lastLine")
157260
}
158261

159262
private func doSomethingElse() {

0 commit comments

Comments
 (0)