Skip to content

Commit 841a503

Browse files
committed
test: refactor so that all vm related tests can be reused
This commit reshuffles the code a bit so that we a reusable ./test/vmtest directory that can be used by the images library. With that we can add a toplevel pyproject.toml file so that we can import vmtest via ```console $ pip install [email protected]/osbuild/bootc-image-builder ``` im other projects. Note that none of this is ideal, butt this is (hopefully) a temporary measure until we find a more permanent home for our vm runner or replace it with something like test.thing or the new osbuild QEMU code.
1 parent a8e8ad7 commit 841a503

File tree

9 files changed

+63
-53
lines changed

9 files changed

+63
-53
lines changed

pyproject.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Note that this is pyproject file is here only for the vmtest utils.
2+
# This should move out eventually to its own repo or a different place
3+
# like "images".
4+
5+
[build-system]
6+
requires = ["setuptools>=61.0"]
7+
build-backend = "setuptools.build_meta"
8+
9+
[project]
10+
name = "vmtest"
11+
version = "0.1.0"
12+
13+
[tool.setuptools.packages.find]
14+
include = ["vmtest"]

test/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from testcases import TestCase
4+
from vmtest.util import get_free_port
45

56

67
def pytest_addoption(parser):
@@ -20,3 +21,8 @@ def pytest_make_parametrize_id(config, val): # pylint: disable=W0613
2021
if isinstance(val, TestCase):
2122
return f"{val}"
2223
return None
24+
25+
26+
@pytest.fixture(name="free_port")
27+
def free_port_fixture():
28+
return get_free_port()

test/test_build_disk.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
import testutil
1919
from containerbuild import build_container_fixture # pylint: disable=unused-import
2020
from testcases import CLOUD_BOOT_IMAGE_TYPES, DISK_IMAGE_TYPES, gen_testcases
21-
from vm import AWS, QEMU
21+
import vmtest.util
22+
from vmtest.vm import AWS_REGION, AWS, QEMU
2223

2324
if not testutil.has_executable("podman"):
2425
pytest.skip("no podman, skipping integration tests that required podman", allow_module_level=True)
@@ -113,7 +114,7 @@ def registry_conf_fixture(shared_tmpdir, request):
113114
{local_registry}:
114115
lookaside: file:///{sigstore_dir}
115116
"""
116-
registry_port = testutil.get_free_port()
117+
registry_port = vmtest.util.get_free_port()
117118
# We cannot use localhost as we need to access the registry from both
118119
# the host system and the bootc-image-builder container.
119120
default_ip = testutil.get_ip_from_default_route()
@@ -410,7 +411,7 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload, gpg_
410411

411412
upload_args = [
412413
f"--aws-ami-name=bootc-image-builder-test-{str(uuid.uuid4())}",
413-
f"--aws-region={testutil.AWS_REGION}",
414+
f"--aws-region={AWS_REGION}",
414415
"--aws-bucket=bootc-image-builder-ci",
415416
]
416417
elif force_aws_upload:
@@ -492,7 +493,7 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload, gpg_
492493
metadata["ami_id"] = parse_ami_id_from_log(journal_output)
493494

494495
def del_ami():
495-
testutil.deregister_ami(metadata["ami_id"])
496+
testutil.deregister_ami(metadata["ami_id"], AWS_REGION)
496497
request.addfinalizer(del_ami)
497498

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

test/test_build_iso.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import testutil
99
from containerbuild import build_container_fixture # pylint: disable=unused-import
1010
from testcases import gen_testcases
11-
from vm import QEMU
11+
from vmtest.vm import QEMU
1212

1313
from test_build_disk import (
1414
assert_kernel_args,

test/testutil.py

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,11 @@
22
import pathlib
33
import platform
44
import shutil
5-
import socket
65
import subprocess
7-
import time
86

97
import boto3
108
from botocore.exceptions import ClientError
119

12-
AWS_REGION = "us-east-1"
13-
1410

1511
def run_journalctl(*args):
1612
pre = []
@@ -35,28 +31,6 @@ def has_executable(name):
3531
return shutil.which(name) is not None
3632

3733

38-
def get_free_port() -> int:
39-
# this is racy but there is no race-free way to do better with the qemu CLI
40-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
41-
s.bind(("localhost", 0))
42-
return s.getsockname()[1]
43-
44-
45-
def wait_ssh_ready(address, port, sleep, max_wait_sec):
46-
for _ in range(int(max_wait_sec / sleep)):
47-
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
48-
s.settimeout(sleep)
49-
try:
50-
s.connect((address, port))
51-
data = s.recv(256)
52-
if b"OpenSSH" in data:
53-
return
54-
except (ConnectionRefusedError, ConnectionResetError, TimeoutError):
55-
pass
56-
time.sleep(sleep)
57-
raise ConnectionRefusedError(f"cannot connect to port {port} after {max_wait_sec}s")
58-
59-
6034
def has_x86_64_v3_cpu():
6135
# x86_64-v3 has multiple features, see
6236
# https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels
@@ -95,8 +69,8 @@ def write_aws_creds(path):
9569
return True
9670

9771

98-
def deregister_ami(ami_id):
99-
ec2 = boto3.resource("ec2", region_name=AWS_REGION)
72+
def deregister_ami(ami_id, aws_region):
73+
ec2 = boto3.resource("ec2", region_name=aws_region)
10074
try:
10175
print(f"Deregistering image {ami_id}")
10276
ami = ec2.Image(ami_id)

vmtest/__init__.py

Whitespace-only changes.

vmtest/util.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import socket
2+
import time
3+
4+
5+
def get_free_port() -> int:
6+
# this is racy but there is no race-free way to do better with the qemu CLI
7+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
8+
s.bind(("localhost", 0))
9+
return s.getsockname()[1]
10+
11+
12+
def wait_ssh_ready(address, port, sleep, max_wait_sec):
13+
for _ in range(int(max_wait_sec / sleep)):
14+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
15+
s.settimeout(sleep)
16+
try:
17+
s.connect((address, port))
18+
data = s.recv(256)
19+
if b"OpenSSH" in data:
20+
return
21+
except (ConnectionRefusedError, ConnectionResetError, TimeoutError):
22+
pass
23+
time.sleep(sleep)
24+
raise ConnectionRefusedError(f"cannot connect to port {port} after {max_wait_sec}s")
Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
11
import contextlib
2-
import platform
2+
import shutil
33
import subprocess
44
from unittest.mock import call, patch
55

66
import pytest
7-
from testutil import get_free_port, has_executable, wait_ssh_ready
7+
8+
from vmtest.util import get_free_port, wait_ssh_ready
89

910

1011
def test_get_free_port():
1112
port_nr = get_free_port()
1213
assert 1024 < port_nr < 65535
1314

1415

15-
@pytest.fixture(name="free_port")
16-
def free_port_fixture():
17-
return get_free_port()
18-
19-
2016
@patch("time.sleep")
21-
def test_wait_ssh_ready_sleeps_no_connection(mocked_sleep, free_port):
17+
def test_wait_ssh_ready_sleeps_no_connection(mocked_sleep):
18+
free_port = get_free_port()
2219
with pytest.raises(ConnectionRefusedError):
2320
wait_ssh_ready("localhost", free_port, sleep=0.1, max_wait_sec=0.35)
2421
assert mocked_sleep.call_args_list == [call(0.1), call(0.1), call(0.1)]
2522

2623

27-
@pytest.mark.skipif(not has_executable("nc"), reason="needs nc")
28-
def test_wait_ssh_ready_sleeps_wrong_reply(free_port):
24+
@pytest.mark.skipif(not shutil.which("nc"), reason="needs nc")
25+
def test_wait_ssh_ready_sleeps_wrong_reply():
26+
free_port = get_free_port()
2927
with contextlib.ExitStack() as cm:
3028
with subprocess.Popen(
3129
f"echo not-ssh | nc -vv -l -p {free_port}",
@@ -47,12 +45,3 @@ def test_wait_ssh_ready_sleeps_wrong_reply(free_port):
4745
wait_ssh_ready("localhost", free_port, sleep=0.1, max_wait_sec=0.55)
4846
assert mocked_sleep.call_args_list == [
4947
call(0.1), call(0.1), call(0.1), call(0.1), call(0.1)]
50-
51-
52-
@pytest.mark.skipif(platform.system() == "Darwin", reason="hangs on macOS")
53-
@pytest.mark.skipif(not has_executable("nc"), reason="needs nc")
54-
def test_wait_ssh_ready_integration(free_port):
55-
with contextlib.ExitStack() as cm:
56-
with subprocess.Popen(f"echo OpenSSH | nc -l -p {free_port}", shell=True) as p:
57-
cm.callback(p.kill)
58-
wait_ssh_ready("localhost", free_port, sleep=0.1, max_wait_sec=10)

test/vm.py renamed to vmtest/vm.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
import paramiko
1313
from botocore.exceptions import ClientError
1414
from paramiko.client import AutoAddPolicy, SSHClient
15-
from testutil import AWS_REGION, get_free_port, wait_ssh_ready
15+
from vmtest.util import get_free_port, wait_ssh_ready
16+
17+
AWS_REGION = "us-east-1"
1618

1719

1820
class VM(abc.ABC):

0 commit comments

Comments
 (0)