|
| 1 | +# Runtime-discoverable test content |
| 2 | + |
| 3 | +<!-- |
| 4 | +This source file is part of the Swift.org open source project |
| 5 | +
|
| 6 | +Copyright (c) 2024 Apple Inc. and the Swift project authors |
| 7 | +Licensed under Apache License v2.0 with Runtime Library Exception |
| 8 | +
|
| 9 | +See https://swift.org/LICENSE.txt for license information |
| 10 | +See https://swift.org/CONTRIBUTORS.txt for Swift project authors |
| 11 | +--> |
| 12 | + |
| 13 | +This document describes the format and location of test content that the testing |
| 14 | +library emits at compile time and can discover at runtime. |
| 15 | + |
| 16 | +> [!WARNING] |
| 17 | +> The content of this document is subject to change pending efforts to define a |
| 18 | +> Swift-wide standard mechanism for runtime metadata emission and discovery. |
| 19 | +> Treat the information in this document as experimental. |
| 20 | +
|
| 21 | +## Basic format |
| 22 | + |
| 23 | +Swift Testing stores test content records in a dedicated platform-specific |
| 24 | +section in built test products: |
| 25 | + |
| 26 | +| Platform | Binary Format | Section Name | |
| 27 | +|-|:-:|-| |
| 28 | +| macOS, iOS, watchOS, tvOS, visionOS | Mach-O | `__DATA_CONST,__swift5_tests` | |
| 29 | +| Linux, FreeBSD, OpenBSD, Android | ELF | `swift5_tests` | |
| 30 | +| WASI | WebAssembly | `swift5_tests` | |
| 31 | +| Windows | PE/COFF | `.sw5test$B`[^windowsPadding] | |
| 32 | + |
| 33 | +[^windowsPadding]: On Windows, the Swift compiler [emits](https://github.com/swiftlang/swift/blob/main/stdlib/public/runtime/SwiftRT-COFF.cpp) |
| 34 | + leading and trailing padding into this section, both zeroed and of size |
| 35 | + `MemoryLayout<UInt>.stride`. Code that walks this section must skip over this |
| 36 | + padding. |
| 37 | + |
| 38 | +### Record layout |
| 39 | + |
| 40 | +Regardless of platform, all test content records created and discoverable by the |
| 41 | +testing library have the following layout: |
| 42 | + |
| 43 | +```swift |
| 44 | +typealias TestContentRecord = ( |
| 45 | + kind: UInt32, |
| 46 | + reserved1: UInt32, |
| 47 | + accessor: (@convention(c) (_ outValue: UnsafeMutableRawPointer, _ hint: UnsafeRawPointer?) -> CBool)?, |
| 48 | + context: UInt, |
| 49 | + reserved2: UInt |
| 50 | +) |
| 51 | +``` |
| 52 | + |
| 53 | +This type has natural size, stride, and alignment. Its fields are native-endian. |
| 54 | +If needed, this type can be represented in C as a structure: |
| 55 | + |
| 56 | +```c |
| 57 | +struct SWTTestContentRecord { |
| 58 | + uint32_t kind; |
| 59 | + uint32_t reserved1; |
| 60 | + bool (* _Nullable accessor)(void *outValue, const void *_Null_unspecified hint); |
| 61 | + uintptr_t context; |
| 62 | + uintptr_t reserved2; |
| 63 | +}; |
| 64 | +``` |
| 65 | + |
| 66 | +### Record content |
| 67 | + |
| 68 | +#### The kind field |
| 69 | + |
| 70 | +Each record's _kind_ determines how the record will be interpreted at runtime. A |
| 71 | +record's kind is a 32-bit unsigned value. The following kinds are defined: |
| 72 | + |
| 73 | +| As Hexadecimal | As [FourCC](https://en.wikipedia.org/wiki/FourCC) | Interpretation | |
| 74 | +|-:|:-:|-| |
| 75 | +| `0x00000000` | – | Reserved (**do not use**) | |
| 76 | +| `0x74657374` | `'test'` | Test or suite declaration | |
| 77 | +| `0x65786974` | `'exit'` | Exit test | |
| 78 | + |
| 79 | +<!-- When adding cases to this enumeration, be sure to also update the |
| 80 | +corresponding enumeration in TestContentGeneration.swift. --> |
| 81 | + |
| 82 | +#### The accessor field |
| 83 | + |
| 84 | +The function `accessor` is a C function. When called, it initializes the memory |
| 85 | +at its argument `outValue` to an instance of some Swift type and returns `true`, |
| 86 | +or returns `false` if it could not generate the relevant content. On successful |
| 87 | +return, the caller is responsible for deinitializing the memory at `outValue` |
| 88 | +when done with it. |
| 89 | + |
| 90 | +If `accessor` is `nil`, the test content record is ignored. The testing library |
| 91 | +may, in the future, define record kinds that do not provide an accessor function |
| 92 | +(that is, they represent pure compile-time information only.) |
| 93 | + |
| 94 | +The second argument to this function, `hint`, is an optional input that can be |
| 95 | +passed to help the accessor function determine if its corresponding test content |
| 96 | +record matches what the caller is looking for. If the caller passes `nil` as the |
| 97 | +`hint` argument, the accessor behaves as if it matched (that is, no additional |
| 98 | +filtering is performed.) |
| 99 | + |
| 100 | +The concrete Swift type of the value written to `outValue` and the value pointed |
| 101 | +to by `hint` depend on the kind of record: |
| 102 | + |
| 103 | +- For test or suite declarations (kind `0x74657374`), the accessor produces an |
| 104 | + asynchronous Swift function that returns an instance of `Test`: |
| 105 | + |
| 106 | + ```swift |
| 107 | + @Sendable () async -> Test |
| 108 | + ``` |
| 109 | + |
| 110 | + This signature is not the signature of `accessor`, but of the Swift function |
| 111 | + reference it writes to `outValue`. This level of indirection is necessary |
| 112 | + because loading a test or suite declaration is an asynchronous operation, but |
| 113 | + C functions cannot be `async`. |
| 114 | + |
| 115 | + Test content records of this kind do not specify a type for `hint`. Always |
| 116 | + pass `nil`. |
| 117 | + |
| 118 | +- For exit test declarations (kind `0x65786974`), the accessor produces a |
| 119 | + structure describing the exit test (of type `__ExitTest`.) |
| 120 | + |
| 121 | + Test content records of this kind accept a `hint` of type `SourceLocation`. |
| 122 | + They only produce a result if they represent an exit test declared at the same |
| 123 | + source location (or if the hint is `nil`.) |
| 124 | + |
| 125 | +#### The context field |
| 126 | + |
| 127 | +This field can be used by test content to store additional context for a test |
| 128 | +content record that needs to be made available before the accessor is called: |
| 129 | + |
| 130 | +- For test or suite declarations (kind `0x74657374`), this field contains a bit |
| 131 | + mask with the following flags currently defined: |
| 132 | + |
| 133 | + | Bit | Value | Description | |
| 134 | + |-:|-:|-| |
| 135 | + | `1 << 0` | `1` | This record contains a suite declaration | |
| 136 | + | `1 << 1` | `2` | This record contains a parameterized test function declaration | |
| 137 | + |
| 138 | + Other bits are reserved for future use and must be set to `0`. |
| 139 | + |
| 140 | +- For exit test declarations (kind `0x65786974`), this field is reserved for |
| 141 | + future use and must be set to `0`. |
| 142 | + |
| 143 | +#### The reserved1 and reserved2 fields |
| 144 | + |
| 145 | +These fields are reserved for future use. Always set them to `0`. |
| 146 | + |
| 147 | +## Third-party test content |
| 148 | + |
| 149 | +Testing tools may make use of the same storage and discovery mechanisms by |
| 150 | +emitting their own test content records into the test record content section. |
| 151 | + |
| 152 | +Third-party test content should set the `kind` field to a unique value only used |
| 153 | +by that tool, or used by that tool in collaboration with other compatible tools. |
| 154 | +At runtime, Swift Testing ignores test content records with unrecognized `kind` |
| 155 | +values. To reserve a new unique `kind` value, open a [GitHub issue](https://github.com/swiftlang/swift-testing/issues/new/choose) |
| 156 | +against Swift Testing. |
| 157 | + |
| 158 | +The layout of third-party test content records must be compatible with that of |
| 159 | +`TestContentRecord` as specified above. Third-party tools are ultimately |
| 160 | +responsible for ensuring the values they emit into the test content section are |
| 161 | +correctly aligned and have sufficient padding; failure to do so may render |
| 162 | +downstream test code unusable. |
| 163 | + |
| 164 | +<!-- |
| 165 | +TODO: elaborate further, give examples |
| 166 | +TODO: standardize a mechanism for third parties to produce `Test` instances |
| 167 | + since we don't have a public initializer for the `Test` type. |
| 168 | +--> |
0 commit comments