|
| 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 | +## Basic format |
| 17 | + |
| 18 | +Swift Testing uses the [ELF Note format](https://man7.org/linux/man-pages/man5/elf.5.html) |
| 19 | +to store individual records of test content. Records created and discoverable by |
| 20 | +the testing library are stored in dedicated platform-specific sections: |
| 21 | + |
| 22 | +| Platform | Binary Format | Section Name | |
| 23 | +|-|:-:|-| |
| 24 | +| macOS, iOS, watchOS, tvOS, visionOS | Mach-O | `__DATA_CONST,__swift5_tests` | |
| 25 | +| Linux, FreeBSD, Android | ELF | `PT_NOTE`[^1] | |
| 26 | +| WASI | Statically Linked | `swift5_tests` | |
| 27 | +| Windows | PE/COFF | `.sw5test` | |
| 28 | + |
| 29 | +[^1]: On platforms that use the ELF binary format natively, test content records |
| 30 | + are stored in ELF program headers of type `PT_NOTE`. Take care not to |
| 31 | + remove these program headers (for example, by invoking [`strip(1)`](https://www.man7.org/linux/man-pages/man1/strip.1.html).) |
| 32 | + |
| 33 | +### Record headers |
| 34 | + |
| 35 | +Regardless of platform, all test content records created and discoverable by the |
| 36 | +testing library have the following structure: |
| 37 | + |
| 38 | +```c |
| 39 | +struct SWTTestContentHeader { |
| 40 | + int32_t n_namesz; |
| 41 | + int32_t n_descsz; |
| 42 | + int32_t n_type; |
| 43 | + char n_name[n_namesz]; |
| 44 | + // ... |
| 45 | +}; |
| 46 | +``` |
| 47 | + |
| 48 | +This structure can be represented in Swift as a heterogenous tuple: |
| 49 | + |
| 50 | +```swift |
| 51 | +typealias SWTTestContentHeader = ( |
| 52 | + n_namesz: Int32, |
| 53 | + n_descsz: Int32, |
| 54 | + n_type: Int32, |
| 55 | + n_name: (CChar, CChar, /* ... */), |
| 56 | + // ... |
| 57 | +) |
| 58 | +``` |
| 59 | + |
| 60 | +The size of `n_name` is dynamic and cannot be statically computed. The testing |
| 61 | +library always generates the name `"Swift Testing"` and specifies an `n_namesz` |
| 62 | +value of `20` (the string being null-padded to the correct length), but other |
| 63 | +content may be present in the same section whose header size differs. For more |
| 64 | +information about this structure such as its alignment requirements, see the |
| 65 | +documentation for the [ELF format](https://man7.org/linux/man-pages/man5/elf.5.html). |
| 66 | + |
| 67 | +Each record's _kind_ (stored in the `n_type` field) determines how the record |
| 68 | +will be interpreted at runtime: |
| 69 | + |
| 70 | +| Type Value | Interpretation | |
| 71 | +|-:|-| |
| 72 | +| `< 0` | Undefined (**do not use**) | |
| 73 | +| `0 ... 99` | Reserved | |
| 74 | +| `100` | Test or suite declaration | |
| 75 | +| `101` | Exit test | |
| 76 | + |
| 77 | +<!-- When adding cases to this enumeration, be sure to also update the |
| 78 | +corresponding enumeration in Discovery.h and TestContentGeneration.swift. --> |
| 79 | + |
| 80 | +### Record contents |
| 81 | + |
| 82 | +For all currently-defined record types, the header structure is immediately |
| 83 | +followed by the actual content of the record. A test content record currently |
| 84 | +contains an `accessor` function to load the corresponding Swift content and a |
| 85 | +`flags` field whose value depends on the type of record. The overall structure |
| 86 | +of a record therefore looks like: |
| 87 | + |
| 88 | +```c |
| 89 | +struct SWTTestContent { |
| 90 | + SWTTestContentHeader header; |
| 91 | + bool (* accessor)(void *outValue); |
| 92 | + uint32_t flags; |
| 93 | + uint32_t reserved; |
| 94 | +}; |
| 95 | +``` |
| 96 | + |
| 97 | +Or, in Swift as a tuple: |
| 98 | + |
| 99 | +```swift |
| 100 | +typealias SWTTestContent = ( |
| 101 | + header: SWTTestContentHeader, |
| 102 | + accessor: @convention(c) (_ outValue: UnsafeMutableRawPointer) -> Bool, |
| 103 | + flags: UInt32, |
| 104 | + reserved: UInt32 |
| 105 | +) |
| 106 | +``` |
| 107 | + |
| 108 | +This structure may grow in the future as needed. Check the `header.n_descsz` |
| 109 | +field to determine if there are additional fields present. Do not assume that |
| 110 | +the size of this structure will remain fixed over time or that all discovered |
| 111 | +test content records are the same size. |
| 112 | + |
| 113 | +#### The accessor field |
| 114 | + |
| 115 | +The function `accessor` is a C function. When called, it initializes the memory |
| 116 | +at its argument `outValue` to an instance of some Swift type and returns `true`, |
| 117 | +or returns `false` if it could not generate the relevant content. On successful |
| 118 | +return, the caller is responsible for deinitializing the memory at `outValue` |
| 119 | +when done with it. |
| 120 | + |
| 121 | +The concrete Swift type of the value written to `outValue` depends on the type |
| 122 | +of record: |
| 123 | + |
| 124 | +| Type Value | Return Type | |
| 125 | +|-:|-| |
| 126 | +| `..< 0` | Undefined (**do not use**) | |
| 127 | +| `0 ... 99` | Reserved (**do not use**) | |
| 128 | +| `100` | `@Sendable () async -> Test`[^2] | |
| 129 | +| `101` | `ExitTest` (consumed by caller) | |
| 130 | + |
| 131 | +[^2]: This signature is not the signature of `accessor`, but of the Swift |
| 132 | + function reference it writes to `outValue`. This level of indirection is |
| 133 | + necessary because loading a test or suite declaration is an asynchronous |
| 134 | + operation, but C functions cannot be `async`. |
| 135 | + |
| 136 | +#### The flags field |
| 137 | + |
| 138 | +For test or suite declarations (type `100`), the following flags are defined: |
| 139 | + |
| 140 | +| Bit | Description | |
| 141 | +|-:|-| |
| 142 | +| `1 << 0` | This record contains a suite declaration | |
| 143 | +| `1 << 1` | This record contains a parameterized test function declaration | |
| 144 | + |
| 145 | +For exit test declarations (type `101`), no flags are currently defined. |
| 146 | + |
| 147 | +#### The reserved field |
| 148 | + |
| 149 | +This field is reserved for future use. Always set it to `0`. |
| 150 | + |
| 151 | +## Third-party test content |
| 152 | + |
| 153 | +Testing tools may make use of the same storage and discovery mechanisms by |
| 154 | +emitting their own test content records into the test record content section. |
| 155 | + |
| 156 | +Third-party test content should use the same value for the `n_name` field |
| 157 | +(`"Swift Testing"`). The `n_type` field should be set to a unique value only |
| 158 | +used by that tool, or used by that tool in collaboration with other compatible |
| 159 | +tools. At runtime, Swift Testing ignores test content records with unrecognized |
| 160 | +`n_type` values. To reserve a new unique `n_type` value, open a [GitHub issue](https://github.com/swiftlang/swift-testing/issues/new/choose) |
| 161 | +against Swift Testing. |
| 162 | + |
| 163 | +The layout of third-party test content records must be compatible with that of |
| 164 | +`SWTTestContentHeader` as specified above. For the actual content of a test |
| 165 | +record, you do not need to use the same on-disk/in-memory layout as is specified |
| 166 | +by `SWTTestContent` above, but it is preferred. Third-party tools are ultimately |
| 167 | +responsible for ensuring the values they emit into the test content section are |
| 168 | +correctly aligned and have sufficient padding; failure to do so may render |
| 169 | +downstream test code unusable. |
| 170 | + |
| 171 | +<!-- |
| 172 | +TODO: elaborate further, give examples |
| 173 | +TODO: standardize a mechanism for third parties to produce `Test` instances |
| 174 | + since we don't have a public initializer for the `Test` type. |
| 175 | +--> |
0 commit comments