Skip to content

Commit

Permalink
Store test content in a custom metadata section.
Browse files Browse the repository at this point in the history
See also: swiftlang/swift#76698

Resolves #735.
  • Loading branch information
grynspan committed Oct 3, 2024
1 parent b439448 commit ae3d2cf
Showing 1 changed file with 42 additions and 22 deletions.
64 changes: 42 additions & 22 deletions Documentation/ABI/TestContent.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,38 @@ the testing library are stored in dedicated platform-specific sections:

| Platform | Binary Format | Section Name |
|-|:-:|-|
| macOS | Mach-O | `__DATA_CONST,__swift5_tests` |
| iOS | Mach-O | `__DATA_CONST,__swift5_tests` |
| watchOS | Mach-O | `__DATA_CONST,__swift5_tests` |
| tvOS | Mach-O | `__DATA_CONST,__swift5_tests` |
| visionOS | Mach-O | `__DATA_CONST,__swift5_tests` |
| Linux | ELF | `PT_NOTE`[^1] |
| FreeBSD | ELF | `PT_NOTE`[^1] |
| Android | ELF | `PT_NOTE`[^1] |
| macOS, iOS, watchOS, tvOS, visionOS | Mach-O | `__DATA_CONST,__swift5_tests` |
| Linux, FreeBSD, Android | ELF | `PT_NOTE`[^1] |
| WASI | Statically Linked | `swift5_tests` |
| Windows | PE/COFF | `.sw5test` |

[^1]: On platforms that use the ELF binary format natively, test content records
are stored in ELF program headers of type `PT_NOTE`. Take care not to
remove these program headers (for example, by invoking [`strip(1)`](https://www.man7.org/linux/man-pages/man1/strip.1.html).)

### Determining the type of test content
### Record headers
Regardless of platform, all test content records created and discoverable by the
testing library start have the name `"Swift Testing"` stored in the implied
`n_name` field of their underlying ELF Notes. Each record's _type_ (stored in
the underlying ELF Note's `n_type` field) determines how the record will be
interpreted at runtime:
testing library have the following structure:

```c
struct SWTTestContentHeader {
int32_t n_namesz;
int32_t n_descsz;
int32_t n_type;
char n_name[n_namesz];
// ...
};

The size of `n_name` is dynamic and cannot be statically computed. The testing
library always generates the name `"Swift Testing"` and specifies an `n_namesz`
value of `20` (the string being null-padded to the correct length), but other
content may be present in the same section whose header content differs. For
more information about this structure such as its alignment requirements, see
the documentation for the [ELF format](https://man7.org/linux/man-pages/man5/elf.5.html).

Each record's _kind_ (stored in the `n_type` field) determines how the record
will be interpreted at runtime:
| Type Value | Interpretation |
|-:|-|
Expand All @@ -54,18 +64,27 @@ interpreted at runtime:
<!-- When adding cases to this enumeration, be sure to also update the
corresponding enumeration in Discovery.h and TestContentGeneration.swift. -->
### Loading test content from a record
### Record contents
For all currently-defined record types, the header and name are followed by a
structure of the following form:
For all currently-defined record types, the header structure is immediately
followed by the actual content of the record. A test content record currently
contains an `accessor` function to load the corresponding Swift content and a
`flags` field whose value depends on the type of record. The overall structure
of a record therefore looks like:
```c
struct SWTTestContent {
SWTTestContentHeader header;
bool (* accessor)(void *);
uint64_t flags;
};
```
This structure may grow in the future as needed. Check the `header.n_descsz`
field to determine if there are additional fields present. Do not assume that
the size of this structure will remain fixed over time or that all discovered
test content records are the same size.
#### The accessor field
The function `accessor` is a C function whose signature in Swift can be restated
Expand All @@ -80,19 +99,20 @@ Swift type and returns `true`, or returns `false` if it could not generate the
relevant content. On successful return, the caller is responsible for
deinitializing the memory at `outValue` when done with it.
The concrete Swift type of `accessor`'s result depends on the type of record:
The concrete Swift type of the value written to `outValue` depends on the type
of record:
| Type Value | Return Type |
|-:|-|
| < `0` | Undefined (**do not use**) |
| `0` ... `99` | `nil` |
| `0` ... `99` | Reserved (**do not use**) |
| `100` | `@Sendable () async -> Test`[^2] |
| `101` | `ExitTest` (owned by caller) |
| `101` | `ExitTest` (consumed by caller) |
[^2]: This signature is not the signature of `accessor`, but of the Swift
function reference it returns. This level of indirection is necessary
because loading a test or suite declaration is an asynchronous operation,
but C functions cannot be `async`.
function reference it writes to `outValue`. This level of indirection is
necessary because loading a test or suite declaration is an asynchronous
operation, but C functions cannot be `async`.
#### The flags field
Expand Down

0 comments on commit ae3d2cf

Please sign in to comment.