blob: df0d92a049f33c7fb2df2be5f6f1e73bb40328bf [file] [log] [blame] [view]
# Compatibility Tests for Fuchsia
Compatibility Testing for Fuchsia (CTF) is a mechanism for running
different versions of pre-built Fuchsia software against the Fuchsia
platform surface area to detect compatibility problems.
CTF tests ensure that a client frozen at a previous release milestone
is compatible with the Application Programming Interface (API) and
Application Binary Interface (ABI) made available to developers via
the SDK. It simulates the situation where a behavioral change is
landed in the Fuchsia platform that breaks a pre-compiled client.
CTF fundamentally consists of the following parts:
- A mechanism to freeze artifacts on a release branch and then use
those artifacts in a test on the main branch.
- A set of build rules to select artifacts to freeze, and a set
of build rules to simplify thawing those frozen artifacts on
main.
- A suite of tests using those build rules to test the compatibility of
old client code with the latest platform surface area.
- The coverage provided by that suite of tests, in terms of which
FIDL methods and syscalls are tested for compatibility across
versions.
CTF was originally proposed as [RFC 0015][rfc-0015], and the project
code is located at [`//sdk/ctf`][sdk-ctf-directory].
## Motivation
The Fuchsia platform defines a surface area consisting of a number of
FIDL protocols which are exposed in the Fuchsia SDK. Developers may
download and use the Fuchsia SDK to write software targeting Fuchsia systems
at a specific *API level*.
As the Fuchsia platform surface area evolves over time, changes are
represented through availability annotations on FIDL protocols:
```fidl
protocol Entry {
@available(added=12)
SetValue(struct { value string; });
@available(added=20)
GetTimestamp() -> (struct { timestamp int64; });
@available(added=13, removed=20)
Encrypt();
};
```
In the above example, the protocol `Entry` has three methods:
- `SetValue`, which was added at API level 12
- `GetTimestamp`, which was added at API level 20
- `Encrypt`, which was added at API level 13 but removed at API level
20 when it was found to not be needed any longer.
Components built using the Fuchsia SDK must declare the API level
they target, affecting which definitions are visible. Consider the following
scenarios:
- A component is built targeting API level 19:
- `SetValue` and `Encrypt` are visible.
- `GetTimestamp` is absent, because it did not yet exist at API level 19.
- A component is built targeting API level 21:
- `SetValue` and `GetTimestamp` are visible.
- `Encrypt` is absent, because it was removed at API level 20.
Not all API levels are currently supported by the Fuchsia Platform.
The canonical list of [supported API levels][version-history] is checked
in to the `fuchsia.git` repository and is used to determine which API
levels will be supported in releases of the Fuchsia SDK.
Targeting a supported API level ensures that a client can connect
only to protocols that are supported and guaranteed to be implemented,
but it does not provide a guarantee that the behavior of the platform
will remain consistent for pre-built components targeting that API
level.
Ensuring consistent behavior is important to support components
built outside of the `fuchsia.git` repository, especially when those
components are downloaded as pre-built binaries and assembled into
product images.
### Example: A compatibility problem
Consider a simplified version of the `Entry` protocol above:
```fidl
protocol Entry {
@available(added=20)
GetTimestamp() -> (struct { timestamp int64; });
};
```
The `GetTimestamp` method returns the timestamp of an `Entry` *in seconds*.
We can test it as follows:
```c++
TEST(Timestamp) {
EntrySyncPtr entry = CreateEntryAtMidnightJan1stUTC();
ASSERT_EQ(entry.GetTimestamp(), 1704067200);
}
```
Suppose that we decide we actually want nanosecond granularity. We
will change the *implementation* of `GetTimestamp` to return
nanoseconds, but note that the *FIDL definition* does not change.
We would update our test as follows:
```diff
TEST(Timestamp) {
EntrySyncPtr entry = CreateEntryAtMidnightJan1stUTC();
- ASSERT_EQ(entry.GetTimestamp(), 17040672000);
+ ASSERT_EQ(entry.GetTimestamp(), 17040672000000000);
}
```
This will pass all checks in `fuchsia.git` and submit, but we have now
introduced a **compatibility breakage to the Fuchsia Platform**.
#### Failure timeline
Let's look at a complete timeline that leads to a breakage.
1. **Before the SDK change**: Suppose the FooWidget team wants to
implement their FooWidget for Fuchsia. They download the latest
Fuchsia SDK and build their component targeting API level 20. This
component uses the `Entry` protocol and calls the `GetTimestamp`
method. At this point in time, `GetTimestamp` returns seconds.
This works for them, so the FooWidget team will publish a Fuchsia
package called `foo-widget`. Upon inspection, users of this package
see it targets API level 20.
1. **FooWidget is included in a Fuchsia product**: A Fuchsia product
is assembled that includes the `foo-widget` package and the Fuchsia
platform from an SDK. The `foo-widget` package was ingested as
a pre-built binary without source code access, but product assembly
sees that the package was built targeting API level 20 and that the
Fuchsia platform image supports API level 20.
1. **The SDK change is rolled**: At this point, the change we defined
above lands in `fuchsia.git`, a new SDK is produced, and it is
released.
1. **The product is assembled with the new SDK**: The product
repository rolls to the new SDK and assembles the product using
a new Fuchsia platform image combined with the existing pre-built
`foo-widget`.
1. **FooWidget is broken on that product**: When the `foo-widget`
component calls `GetTimestamp()`, the results will be in nanoseconds
rather than seconds, which can have very strange results.
For instance, the FooWidget UI may start displaying times millions
of years in the future because it is treating nanoseconds as
seconds!
It is dangerous to change the behavior of platform functionality
when pre-existing software depends on the old behavior.
### Catching compatibility problems
CTF tests simulate the above scenario by taking advantage of Fuchsia's
release branching process. If a CTF test fails, it means that
pre-built components targeting an old Fuchsia platform implementation
will fail when targeting the latest platform implementation. It works
as follows:
1. Each Fuchsia [milestone release][milestone-release] has an
associated release branch in `fuchsia.git`. This represents the
state of Fuchsia's platform implementation at the time of release.
1. That platform implementation passes the set of tests on that
release branch.
1. A set of artifacts are included in a *CTF Artifacts* bundle for
that release branch.
1. When the release branch is created and/or modified, the CTF
Artifacts for that branch are compiled and uploaded to CIPD.
1. The CTF Artifacts for each supported API level are downloaded as
pre-builts on the `main` branch and are *thawed* by turning them
into test packages.
1. Those test packages are run against the latest Fuchsia platform
on the `main` branch, which will block CL submission if they fail.
This exactly maps to the scenario in which a component is built
against an old Fuchsia SDK and platform, and then is run as a
pre-built against the current Fuchsia platform.
## Background and history
CTF exists to prevent breaking changes to the implemented Fuchsia
API and ABI from landing in the platform.
CTF originally was designed to provide confidence that an arbitrary
Fuchsia system correctly implemented the platform surface area for
a particular ABI revision. This would mean that components built
for that revision would work on the system, and that the system is
backwards compatible with that revision.
The current purpose of CTF is to provide a mechanism for freezing
and "thawing" test artifacts across release branches.
This mechanism can be applied to ensure the current Fuchsia platform
supports the old API and ABI behavior for each version that is
supposed to be supported. Additionally, the frozen tests may be
applied to arbitrary Fuchsia images to achieve the original goal
of CTF if desired.
<!-- Reference links -->
[rfc-0015]: /docs/contribute/governance/rfcs/0015_cts.md
[sdk-ctf-directory]: https://fuchsia.googlesource.com/fuchsia/+/refs/heads/main/sdk/ctf/
[version-history]: https://fuchsia.googlesource.com/fuchsia/+/HEAD/sdk/version_history.json
[milestone-release]: /docs/concepts/versioning/release.md#milestone