Skip to content

Commit

Permalink
test: add cross-arch build/boot test
Browse files Browse the repository at this point in the history
  • Loading branch information
mvo5 committed Jan 30, 2024
1 parent c33db74 commit 8d045bc
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- name: Install test dependencies
run: |
sudo apt update
sudo apt install -y podman python3-pytest python3-paramiko python3-boto3 flake8 qemu-system-x86
sudo apt install -y podman python3-pytest python3-paramiko python3-boto3 flake8 qemu-efi-aarch64 qemu-system-x86 qemu-system-arm
- name: Run tests
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
Expand Down
1 change: 1 addition & 0 deletions plans/all.fmf
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ provision:
prepare:
how: install
package:
- edk2-aarch64
- podman
- pytest
- python3-boto3
Expand Down
17 changes: 14 additions & 3 deletions test/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
class ImageBuildResult(NamedTuple):
img_type: str
img_path: str
img_arch: str
username: str
password: str
journal_output: str
Expand All @@ -44,7 +45,13 @@ def image_type_fixture(tmpdir_factory, build_container, request, force_aws_uploa
ImageBuildResult with the resulting image path and user/password
"""
# image_type is passed via special pytest parameter fixture
container_ref, image_type = request.param.split(",")
if request.param.count(",") == 2:
container_ref, image_type, target_arch = request.param.split(",")
elif request.param.count(",") == 1:
container_ref, image_type = request.param.split(",")
target_arch = None
else:
raise ValueError(f"cannot parse {request.param.count}")

username = "test"
password = "password"
Expand Down Expand Up @@ -90,6 +97,9 @@ def image_type_fixture(tmpdir_factory, build_container, request, force_aws_uploa

upload_args = []
creds_args = []
target_arch_args = []
if target_arch:
target_arch_args = ["--target-arch", target_arch]

with tempfile.TemporaryDirectory() as tempdir:
if image_type == "ami":
Expand Down Expand Up @@ -120,6 +130,7 @@ def image_type_fixture(tmpdir_factory, build_container, request, force_aws_uploa
"--config", "/output/config.json",
"--type", image_type,
*upload_args,
*target_arch_args,
])
journal_output = testutil.journal_after_cursor(cursor)
metadata = {}
Expand All @@ -132,7 +143,7 @@ def del_ami():

journal_log_path.write_text(journal_output, encoding="utf8")

return ImageBuildResult(image_type, generated_img, username, password, journal_output, metadata)
return ImageBuildResult(image_type, generated_img, target_arch, username, password, journal_output, metadata)


def test_container_builds(build_container):
Expand All @@ -150,7 +161,7 @@ def test_image_is_generated(image_type):
@pytest.mark.skipif(platform.system() != "Linux", reason="boot test only runs on linux right now")
@pytest.mark.parametrize("image_type", gen_testcases("direct-boot"), indirect=["image_type"])
def test_image_boots(image_type):
with QEMU(image_type.img_path) as test_vm:
with QEMU(image_type.img_path, arch=image_type.img_arch) as test_vm:
exit_status, _ = test_vm.run("true", user=image_type.username, password=image_type.password)
assert exit_status == 0
exit_status, output = test_vm.run("echo hello", user=image_type.username, password=image_type.password)
Expand Down
9 changes: 9 additions & 0 deletions test/testcases.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import platform
import os


Expand Down Expand Up @@ -43,6 +44,14 @@ def gen_testcases(what):
CONTAINERS_TO_TEST["centos"] + "," + DIRECT_BOOT_IMAGE_TYPES[2],
CONTAINERS_TO_TEST["fedora"] + "," + DIRECT_BOOT_IMAGE_TYPES[0],
]
# do a cross arch test too
if platform.machine() == "x86_64":
# todo: add fedora:eln
test_cases.append(
f'{CONTAINERS_TO_TEST["centos"]},raw,arm64')
elif platform.machine() == "arm64":
# TODO: add arm64->x86_64 cross build test too
pass
return test_cases
elif what == "all":
test_cases = []
Expand Down
55 changes: 37 additions & 18 deletions test/vm.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import abc
import os
import pathlib
import platform
import subprocess
import sys
import time
Expand Down Expand Up @@ -93,33 +94,46 @@ def find_ovmf():

class QEMU(VM):
MEM = "2000"
# TODO: support qemu-system-aarch64 too :)
QEMU = "qemu-system-x86_64"

def __init__(self, img, snapshot=True, cdrom=None):
def __init__(self, img, arch="", snapshot=True, cdrom=None):
super().__init__()
self._img = pathlib.Path(img)
self._qmp_socket = self._img.with_suffix(".qemp-socket")
self._qemu_p = None
self._snapshot = snapshot
self._cdrom = cdrom
self._ssh_port = None
if not arch:
arch = platform.machine()
self._arch = arch

def __del__(self):
self.force_stop()

# XXX: move args to init() so that __enter__ can use them?
def start(self, wait_event="ssh", snapshot=True, use_ovmf=False):
if self.running():
return
log_path = self._img.with_suffix(".serial-log")
self._ssh_port = get_free_port()
self._address = "localhost"
qemu_cmdline = [
self.QEMU, "-enable-kvm",
def _gen_qemu_cmdline(self, snapshot, use_ovmf):
if self._arch in ("arm64", "aarch64"):
qemu_cmdline = [
"qemu-system-aarch64",
"-machine", "virt",
"-cpu", "cortex-a57",
"-smp", "2",
"-bios", "/usr/share/AAVMF/AAVMF_CODE.fd",
]
elif self._arch in ("amd64", "x86_64"):
qemu_cmdline = [
"qemu-system-x86_64",
"-M", "accel=kvm",
# get "illegal instruction" inside the VM otherwise
"-cpu", "host",
]
if use_ovmf:
qemu_cmdline.extend(["-bios", find_ovmf()])
else:
raise ValueError(f"unsupported architecture {self._arch}")

# common part
qemu_cmdline += [
"-m", self.MEM,
# get "illegal instruction" inside the VM otherwise
"-cpu", "host",
"-serial", "stdio",
"-monitor", "none",
"-netdev", f"user,id=net.0,hostfwd=tcp::{self._ssh_port}-:22",
Expand All @@ -128,18 +142,23 @@ def start(self, wait_event="ssh", snapshot=True, use_ovmf=False):
]
if not os.environ.get("OSBUILD_TEST_QEMU_GUI"):
qemu_cmdline.append("-nographic")
if use_ovmf:
qemu_cmdline.extend(["-bios", find_ovmf()])
if self._cdrom:
qemu_cmdline.extend(["-cdrom", self._cdrom])
if snapshot:
qemu_cmdline.append("-snapshot")
qemu_cmdline.append(self._img)
self._log(f"vm starting, log available at {log_path}")
return qemu_cmdline

# XXX: move args to init() so that __enter__ can use them?
def start(self, wait_event="ssh", snapshot=True, use_ovmf=False):
if self.running():
return
self._ssh_port = get_free_port()
self._address = "localhost"

# XXX: use systemd-run to ensure cleanup?
self._qemu_p = subprocess.Popen(
qemu_cmdline,
self._gen_qemu_cmdline(snapshot, use_ovmf),
stdout=sys.stdout,
stderr=sys.stderr,
)
Expand Down

0 comments on commit 8d045bc

Please sign in to comment.