Skip to content
Open
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
3 changes: 0 additions & 3 deletions .ci/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ set -euo pipefail
MACHINE_TYPE="$(uname -m)"
OS_TYPE="$(uname -s)"

# Enable SDL headless mode explicitly.
export SDL_VIDEODRIVER=offscreen

# Cleanup function - kills all semu processes
cleanup() {
sleep 1
Expand Down
34 changes: 22 additions & 12 deletions .ci/publish-prebuilt.sh
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
#!/usr/bin/env bash
#
# Compress the prebuilt Image and rootfs.cpio in cwd, write a sha1
# manifest, hash the input files that define the prebuilt's contents,
# and print all three sums in KEY=VAL form on stdout so callers can
# splice them into release notes, GITHUB_OUTPUT, or whatever else.
# Compress the prebuilt Image, rootfs.cpio, and test-tools.img in cwd, write a
# sha1 manifest, hash the input files that define the prebuilt's contents, and
# print all four sums in KEY=VAL form on stdout so callers can splice them into
# release notes, GITHUB_OUTPUT, or whatever else.
#
# Inputs (in cwd):
# Image
# rootfs.cpio
# test-tools.img
# plus the source inputs listed in INPUTS below (config + scripts +
# target/init that define the buildroot/kernel content)
# target files that define the prebuilt content)
#
# Outputs (in cwd):
# Image.bz2
# rootfs.cpio.bz2
# prebuilt.sha1 -- three-line manifest in sha1sum format. The
# first two lines verify the published archives;
# the third uses the virtual name 'inputs' to
# test-tools.img.bz2
# prebuilt.sha1 -- four-line manifest in sha1sum format. The
# first three lines verify the published archives;
# the fourth uses the virtual name 'inputs' to
# publish the SHA-1 of the concatenated input
# files so drift-detection consumers can read it
# directly from the release.
#
# Stdout (machine-readable, one assignment per line):
# kernel_sha1=<sha1 of Image.bz2>
# initrd_sha1=<sha1 of rootfs.cpio.bz2>
# test_tools_sha1=<sha1 of test-tools.img.bz2>
# inputs_sha1=<sha1 of the concatenated input files>

set -euo pipefail
Expand All @@ -45,35 +48,41 @@ INPUTS=(
configs/linux.config
configs/busybox.config
configs/buildroot.config
configs/x11.config
configs/riscv-cross-file
scripts/build-image.sh
scripts/rootfs_ext4.sh
target/init
target/local-env.sh
)

for f in Image rootfs.cpio "${INPUTS[@]}"; do
for f in Image rootfs.cpio test-tools.img "${INPUTS[@]}"; do
if [ ! -f "$f" ]; then
echo "[!] Missing $f -- run scripts/build-image.sh --all first" >&2
echo "[!] Missing $f -- run scripts/build-image.sh --all --x11 --directfb2-test first" >&2
exit 1
fi
done

bzip2 -k -f Image
bzip2 -k -f rootfs.cpio
bzip2 -k -f test-tools.img

KERNEL_SHA1=$("${SHA1[@]}" Image.bz2 | awk '{print $1}')
INITRD_SHA1=$("${SHA1[@]}" rootfs.cpio.bz2 | awk '{print $1}')
TEST_TOOLS_SHA1=$("${SHA1[@]}" test-tools.img.bz2 | awk '{print $1}')
# Concatenate inputs in deterministic order and hash the stream. Matches
# the make-time computation in mk/external.mk so they compare directly.
INPUTS_SHA1=$(cat "${INPUTS[@]}" | "${SHA1[@]}" | awk '{print $1}')

# Write the manifest. The first two lines match 'sha1sum -c' format for
# the real archives; the third line uses the virtual filename 'inputs'
# Write the manifest. The first three lines match 'sha1sum -c' format for
# the real archives; the fourth line uses the virtual filename 'inputs'
# to publish the input-fingerprint hash so consumers (mk/external.mk's
# drift warning, .github/workflows/main.yml's PR drift detection) can
# read it from the release without parsing the release-body markdown.
{
echo "$KERNEL_SHA1 Image.bz2"
echo "$INITRD_SHA1 rootfs.cpio.bz2"
echo "$TEST_TOOLS_SHA1 test-tools.img.bz2"
echo "$INPUTS_SHA1 inputs"
} > prebuilt.sha1

Expand All @@ -86,4 +95,5 @@ INPUTS_SHA1=$(cat "${INPUTS[@]}" | "${SHA1[@]}" | awk '{print $1}')

echo "kernel_sha1=$KERNEL_SHA1"
echo "initrd_sha1=$INITRD_SHA1"
echo "test_tools_sha1=$TEST_TOOLS_SHA1"
echo "inputs_sha1=$INPUTS_SHA1"
185 changes: 185 additions & 0 deletions .ci/test-gpu.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/env bash

set -euo pipefail
Comment thread
Mes0903 marked this conversation as resolved.

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "${SCRIPT_DIR}/common.sh"

# Override timeout and sleep duration for macOS - emulation is significantly slower
case "${OS_TYPE}" in
Darwin)
TIMEOUT=10800
DFB_SLEEP=180
;;
*)
DFB_SLEEP=5
;;
esac
export DFB_SLEEP
SEMU_DIRECTFB2_TEST="${SEMU_DIRECTFB2_TEST:-1}"
export SEMU_DIRECTFB2_TEST
MAKE_CHECK_DISKIMG_ARG=""

cleanup
trap cleanup EXIT

# Feature toggles are passed through environment variables, which do not
# participate in normal dependency tracking by 'make'. Force a rebuild here so
# one-feature-at-a-time test runs never reuse a stale 'semu' binary or DTB.
make -B semu minimal.dtb

if [ ! -f Image ] || [ ! -f rootfs.cpio ]; then
make Image rootfs.cpio
fi
if [[ "${SEMU_DIRECTFB2_TEST}" == "1" ]]; then
# The default ext4.img is intentionally small. DirectFB2 lives in the
# optional test tools disk, which is supplied by PR-built artifacts or downloaded
# like the other prebuilt artifacts.
if [ ! -f test-tools.img ]; then
make test-tools.img
fi
MAKE_CHECK_DISKIMG_ARG="DISKIMG_FILE=test-tools.img"
elif [ ! -f ext4.img ]; then
make ext4.img
fi
export MAKE_CHECK_DISKIMG_ARG

# NOTE: We want to capture the 'expect' exit code and map
# it to our 'MESSAGES' array for meaningful error output.
# Temporarily disable 'errexit' for the 'expect' call.
set +e
expect <<'DONE'
set timeout $env(TIMEOUT)
if {$env(MAKE_CHECK_DISKIMG_ARG) eq ""} {
spawn make check
} else {
spawn make check $env(MAKE_CHECK_DISKIMG_ARG)
}

# Boot and login
expect "buildroot login:" { send "root\r" } timeout { exit 1 }
expect "# " { send "uname -a\r" } timeout { exit 2 }
expect "riscv32 GNU/Linux" {}

# ---------------- virtio-gpu basic checks ----------------
expect "# " { send "ls -la /dev/dri/ 2>/dev/null || true\r" }
# Emit a shell-expanded status marker so 'expect' cannot match the echoed command.
expect "# " { send "if test -c /dev/dri/card0; then status=OK; else status=MISSING; fi; printf \"__VGPU_DRM_%s__\\n\" \"\$status\"\r" } timeout { exit 3 }
expect {
-exact "__VGPU_DRM_OK__" {}
-exact "__VGPU_DRM_MISSING__" { exit 3 }
timeout { exit 3 }
}

# virtio transport may be 'virtio-mmio', binding check should look at the
# 'virtio_gpu' driver directory.
expect "# " {
send "sh -lc 'if ls /sys/bus/virtio/drivers/virtio_gpu/virtio* >/dev/null 2>&1; then status=OK; else status=BAD; fi; printf \"__VGPU_BIND_%s__\\n\" \"\u0024status\"'\r"
} timeout { exit 3 }
expect {
-exact "__VGPU_BIND_OK__" {}
-exact "__VGPU_BIND_BAD__" {
send "ls -l /sys/bus/virtio/drivers/virtio_gpu/ 2>/dev/null || true\r"
# Emit literal '$d' via '\u0024' to avoid Tcl variable substitution.
send "sh -lc 'for d in /sys/bus/virtio/devices/virtio*; do echo \u0024d; ls -l \u0024d/driver 2>/dev/null || true; done'\r"
exit 3
}
timeout { exit 3 }
}

# Useful logs (non-fatal)
expect "# " { send "dmesg | grep -Ei 'virtio.*gpu|drm.*virtio|scanout|number of scanouts' | tail -n 80 || true\r" }

if {$env(SEMU_DIRECTFB2_TEST) ne "1"} {
exit 0
}

# ---------------- DirectFB2 ----------------
# Strategy:
# 1) Stop X11 if running (it holds the DRM device)
# 2) Check 'local-env.sh' exists at '/root/local-env.sh'
# 3) Source 'local-env.sh' to set 'PATH'/'LD_LIBRARY_PATH'
# 4) Verify 'df_drivertest' is in 'PATH'
# 5) Run 'df_drivertest' and check for DirectFB init messages
#
# NOTE: 'df_drivertest' may segfault when killed due to a race condition in
# DirectFB2's fusion module ('libfusion') during signal handling. When 'SIGTERM'
# is sent, the signal handler starts cleanup while the "Fusion Dispatch" thread
# may still be accessing shared state, leading to a use-after-free crash. The
# test passes if DirectFB init messages appear, even if the program crashes
# afterward during cleanup.

# Step 0: Stop X11 to release DRM device (it holds '/dev/dri/card0')
# Use 'pidof' with fallback to 'ps'/'grep' if 'pidof' is unavailable.
expect "# " {
send "sh -lc '\
if command -v pidof >/dev/null 2>&1; then \
pidof Xorg >/dev/null 2>&1 && kill \u0024(pidof Xorg) 2>/dev/null || true; \
else \
ps | grep Xorg | grep -v grep | awk \"{print \u00241}\" | xargs kill 2>/dev/null || true; \
fi; \
sleep 1; printf \"__X11_%s__\\n\" STOPPED'\r"
}
expect {
-exact "__X11_STOPPED__" {}
timeout { exit 4 }
}

# Step 1: Check 'local-env.sh' exists.
expect "# " { send "if test -f /root/local-env.sh; then status=OK; else status=MISSING; fi; printf \"__LOCALENV_%s__\\n\" \"\$status\"\r" }
expect {
-exact "__LOCALENV_OK__" {}
-exact "__LOCALENV_MISSING__" { exit 4 }
timeout { exit 4 }
}

# Step 2: Source 'local-env.sh'.
expect "# " { send "if . /root/local-env.sh >/dev/null 2>&1; then status=DONE; else status=FAIL; fi; printf \"__SRC_%s__\\n\" \"\$status\"\r" }
expect {
-exact "__SRC_DONE__" {}
-exact "__SRC_FAIL__" { exit 4 }
timeout { exit 4 }
}

# Step 3: Verify 'df_drivertest' is available.
expect "# " { send "if command -v df_drivertest >/dev/null 2>&1; then status=OK; else status=MISS; fi; printf \"__APP_%s__\\n\" \"\$status\"\r" }
expect {
-exact "__APP_OK__" {}
-exact "__APP_MISS__" { exit 4 }
timeout { exit 4 }
}

# Step 4: Run 'df_drivertest' and check output (run in background, kill after
# delay).
expect "# " { send "df_drivertest >/tmp/dfb.log 2>&1 & sleep $env(DFB_SLEEP); kill \u0024! 2>/dev/null; head -30 /tmp/dfb.log\r" }
# Check for 'DRMKMS' init message.
expect "# " { send "if grep -qi 'DRMKMS/System' /tmp/dfb.log; then status=OK; else status=FAIL; fi; printf \"__DFB_%s__\\n\" \"\$status\"\r" }
expect {
-exact "__DFB_OK__" {}
-exact "__DFB_FAIL__" { exit 4 }
timeout { exit 4 }
}
DONE

ret="$?"
set -e # Re-enable 'errexit' after capturing 'expect' return code.

if [[ "${ret}" -eq 0 ]]; then
if [[ "${SEMU_DIRECTFB2_TEST}" == "1" ]]; then
print_success "PASS: headless virtio-gpu + DirectFB2 checks"
else
print_success "PASS: headless virtio-gpu checks"
fi
exit 0
fi

MESSAGES=(
"unused"
"FAIL: boot/login prompt not found"
"FAIL: shell prompt not found"
"FAIL: virtio-gpu basic checks failed (/dev/dri/card0 or virtio_gpu binding)"
"FAIL: DirectFB2 check failed (local-env.sh/df_drivertest missing or no DRMKMS init messages)"
)

print_error "${MESSAGES[${ret}]:-FAIL: unknown error (exit code ${ret})}"
exit "${ret}"
Loading
Loading