Skip to content

Commit f682170

Browse files
authored
Add a C-callable "gestalt" function for querying some basic info. (#1596)
This PR adds an exported C function that lets callers query some info that may be necessary to gather before those callers can reliably invoke the testing library's ABI-stable entry point function. This function is _not_ meant to be a general-purpose querying function, nor is it intended for use by tools authors. Rather, it is useful if you're writing code that works with the JSON event stream and needs to know how to communicate with the testing library. In particular, a caller would need to know the range of supported JSON schema versions. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 6c301e0 commit f682170

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

Sources/Testing/ABI/ABI.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
99
//
1010

11+
private import _TestingInternals
12+
1113
/// A namespace for ABI symbols.
1214
@_spi(ForToolsIntegrationOnly)
1315
public enum ABI: Sendable {}
@@ -209,3 +211,57 @@ extension ABI {
209211
}
210212
}
211213
}
214+
215+
// MARK: -
216+
217+
/// The set of keys accepted by `_swift_testing_copyMetadataValue(_:_:)`.
218+
private enum _MetadataKey: String, Sendable, CaseIterable {
219+
/// The minimum supported ABI version.
220+
case minimumSupportedABIVersion = "_minimumSupportedABIVersion"
221+
222+
/// The maximum supported ABI version.
223+
case maximumSupportedABIVersion = "_maximumSupportedABIVersion"
224+
}
225+
226+
/// An exported C function that allows querying information about the testing
227+
/// library without running any tests.
228+
///
229+
/// - Parameters:
230+
/// - key: The name of the value of interest. See the `_MetadataKey`
231+
/// enumeration for a list of supported values.
232+
/// - reserved: Reserved for future use. Pass `0`.
233+
///
234+
/// - Returns: A pointer to a UTF-8 C string containing the JSON representation
235+
/// of the requested information, or `nil` if that information was not
236+
/// available. The caller is responsible for freeing this memory with C's
237+
/// `free()` function.
238+
#if compiler(>=6.3)
239+
@c(_swift_testing_copyMetadataValue)
240+
#else
241+
@_cdecl("_swift_testing_copyMetadataValue")
242+
#endif
243+
@usableFromInline func _swift_testing_copyMetadataValue(_ key: UnsafePointer<CChar>, _ reserved: UInt) -> UnsafeMutablePointer<CChar>? {
244+
func copyJSON(for value: some Encodable) -> UnsafeMutablePointer<CChar>? {
245+
try? JSON.withEncoding(of: value) { json in
246+
json.withMemoryRebound(to: CChar.self) { json in
247+
// The JSON produced by Foundation is not null-terminated, so to avoid
248+
// an intermediate copy we call `calloc()` instead of `strdup()`.
249+
let result = calloc(json.count + 1, MemoryLayout<CChar>.stride)
250+
guard let result = result?.assumingMemoryBound(to: CChar.self) else {
251+
return nil
252+
}
253+
result.initialize(from: json.baseAddress!, count: json.count)
254+
return result
255+
}
256+
}
257+
}
258+
259+
switch String(validatingCString: key).flatMap(_MetadataKey.init) {
260+
case .minimumSupportedABIVersion:
261+
return copyJSON(for: ABI.v0.versionNumber)
262+
case .maximumSupportedABIVersion:
263+
return copyJSON(for: ABI.CurrentVersion.versionNumber)
264+
case nil:
265+
return nil
266+
}
267+
}

Tests/TestingTests/ABIEntryPointTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,22 @@ struct ABIEntryPointTests {
184184
}
185185
}
186186
#endif
187+
188+
func decodeMetadataValue<T>(forKey key: String, ofType type: T.Type) throws -> T where T: Decodable {
189+
let cString = try #require(_swift_testing_copyMetadataValue(key, 0))
190+
defer {
191+
free(cString)
192+
}
193+
let byteCount = strlen(cString)
194+
return try JSON.decode(type, from: UnsafeRawBufferPointer(start: cString, count: byteCount))
195+
}
196+
197+
@Test func copyMetadataValue() throws {
198+
let lowerBound = try decodeMetadataValue(forKey: "_minimumSupportedABIVersion", ofType: ABI.VersionNumber.self)
199+
#expect(lowerBound == ABI.v0.versionNumber)
200+
let upperBound = try decodeMetadataValue(forKey: "_maximumSupportedABIVersion", ofType: ABI.VersionNumber.self)
201+
#expect(upperBound == ABI.CurrentVersion.versionNumber)
202+
}
187203
}
188204

189205
#if !SWT_NO_DYNAMIC_LINKING

0 commit comments

Comments
 (0)