Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# The checks of the Makefile; the QEMU boots, which need KVM and apt packages, in
# a job of their own.
name: ci

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

# PRs: a new push cancels the previous run. main: one group per run, as a shared
# group drops pending runs.
concurrency:
group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.ref }}
cancel-in-progress: true

jobs:
lint-and-test:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- run: make setup

# After setup: the key covers installed toolchains.
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2

# Every check but the boots.
- run: make -k lint test

test-e2e-qemu:
runs-on: ubuntu-24.04
timeout-minutes: 120
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- run: |
sudo apt-get update
xargs --arg-file=tests/qemu/ubuntu-packages sudo apt-get install --yes

# Opens /dev/kvm to the runner user, per
# https://github.blog/changelog/2024-04-02-github-actions-hardware-accelerated-android-virtualization-now-available/
- run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' |
sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

# No rust-cache: its cleanup takes run.sh's initramfs/tests for a cargo
# profile and logs errors; the builds take about a minute without it.
- run: make setup

# Kernel RPMs and DriverSample.efi. Saved only on success, as run.sh reuses
# DriverSample.efi unchecked.
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
target/qemu-test/kernel-rpms
target/qemu-test/driver-sample
key: qemu-inputs-${{ hashFiles('tests/qemu/kernel.sha256', 'tests/qemu/build-driver-sample.sh') }}

- run: make test-e2e-qemu

- if: failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: qemu-test-logs
path: |
target/qemu-test/*/*/*.log
target/qemu-test/*/executables
if-no-files-found: ignore
retention-days: 14
35 changes: 20 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# The checks of uefisettings. They need GNU make and bash, rustup while
# RUST_TOOLCHAIN is set, as it is by default, and for qemu an x86_64 host with
# /dev/kvm and what tests/qemu/README.md lists:
# RUST_TOOLCHAIN is set, as it is by default, and for test-e2e-qemu an x86_64
# host with /dev/kvm and what tests/qemu/README.md lists:
# make every check of CHECKS, stopping at the first that fails
# make -k every check of CHECKS, even after one fails
# make <check>... the named checks, e.g. make clippy-aarch64 doc
# make lint the checks of LINTS
# make lint test every check but test-e2e-qemu
# make RUST_TOOLCHAIN= the checks on the cargo of PATH
# make setup installs, through rustup, the toolchain, components and
# targets that the checks use
# make help the checks
# make -n <check> prints the commands of a check without running them. The
# checks install nothing themselves. qemu needs qemu-lint, so under make -k a
# qemu-lint failure skips the qemu boots.
# checks install nothing themselves.
#
# A CI host runs make setup && make -k under timeout, as nothing bounds the time
# that cargo takes: timeout -k 60 <seconds> bash -c 'make setup && make -k'. It
Expand Down Expand Up @@ -60,15 +61,21 @@ unexport CARGO_ENCODED_RUSTFLAGS
TARGETS := x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,x86_64-unknown-linux-musl,x86_64-unknown-uefi
comma := ,

CHECKS := fmt clippy-x86_64 clippy-aarch64 test doc qemu-lint qemu
# CI runs lint test and test-e2e-qemu: a new check joins LINTS or gets a CI job.
LINTS := fmt clippy-x86_64 clippy-aarch64 clippy-uefi doc
CHECKS := $(LINTS) test test-e2e-qemu

.PHONY: check help setup toolchain $(CHECKS)
.PHONY: check lint help setup toolchain $(CHECKS)

check: $(CHECKS)

lint: $(LINTS)

help:
@echo "checks: $(CHECKS)"
@echo "make runs every check; make <check>... runs the named ones."
@echo "make lint runs $(LINTS)."
@echo "make lint test runs every check but test-e2e-qemu."
@echo "make -k runs every check, even after one fails."
@echo "make -n <check> prints the commands of a check without running them."
@echo "make RUST_TOOLCHAIN= runs the checks on the cargo of PATH."
Expand All @@ -90,26 +97,24 @@ endif
toolchain:
@scripts/check-toolchain.sh '$(RUST_TOOLCHAIN)' '$(TARGETS)'

# Covers the workspace's QEMU test crates too.
fmt: toolchain
cargo fmt --check

clippy-x86_64 clippy-aarch64: clippy-%: toolchain
cargo clippy --locked --workspace --all-targets --all-features --target $*-unknown-linux-gnu -- -D warnings

# publish-hiidb, the one crate built for UEFI.
clippy-uefi: toolchain
cargo clippy --locked --release --target x86_64-unknown-uefi --manifest-path tests/qemu/publish-hiidb/Cargo.toml -- -D warnings

test: toolchain
scripts/assert-tests-ran.sh cargo test --locked

doc: toolchain
RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items --locked

# The checks of the QEMU test crates that boot no guest.
qemu-lint: toolchain
cargo fmt --check --manifest-path tests/qemu/guest/Cargo.toml
RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --document-private-items --locked --manifest-path tests/qemu/guest/Cargo.toml
cargo fmt --check --manifest-path tests/qemu/publish-hiidb/Cargo.toml
cargo clippy --locked --release --target x86_64-unknown-uefi --manifest-path tests/qemu/publish-hiidb/Cargo.toml -- -D warnings
RUSTDOCFLAGS='-D warnings' cargo doc --workspace --no-deps --document-private-items --locked

# Boots the scenarios of tests/qemu/scenarios.tsv under KVM, with logs in
# target/qemu-test/<UTC time>/; see tests/qemu/README.md.
qemu: qemu-lint
test-e2e-qemu: toolchain
tests/qemu/run.sh
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ mv lib.rs thrift/rust/uefisettings_spellings_db_thrift/uefisettings_spellings_db

## Checks

The `Makefile` in the root of the repository runs the checks that a change should pass: rustfmt, clippy for x86_64 and aarch64, the tests, the docs and the QEMU tests.
The `Makefile` in the root of the repository runs the checks that a change should pass: the lints (`make lint`: rustfmt, clippy for x86_64, aarch64 and UEFI, and the docs), the tests (`make test`) and the QEMU tests (`make test-e2e-qemu`).

The checks need GNU make and bash, and rustup unless `make RUST_TOOLCHAIN=` runs them on the cargo of `PATH`.

Expand All @@ -153,11 +153,14 @@ make setup # once: installs, through rustup, the Rust toolchain, compo
make # every check, stopping at the first that fails
make -k # every check, even after one fails
make <check>... # the named checks, e.g. make clippy-aarch64 doc
make lint test # every check but the QEMU tests
make RUST_TOOLCHAIN= # the checks on the cargo of PATH
make help # lists the checks
```

`make qemu`, the QEMU tests, also needs an x86_64 host with `/dev/kvm` and the packages that [`tests/qemu/README.md`](tests/qemu/README.md) lists.
`make test-e2e-qemu`, the QEMU tests, also needs an x86_64 host with `/dev/kvm` and the packages that [`tests/qemu/README.md`](tests/qemu/README.md) lists.

GitHub Actions runs the checks on every pull request and every push to main, as [`.github/workflows/checks.yml`](.github/workflows/checks.yml) describes.

## QEMU tests

Expand Down
4 changes: 2 additions & 2 deletions tests/qemu/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ machine that runs them.

## Running

`make qemu` runs the checks of the QEMU test crates and then every scenario, on the Rust
`make test-e2e-qemu` runs every scenario, on the Rust
toolchain and targets that `make setup` installs (see the Checks section of the
repository's README):

```sh
make setup # once
make qemu
make test-e2e-qemu
```

It needs an x86_64 host with `/dev/kvm` and, on Ubuntu 24.04, the packages of
Expand Down
27 changes: 18 additions & 9 deletions tests/qemu/init
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,39 @@ done
# start and exit lines begin with a newline. run.sh prints the second port from the start
# line on.
printf '\nuefisettings-qemu-test: start %s %s\n' "$test" "$filter" > /dev/ttyS1
# The health checks judge only the test: the kernel log and taint of the boot depend on
# the host, e.g. a RETBleed error on Intel hosts that hide IBRS from the guest.
read -r start < /proc/sys/kernel/random/uuid
echo "uefisettings-qemu-test: start of test $start" > /dev/kmsg
# cat, not read: busybox read reads a byte at a time, and numeric sysctls answer only a
# read at offset 0, so read would get only the first digit.
taint=$(cat /proc/sys/kernel/tainted /sys/kernel/warn_count)
"/tests/$test" --test-threads=1 ${filter:+"$filter"} > /dev/ttyS1 2>&1
test_status=$?
# The kernel is not tainted (TAINT_* in include/linux/panic.h), kept its whole log (the
# first /dev/kmsg record has sequence number 0) and logged no error: each /dev/kmsg record
# The test added no taint (TAINT_* in include/linux/panic.h) or warning (a repeated WARN
# adds no taint bit), and the kernel kept its log
# (/dev/kmsg still has the start record) and logged no error since: each /dev/kmsg record
# starts with facility << 3 | level, and the kernel's own facility is 0. /dev/kmsg keeps
# the records that a log clear hides from dmesg. A read of it returns one record, fails if
# the record does not fit and waits at the end of the log, so dd copies the records into
# a FIFO, awk stops at a record that init writes last, and then dd is killed. The shell
# opens the FIFO for dd, so awk also stops if dd cannot open /dev/kmsg.
health_status=0
{
read -r taint < /proc/sys/kernel/tainted
[ "$taint" = 0 ] || { echo "the kernel is tainted: $taint"; health_status=1; }
dd if=/dev/kmsg bs=8192 count=1 2> /dev/null | grep -q '^[0-9]*,0,' || {
echo "the first kernel log record is gone, or /dev/kmsg cannot be read"
tainted=$(cat /proc/sys/kernel/tainted /sys/kernel/warn_count)
set -- $taint
[ $# = 2 ] && [ "$tainted" = "$taint" ] || {
echo "the taint or warning count changed or is missing:" $taint, then $tainted
health_status=1
}
read -r end < /proc/sys/kernel/random/uuid
echo "uefisettings-qemu-test: end of log $end" > /dev/kmsg
mkfifo /tmp/kmsg
dd if=/dev/kmsg bs=8192 2> /dev/null > /tmp/kmsg &
awk -v end="$end" '
index($0, end) { found = 1; exit }
/^[0-3],/ { print; error = 1 }
awk -v start="$start" -v end="$end" '
index($0, end) { found = started; exit }
started && /^[0-3],/ { print; error = 1 }
index($0, start) { started = 1 }
END { if (!found) print "cannot read the kernel log"; exit !found || error }' /tmp/kmsg ||
health_status=1
kill "$!" 2> /dev/null
Expand Down
1 change: 1 addition & 0 deletions tests/qemu/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ mapfile -t named < <(awk -F '\t' '$1 !~ /^(#|$)/ {

echo "run.sh: start $(date -u +%FT%TZ), logs in $run"
mkdir -p "$run/initramfs/bin" "$run/initramfs/tests" "$run/esp"
grep --max-count=1 '^model name' /proc/cpuinfo
"$qemu" --version
"$busybox" --help | sed -n 1p

Expand Down