Skip to content

Commit 48a9f9f

Browse files
authored
Print warning in console when invoking XCFail from non-DEBUG (#56)
* Non-debug warning. * wip * wip * wip * fix * wip * wip * wip
1 parent b5149a0 commit 48a9f9f

File tree

7 files changed

+106
-13
lines changed

7 files changed

+106
-13
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
steps:
2828
- uses: actions/checkout@v3
2929
- name: Run tests
30-
run: make test
30+
run: make test-linux
3131

3232
wasm:
3333
name: SwiftWASM

Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@ XCT_FAIL = \033[34mXCTFail\033[0m
55
EXPECTED_STRING = This is expected to fail!
66
EXPECTED = \033[31m\"$(EXPECTED_STRING)\"\033[0m
77

8-
test:
8+
test-debug:
99
@swift build --build-tests \
1010
&& TEST_FAILURE=true swift test 2>&1 | grep '$(EXPECTED_STRING)' > /dev/null \
1111
&& (echo "$(PASS) $(XCT_FAIL) was called with $(EXPECTED)" && exit) \
1212
|| (echo "$(FAIL) expected $(XCT_FAIL) to be called with $(EXPECTED)" >&2 && exit 1)
1313

14-
test-linux:
14+
test: test-debug
15+
@swift test -c release | grep '⚠︎ Warning: This XCTFail was ignored' || exit 1
16+
17+
test-linux: test-debug
18+
@swift test -c release
19+
20+
test-linux-docker:
1521
@docker run \
1622
--rm \
1723
-v "$(PWD):$(PWD)" \

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,20 @@ import XCTest
3131
3232
This is due to a confluence of problems, including test header search paths, linker issues, and more. XCTest just doesn't seem to be built to be loaded alongside your application or library code.
3333

34-
## Solution
35-
36-
That doesn't mean we can't try! XCTest Dynamic Overlay is a microlibrary that exposes an `XCTFail` function that can be invoked from anywhere. It dynamically loads XCTest functionality at runtime, which means your code will continue to compile just fine.
34+
So, the XCTest Dynamic Overlay library is a microlibrary that dynamically loads the `XCTFail` symbol
35+
at runtime and exposes it publicly so that it can be used from anywhere. This means you can import
36+
this library instead of XCTest:
3737

3838
```swift
3939
import XCTestDynamicOverlay //
4040
```
4141

42+
…and your application or library will continue to compile just fine.
43+
44+
> ⚠️ Important: The dynamically loaded `XCTFail` is only available in `DEBUG` builds in order
45+
to prevent App Store rejections due to runtime loading of symbols.
46+
47+
4248
## Example
4349

4450
A real world example of using this is in our library, the [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture). That library vends a `TestStore` type whose purpose is to make it easy to write tests for your application's logic. The `TestStore` uses `XCTFail` internally, and so that forces us to move the code to a dedicated test support module. However, due to how SPM works you cannot currently have that module in the same package as the main module, and so we would be forced to extract it to a separate _repo_. By loading `XCTFail` dynamically we can keep the code where it belongs.

Sources/XCTestDynamicOverlay/Documentation.docc/GettingStarted.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# Getting Started
22

3-
<!--@START_MENU_TOKEN@-->Summary<!--@END_MENU_TOKEN@-->
4-
53
## Overview
64

75
A real world example of using this is in our library, the [Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture). That library vends a `TestStore` type whose purpose is to make it easy to write tests for your application's logic. The `TestStore` uses `XCTFail` internally, and so that forces us to move the code to a dedicated test support module. However, due to how SPM works you cannot currently have that module in the same package as the main module, and so we would be forced to extract it to a separate _repo_. By loading `XCTFail` dynamically we can keep the code where it belongs.

Sources/XCTestDynamicOverlay/Documentation.docc/XCTestDynamicOverlay.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,52 @@ Define XCTest assertion helpers directly in your application and library code.
44

55
## Overview
66

7-
XCTest Dynamic Overlay provides APIs for invoking XCTest's `XCTFail` directly in your application and library code.
7+
It is very common to write test support code for libraries and applications. This often comes in the
8+
form of little domain-specific functions or helpers that make it easier for users of your code to
9+
formulate assertions on behavior.
10+
11+
Currently there are only two options for writing test support code:
12+
13+
* Put it in a test target, but then you can't access it from multiple other test targets. For
14+
whatever reason test targets cannot be imported, and so the test support code will only be available
15+
in that one single test target.
16+
* Create a dedicated test support module that ships just the test-specific code. Then you can import
17+
this module into as many test targets as you want, while never letting the module interact with your
18+
regular, production code.
19+
20+
Neither of these options is ideal. In the first case you cannot share your test support, and the
21+
second case will lead you to a proliferation of modules. For each feature you potentially need 3
22+
modules: `MyFeature`, `MyFeatureTests` and `MyFeatureTestSupport`. SPM makes managing this quite
23+
easy, but it's still a burden.
24+
25+
It would be far better if we could ship the test support code right along side or actual library or
26+
application code. After all, they are intimately related. You can even fence off the test support
27+
code in `#if DEBUG ... #endif` if you are worried about leaking test code into production.
28+
29+
However, as soon as you add `import XCTest` to a source file in your application or a library it
30+
loads, the target becomes unbuildable:
31+
32+
```swift
33+
import XCTest
34+
// 🛑 ld: warning: Could not find or use auto-linked library 'XCTestSwiftSupport'
35+
// 🛑 ld: warning: Could not find or use auto-linked framework 'XCTest'
36+
```
37+
38+
This is due to a confluence of problems, including test header search paths, linker issues, and
39+
more. XCTest just doesn't seem to be built to be loaded alongside your application or library code.
40+
41+
So, the XCTest Dynamic Overlay library is a microlibrary that dynamically loads the `XCTFail` symbol
42+
at runtime and exposes it publicly so that it can be used from anywhere. This means you can import
43+
this library instead of XCTest:
44+
45+
```swift
46+
import XCTestDynamicOverlay //
47+
```
48+
49+
…and your application or library will continue to compile just fine.
50+
51+
> Important: The dynamically loaded `XCTFail` is only available in `DEBUG` builds in order
52+
to prevent App Store rejections due to runtime loading of symbols.
853

954
## Topics
1055

Sources/XCTestDynamicOverlay/Internal/RuntimeWarnings.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Foundation
2+
13
@_transparent
24
@inline(__always)
35
@usableFromInline

Sources/XCTestDynamicOverlay/XCTFail.swift

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,13 @@ import Foundation
124124
@_exported import func XCTest.XCTFail
125125
#else
126126
@_disfavoredOverload
127-
public func XCTFail(_ message: String = "") {}
127+
public func XCTFail(_ message: String = "") {
128+
print(noop(message: message))
129+
}
128130
@_disfavoredOverload
129-
public func XCTFail(_ message: String = "", file: StaticString, line: UInt) {}
131+
public func XCTFail(_ message: String = "", file: StaticString, line: UInt) {
132+
print(noop(message: message, file: file, line: line))
133+
}
130134
#endif
131135
#else
132136
/// This function generates a failure immediately and unconditionally.
@@ -138,7 +142,9 @@ import Foundation
138142
/// - Parameter message: An optional description of the assertion, for inclusion in test
139143
/// results.
140144
@_disfavoredOverload
141-
public func XCTFail(_ message: String = "") {}
145+
public func XCTFail(_ message: String = "") {
146+
print(noop(message: message))
147+
}
142148

143149
/// This function generates a failure immediately and unconditionally.
144150
///
@@ -149,5 +155,35 @@ import Foundation
149155
/// - Parameter message: An optional description of the assertion, for inclusion in test
150156
/// results.
151157
@_disfavoredOverload
152-
public func XCTFail(_ message: String = "", file: StaticString, line: UInt) {}
158+
public func XCTFail(_ message: String = "", file: StaticString, line: UInt) {
159+
print(noop(message: message, file: file, line: line))
160+
}
153161
#endif
162+
163+
private func noop(message: String, file: StaticString? = nil, line: UInt? = nil) -> String {
164+
let fileAndLine: String
165+
if let file = file, let line = line {
166+
fileAndLine = """
167+
:
168+
169+
\(file):\(line)
170+
171+
┃ …
172+
"""
173+
} else {
174+
fileAndLine = "\n"
175+
}
176+
177+
return """
178+
XCTFail: \(message)
179+
180+
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┉┅
181+
┃ ⚠︎ Warning: This XCTFail was ignored
182+
183+
┃ XCTFail was invoked in a non-DEBUG environment\(fileAndLine)and so was ignored. Be sure to run tests with
184+
┃ the DEBUG=1 flag set in order to dynamically
185+
┃ load XCTFail.
186+
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┉┅
187+
▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄
188+
"""
189+
}

0 commit comments

Comments
 (0)