mirror of
https://github.com/rust-vmm/rust-vmm-ci.git
synced 2026-08-05 03:08:31 +00:00
Fixes: https://github.com/rust-vmm/rust-vmm-ci/issues/39 Signed-off-by: Andreea Florescu <fandree@amazon.com>
298 lines
10 KiB
Markdown
298 lines
10 KiB
Markdown
# rust-vmm-ci
|
|
|
|
The `rust-vmm-ci` repository contains [integration tests](#integration-tests)
|
|
and [Buildkite pipeline](#buildkite-pipeline) definitions that are used for
|
|
running the CI for all rust-vmm crates.
|
|
|
|
Having a centralized place for the tests is one of the enablers for keeping the
|
|
same quality standard for all crates in rust-vmm.
|
|
|
|
## Getting Started with rust-vmm-ci
|
|
|
|
To run the integration tests defined in the pipeline as part of the CI:
|
|
|
|
1. Add rust-vmm-ci as a git submodule to your repository
|
|
|
|
```bash
|
|
# Add rust-vmm-ci as a submodule. This will point to the latest rust-vmm-ci
|
|
# commit from the master branch. The following command will also add a
|
|
# `.gitmodules` file and the `rust-vmm-ci` to the index.
|
|
git submodule add https://github.com/rust-vmm/rust-vmm-ci.git
|
|
# Commit the changes to your repository so that the CI can run using the
|
|
# rust-vmm-ci pipeline and tests.
|
|
git commit -s -m "Added rust-vmm-ci as submodule"
|
|
```
|
|
|
|
1. Create the coverage test configuration file named
|
|
`coverage_config_ARCH.json` in the root of the repository, where `ARCH` is the
|
|
architecture of the machine.
|
|
There are two coverage test configuration files, one per each platform.
|
|
The example of the configuration file for the `x86_64` architecture can be
|
|
found in
|
|
[coverage_config_x86_64.json.sample](coverage_config_x86_64.json.sample),
|
|
and the example of the configuration file for the `aarch64` architecture can be
|
|
found in
|
|
[coverage_config_aarch64.json.sample](coverage_config_aarch64.json.sample).
|
|
|
|
The json must have the following fields:
|
|
|
|
- `coverage_score`: The coverage of the repository.
|
|
- `exclude_path`: This field is used for excluding files from the report. It
|
|
should be used to exclude autogenerated files. Files in `exclude_path` are
|
|
separated by one comma. If the repository does not have any autogenerated
|
|
files, `exclude_path` should be an empty string.
|
|
- `crate_features`: `cargo kcov` does not build crate features by default. To
|
|
get the coverage report including optional features, these need to be
|
|
specified in `crate_features` separated by comma. If the crate does not have
|
|
any features, this field should be empty.
|
|
|
|
This file is required for the coverage integration so it needs to be added
|
|
to the repository as well.
|
|
|
|
1. Create a new pipeline definition in Buildkite. For this step ask one of the
|
|
rust-vmm Buildkite [admins](CODEOWNERS) to create one for you. Add a pipeline
|
|
step that is uploading the rust-vmm-ci pipeline:
|
|
|
|
```bash
|
|
buildkite-agent pipeline upload rust-vmm-ci/.buildkite/pipeline.yml
|
|
```
|
|
|
|
1. The code owners of the repository will have to setup a WebHook for
|
|
triggering the CI on
|
|
[pull request](https://developer.github.com/v3/activity/events/types/#pullrequestevent)
|
|
and [push](https://developer.github.com/v3/activity/events/types/#pushevent)
|
|
events.
|
|
|
|
## Buildkite Pipeline
|
|
|
|
The [Buildkite](https://buildkite.com) pipeline is the definition of tests to
|
|
be run as part of the CI. It includes steps for running unit tests and linters
|
|
(including coding style checks), and computing the coverage.
|
|
|
|
Currently the tests can run on Linux `x86_64` and `aarch64` hosts.
|
|
|
|
Example of step that checks the build:
|
|
|
|
```yaml
|
|
steps:
|
|
- label: "build-gnu-x86"
|
|
commands:
|
|
- cargo build --release
|
|
retry:
|
|
automatic: false
|
|
agents:
|
|
platform: x86_64.metal
|
|
plugins:
|
|
- docker#v3.0.1:
|
|
image: "rustvmm/dev:v${LATEST}"
|
|
always-pull: true
|
|
```
|
|
|
|
To see all steps in the pipeline check the
|
|
[.buildkite/pipeline.yml](.buildkite/pipeline.yml) file.
|
|
|
|
### Custom Pipeline
|
|
|
|
Some crates might need to test functionality that is specific to that
|
|
particular component and thus cannot be added to the common pipeline.
|
|
|
|
In this situation, the repositories need to create a custom pipeline (besides
|
|
the rust-vmm-ci pipeline) and add it in the repository. The preferred path for
|
|
the custom pipeline is `.buildkite/pipeline.yml`.
|
|
|
|
For example to test the build with one non-default
|
|
[feature](https://doc.rust-lang.org/1.19.0/book/first-edition/conditional-compilation.html)
|
|
enabled, the following step can be added in the custom pipeline under
|
|
`.buildkite/pipeline.yml`.
|
|
|
|
```yaml
|
|
steps:
|
|
- label: "build-gnu-x86-bzimage"
|
|
commands:
|
|
- cargo build --release --features bzimage
|
|
retry:
|
|
automatic: false
|
|
agents:
|
|
platform: x86_64.metal
|
|
plugins:
|
|
- docker#v3.0.1:
|
|
image: "rustvmm/dev:${LATEST}"
|
|
always-pull: true
|
|
```
|
|
|
|
### Custom Repository Hooks
|
|
|
|
The integration tests of some repositories have dependencies on external
|
|
resources. One example is
|
|
[`linux-loader`](https://github.com/rust-vmm/linux-loader/) which needs to
|
|
download a bzImage before running the unit tests. Because this is specific
|
|
to the `linux-loader` crate, the logic for downloading the required resources
|
|
cannot be part of the common pipeline. The mechanism used here is
|
|
[Repository Hooks](https://buildkite.com/docs/agent/v3/hooks#repository-hooks).
|
|
The hooks are defined per repository and live in the crate repository under
|
|
`.buildkite/hooks`.
|
|
|
|
Example of post-checkout hook that downloads and extracts a bzImage:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
|
|
DEB_NAME="linux-image-4.9.0-9-amd64_4.9.168-1_amd64.deb"
|
|
DEB_URL="http://ftp.debian.org/debian/pool/main/l/linux/${DEB_NAME}"
|
|
|
|
REPO_PATH="${BUILDKITE_BUILD_CHECKOUT_PATH}"
|
|
DEB_PATH="${REPO_PATH}/${DEB_NAME}"
|
|
EXTRACT_PATH="${REPO_PATH}/src/bzimage-archive"
|
|
BZIMAGE_PATH="${EXTRACT_PATH}/boot/vmlinuz-4.9.0-9-amd64"
|
|
|
|
mkdir -p ${EXTRACT_PATH}
|
|
|
|
wget ${DEB_URL} -P ${REPO_PATH}
|
|
dpkg-deb -x ${DEB_PATH} ${EXTRACT_PATH}
|
|
|
|
mv ${BZIMAGE_PATH} ${REPO_PATH}/src/bzimage
|
|
rm -r ${EXTRACT_PATH}
|
|
rm -f ${DEB_PATH}
|
|
```
|
|
|
|
In this example the post-checkout hook downloads a deb image, extracts its
|
|
contents and places it in `linux-loader/src/bzimage`. The unit tests will use
|
|
the relative path `src/bzimage` which does not depend on the image
|
|
being downloaded.
|
|
|
|
## Integration Tests
|
|
|
|
In addition to the one-liner tests defined in the
|
|
[Buildkite Pipeline](#buildkite-pipeline), the rust-vmm-ci also has more
|
|
complex tests defined in [integration_tests](integration_tests).
|
|
|
|
### Test Profiles
|
|
|
|
The integration tests support two test profiles:
|
|
|
|
- **devel**: this is the recommended profile for running the integration tests
|
|
on a local development machine.
|
|
- **ci** (default option): this is the profile used when running the
|
|
integration tests as part of the the Continuous Integration (CI).
|
|
|
|
The test profiles are applicable to
|
|
[`pytest`](https://docs.pytest.org/en/latest/), the integration test framework
|
|
used with rust-vmm-ci. Currently only the
|
|
[coverage test](tests/test_coverage.py) follows this model as all the other
|
|
integration tests are run using the Buildkite pipeline.
|
|
|
|
The difference between is declaring tests as passed or failed:
|
|
|
|
- with the **devel** profile the coverage test passes if the current coverage
|
|
is equal or higher than the upstream coverage value. In case the current
|
|
coverage is higher, the coverage file is updated to the new coverage value.
|
|
- with the **ci** profile the coverage test passes only if the current coverage
|
|
is equal to the upstream coverage value.
|
|
|
|
Further details about the coverage test can be found in the
|
|
[Adaptive Coverage](#adaptive-coverage) section.
|
|
|
|
### Adaptive Coverage
|
|
|
|
The line coverage is saved in [tests/coverage](tests/coverage). To update the
|
|
coverage before submitting a PR, run the coverage test:
|
|
|
|
```bash
|
|
crate="kvm-ioctls"
|
|
container_version=5
|
|
docker run --device=/dev/kvm \
|
|
-it \
|
|
--security-opt seccomp=unconfined \
|
|
--volume $(pwd)/${crate}:/${crate} \
|
|
rustvmm/dev:v{$container_version}
|
|
cd ${crate}
|
|
pytest --profile=devel rust-vmm-ci/integration_tests/test_coverage.py
|
|
```
|
|
|
|
If the PR coverage is higher than the upstream coverage, the coverage file
|
|
needs to be manually added to the commit before submitting the PR:
|
|
|
|
```bash
|
|
git add tests/coverage
|
|
```
|
|
|
|
Failing to do so will generate a fail on the CI pipeline when publishing the
|
|
PR.
|
|
|
|
**NOTE:** The coverage file is only updated in the `devel` test profile. In
|
|
the `ci` profile the coverage test will fail if the current coverage is higher
|
|
than the coverage reported in [tests/coverage](tests/coverage).
|
|
|
|
### Performance tests
|
|
|
|
`rust-vmm-ci` includes an integration test that can run a battery of
|
|
benchmarks at every pull request, comparing the results with the tip of the
|
|
upstream `master` branch. The test is not included in the default Buildkite
|
|
pipeline. Each crate that requires the test to be run as part of the CI must
|
|
add a [custom pipeline](#custom-pipeline).
|
|
|
|
An example of a pipeline that runs the test for ARM platforms and prints the
|
|
results:
|
|
|
|
```yaml
|
|
steps:
|
|
- label: "bench-aarch64"
|
|
commands:
|
|
- pytest rust-vmm-ci/integration_tests/test_benchmark.py -s
|
|
retry:
|
|
automatic: false
|
|
agents:
|
|
platform: arm.metal
|
|
plugins:
|
|
- docker#v3.0.1:
|
|
image: "rustvmm/dev:v${LATEST}"
|
|
always-pull: true
|
|
```
|
|
|
|
The test requires [`criterion`](https://github.com/bheisler/criterion.rs)
|
|
benchmarks to be exported by the crate. The test expects the entry point
|
|
into the performance benchmarks to be named `main`. In other words, the
|
|
following configuration is expected in `Cargo.toml`:
|
|
|
|
```toml
|
|
[[bench]]
|
|
name = "main"
|
|
```
|
|
|
|
All benchmarks need to be collected in a main.rs file placed in `benches/`.
|
|
|
|
`criterion` collects performance results by running a function for a
|
|
user-configured number of iterations, timing the runs, and applying statistics.
|
|
The individual benchmark tests must be added in the crate. They can be run
|
|
outside the CI with:
|
|
|
|
```bash
|
|
cargo bench [--all-features] OR [--features <features>]
|
|
```
|
|
|
|
`rust-vmm-ci` uses [`critcmp`](https://github.com/BurntSushi/critcmp) to
|
|
compare the results yielded by `cargo bench --all-features` on the PR being
|
|
tested with those from the tip of the upstream `master` branch. The test
|
|
runs `cargo bench` twice, once on the current `HEAD`, then again after
|
|
`git checkout origin/master`. `critcmp` takes care of the comparison, making
|
|
use of `criterion`'s stable format for
|
|
[output files](https://bheisler.github.io/criterion.rs/book/user_guide/csv_output.html).
|
|
The results are printed to `stdout` and can be visually inspected in the
|
|
pipeline output. In its present form, the test cannot fail.
|
|
|
|
To run the test locally:
|
|
|
|
```bash
|
|
docker run --device=/dev/kvm \
|
|
-it \
|
|
--security-opt seccomp=unconfined \
|
|
--volume $(pwd)/${CRATE}:/${CRATE} \
|
|
rustvmm/dev:v${LATEST}
|
|
cd ${CRATE}
|
|
pytest rust-vmm-ci/integration_tests/test_benchmark.py -s
|
|
```
|
|
|
|
Note that performance is highly dependent on the underlying platform that the
|
|
tests are running on. The raw numbers obtained are likely to differ from their
|
|
counterparts on a CI instance.
|