Skip to content

Commit 261bf59

Browse files
Merge pull request #6 from EmbeddedAndroid/qemu-runtime-tests
ci: qemu runtime tests for vscode-weston-launcher
2 parents 24ee97c + b23b734 commit 261bf59

2 files changed

Lines changed: 303 additions & 0 deletions

File tree

.github/workflows/qemu-runtime.yml

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
name: qemu-runtime
2+
3+
# Builds a Weston desktop image with VSCode + the launcher pulled in,
4+
# boots it under QEMU via Yocto's testimage class, and runs OEQA test
5+
# cases (in lib/oeqa/runtime/cases/vscode_launcher.py) that verify:
6+
# - the postinst added the launcher block to weston.ini
7+
# - the VSCode binary is on the target and `--version` works
8+
# - Weston actually started on boot
9+
# - VSCode can connect to the live wayland-0 socket and stay alive
10+
#
11+
# This is heavy CI (full Yocto image build, ~1-3 hours from cold
12+
# sstate) so it runs weekly + on manual dispatch rather than per-PR.
13+
14+
on:
15+
schedule:
16+
# 03:17 UTC Sunday. Off-peak; small offset so cdn.kernel.org +
17+
# mirrors aren't slammed alongside everyone else's nightly.
18+
- cron: '17 3 * * 0'
19+
workflow_dispatch:
20+
21+
# Don't pile up qemu runs if one is already in progress.
22+
concurrency:
23+
group: ${{ github.workflow }}
24+
cancel-in-progress: false
25+
26+
jobs:
27+
testimage:
28+
name: testimage (scarthgap / qemux86-64)
29+
runs-on: ubuntu-22.04
30+
timeout-minutes: 360
31+
steps:
32+
- uses: actions/checkout@v4
33+
with:
34+
path: meta-vscode
35+
36+
- name: Free disk space
37+
# Yocto builds need ~25-30 GB; the default GHA runner has ~14
38+
# GB free on /. Reclaim ~30 GB by purging things we don't use.
39+
run: |
40+
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
41+
/opt/hostedtoolcache/CodeQL /usr/local/share/boost \
42+
"$AGENT_TOOLSDIRECTORY"
43+
sudo apt-get autoremove --purge -y \
44+
azure-cli google-chrome-stable firefox powershell mongodb-* \
45+
mysql-* postgresql-* || true
46+
df -h
47+
48+
- name: Install Yocto host + qemu deps
49+
run: |
50+
sudo apt-get update
51+
sudo apt-get install -y --no-install-recommends \
52+
gawk wget git diffstat unzip texinfo gcc build-essential chrpath \
53+
socat cpio python3 python3-pip python3-pexpect xz-utils debianutils \
54+
iputils-ping python3-git python3-jinja2 python3-subunit zstd liblz4-tool \
55+
file locales libacl1 \
56+
qemu-system-x86 qemu-utils
57+
sudo locale-gen en_US.UTF-8
58+
# KVM is the difference between qemu booting in seconds vs
59+
# minutes. GHA hosted runners ship /dev/kvm; just make the
60+
# runner user a member of the kvm group.
61+
sudo usermod -aG kvm "$USER"
62+
ls -la /dev/kvm || true
63+
64+
- name: Cache poky checkout
65+
uses: actions/cache@v4
66+
with:
67+
path: poky
68+
key: poky-scarthgap-runtime-v1
69+
restore-keys: poky-scarthgap-
70+
71+
- name: Clone poky (scarthgap)
72+
run: |
73+
if [ ! -d poky/.git ]; then
74+
git clone --depth 1 --branch scarthgap \
75+
https://git.yoctoproject.org/poky poky
76+
else
77+
git -C poky fetch --depth 1 origin scarthgap:refs/remotes/origin/scarthgap
78+
git -C poky checkout -B scarthgap refs/remotes/origin/scarthgap
79+
fi
80+
81+
- name: Cache sstate
82+
# GHA caches cap at 10 GB. Yocto sstate for a full Weston
83+
# image is much larger than that, so we can't cache it whole.
84+
# Cache what fits and let cold cells rebuild.
85+
uses: actions/cache@v4
86+
with:
87+
path: sstate-cache
88+
key: sstate-scarthgap-weston-${{ hashFiles('meta-vscode/recipes-devtools/vscode/*.bb') }}
89+
restore-keys: |
90+
sstate-scarthgap-weston-
91+
sstate-scarthgap-
92+
93+
- name: Cache downloads
94+
uses: actions/cache@v4
95+
with:
96+
path: downloads
97+
key: downloads-scarthgap-weston-${{ hashFiles('meta-vscode/recipes-devtools/vscode/*.bb') }}
98+
restore-keys: |
99+
downloads-scarthgap-weston-
100+
downloads-scarthgap-
101+
102+
- name: Configure build
103+
run: |
104+
set -eo pipefail
105+
cd poky
106+
source oe-init-build-env build
107+
cat > conf/local.conf <<'EOF'
108+
MACHINE = "qemux86-64"
109+
DISTRO = "poky"
110+
BB_NUMBER_THREADS = "2"
111+
PARALLEL_MAKE = "-j 2"
112+
CONF_VERSION = "2"
113+
PACKAGE_CLASSES ?= "package_rpm"
114+
SSTATE_DIR ?= "${TOPDIR}/../../sstate-cache"
115+
DL_DIR ?= "${TOPDIR}/../../downloads"
116+
117+
# Weston + ssh + VSCode + the launcher into the image.
118+
IMAGE_INSTALL:append = " vscode vscode-weston-launcher wayland-utils"
119+
IMAGE_FEATURES:append = " ssh-server-openssh debug-tweaks"
120+
121+
# Wire up testimage. Empty root password (debug-tweaks) lets
122+
# OEQA ssh in as root with no key dance.
123+
INHERIT += "testimage"
124+
TEST_TARGET = "qemu"
125+
TEST_SUITES = "ping ssh vscode_launcher"
126+
TESTIMAGE_AUTO = "1"
127+
128+
# Headless qemu boot.
129+
QB_GRAPHICS = "-vga none -nographic"
130+
EOF
131+
bitbake-layers add-layer "$GITHUB_WORKSPACE/meta-vscode"
132+
bitbake-layers show-layers
133+
134+
- name: Build core-image-weston (with testimage auto-run)
135+
env:
136+
LANG: en_US.UTF-8
137+
LC_ALL: en_US.UTF-8
138+
run: |
139+
set -eo pipefail
140+
cd poky
141+
source oe-init-build-env build
142+
# TESTIMAGE_AUTO=1 means do_testimage fires at the end of
143+
# build automatically, so this single bitbake invocation
144+
# both builds the image AND runs the tests.
145+
bitbake core-image-weston
146+
147+
- name: Surface test results
148+
if: always()
149+
run: |
150+
# OEQA's testimage emits a JUnit-style report under
151+
# build/tmp/log/oeqa/. Surface the most recent one so the
152+
# GHA log makes the failure obvious.
153+
find poky/build/tmp/log/oeqa -name '*.xml' -newer poky/build/conf/local.conf \
154+
-exec echo "==== {} ====" \; -exec cat {} \; 2>/dev/null || true
155+
# And any captured console output.
156+
find poky/build/tmp/log -name 'qemu_boot_log*' \
157+
-exec echo "==== {} ====" \; -exec tail -120 {} \; 2>/dev/null || true
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# OEQA runtime tests for vscode + vscode-weston-launcher.
2+
#
3+
# These are picked up by the `testimage` bbclass when this layer is
4+
# enabled and TEST_SUITES contains "vscode_launcher". They run against
5+
# a booted qemu image via ssh.
6+
7+
import time
8+
9+
from oeqa.runtime.case import OERuntimeTestCase
10+
from oeqa.core.decorator.depends import OETestDepends
11+
12+
13+
class VSCodeLauncherInstallTest(OERuntimeTestCase):
14+
"""Validate the vscode-weston-launcher postinst actually ran on the
15+
target and the VSCode binary itself is in place."""
16+
17+
def test_weston_ini_has_launcher(self):
18+
# The postinst writes a sentinel-delimited [launcher] block
19+
# into weston.ini. If it isn't there the installer ran on a
20+
# target that didn't have /etc/xdg/weston/weston.ini yet (e.g.
21+
# weston-init wasn't installed before vscode-weston-launcher),
22+
# or the postinst failed silently.
23+
status, out = self.target.run(
24+
"grep meta-vscode-launcher-begin /etc/xdg/weston/weston.ini")
25+
self.assertEqual(
26+
status, 0,
27+
"vscode-weston-launcher postinst didn't insert its block "
28+
"into /etc/xdg/weston/weston.ini.\nOutput: %s" % out)
29+
30+
def test_vscode_binary_is_executable(self):
31+
status, out = self.target.run(
32+
"test -x /usr/share/vscode/bin/code "
33+
"&& test -f /usr/share/vscode/resources/app/resources/linux/code.png")
34+
self.assertEqual(
35+
status, 0,
36+
"/usr/share/vscode/bin/code or the launcher icon are not "
37+
"present on the target.\nOutput: %s" % out)
38+
39+
@OETestDepends([
40+
'vscode_launcher.VSCodeLauncherInstallTest.test_vscode_binary_is_executable',
41+
])
42+
def test_vscode_cli_version(self):
43+
# CLI mode doesn't initialise Electron; it just dlopens enough
44+
# of node to print the version triplet:
45+
#
46+
# 1.120.0
47+
# 0958016b2af9f09bb4257e0df4a95e2f90590f9f
48+
# x64
49+
#
50+
# This proves the binary's glibc / libstdc++ / libnss links
51+
# are all resolved on the target.
52+
status, out = self.target.run("/usr/share/vscode/bin/code --version")
53+
self.assertEqual(status, 0,
54+
"code --version exit=%d: %s" % (status, out))
55+
lines = out.strip().splitlines()
56+
self.assertGreaterEqual(
57+
len(lines), 2,
58+
"Expected at least 2 lines from --version, got: %r" % out)
59+
60+
61+
class WestonStartedTest(OERuntimeTestCase):
62+
"""Validate weston-init started Weston on boot."""
63+
64+
def test_weston_process_running(self):
65+
# weston-init is socket-activated via systemd and brings weston
66+
# up shortly after boot. Give it 30s to settle.
67+
deadline = time.time() + 30
68+
while time.time() < deadline:
69+
status, _ = self.target.run("pgrep -x weston")
70+
if status == 0:
71+
return
72+
time.sleep(1)
73+
self.fail("Weston did not start within 30s of boot")
74+
75+
@OETestDepends([
76+
'vscode_launcher.WestonStartedTest.test_weston_process_running',
77+
])
78+
def test_wayland_socket_present(self):
79+
# XDG_RUNTIME_DIR varies (root vs the weston user); look in
80+
# both well-known locations.
81+
status, out = self.target.run(
82+
"for d in /run/user/0 /run/user/1000; do "
83+
" [ -S \"$d/wayland-0\" ] && echo \"$d\" && exit 0; "
84+
"done; exit 1")
85+
self.assertEqual(status, 0,
86+
"No wayland-0 socket found on the target")
87+
88+
89+
class VSCodeWaylandLaunchTest(OERuntimeTestCase):
90+
"""Validate VSCode can talk to Weston: launch the Electron app
91+
against the live Wayland socket, give it a few seconds to settle,
92+
and verify the process is still alive and has the wayland socket
93+
open."""
94+
95+
@OETestDepends([
96+
'vscode_launcher.VSCodeLauncherInstallTest.test_vscode_cli_version',
97+
'vscode_launcher.WestonStartedTest.test_wayland_socket_present',
98+
])
99+
def test_vscode_starts_on_wayland(self):
100+
# Find weston's XDG_RUNTIME_DIR.
101+
status, runtime_dir = self.target.run(
102+
"for d in /run/user/0 /run/user/1000; do "
103+
" [ -S \"$d/wayland-0\" ] && echo \"$d\" && exit 0; "
104+
"done")
105+
self.assertEqual(status, 0)
106+
runtime_dir = runtime_dir.strip()
107+
self.assertTrue(runtime_dir, "no wayland-0 socket found")
108+
109+
# Launch VSCode pointed at the Wayland socket. --no-sandbox
110+
# because the chromium sandbox needs CAP_SYS_ADMIN which the
111+
# test rootfs doesn't grant. user-data-dir and extensions-dir
112+
# under /tmp so the launch doesn't try to write into
113+
# ~/.config / ~/.vscode and trip permission errors.
114+
launch_cmd = (
115+
"rm -rf /tmp/vscode-test* && "
116+
"env WAYLAND_DISPLAY=wayland-0 "
117+
" XDG_RUNTIME_DIR=%s "
118+
"/usr/share/vscode/bin/code "
119+
" --no-sandbox "
120+
" --user-data-dir=/tmp/vscode-test "
121+
" --extensions-dir=/tmp/vscode-test-ext "
122+
" >/tmp/vscode.log 2>&1 &" % runtime_dir)
123+
self.target.run(launch_cmd)
124+
125+
# Electron/VSCode takes 10-15s to fully spin up under qemu.
126+
time.sleep(20)
127+
128+
# Process still running?
129+
status, _ = self.target.run("pgrep -f 'vscode-test'")
130+
self.assertEqual(
131+
status, 0,
132+
"VSCode died within 20s of launch. Last log lines:\n%s"
133+
% self.target.run("tail -40 /tmp/vscode.log")[1])
134+
135+
# Has the wayland-0 socket open in its fd table?
136+
status, fd_list = self.target.run(
137+
"for pid in $(pgrep -f vscode-test); do "
138+
" ls -l /proc/$pid/fd 2>/dev/null | grep -F wayland-0 && exit 0; "
139+
"done; exit 1")
140+
self.assertEqual(
141+
status, 0,
142+
"No VSCode process has wayland-0 open. fd dump:\n%s"
143+
% fd_list)
144+
145+
# Tidy up so subsequent test runs don't trip over each other.
146+
self.target.run("pkill -9 -f vscode-test || true")

0 commit comments

Comments
 (0)