Skip to content

Commit

Permalink
Merge pull request #1145 from mendersoftware/MEN-2208-test-update-to-…
Browse files Browse the repository at this point in the history
…common

MEN-2208: test_update.py to mender-image-tests
  • Loading branch information
lluiscampos committed Oct 25, 2020
2 parents e0cb0c5 + 8867346 commit 6a1e376
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 1,033 deletions.
47 changes: 27 additions & 20 deletions tests/acceptance/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,15 @@
configuration = {}


def start_qemu(qenv=None, conn=None):
def _start_qemu(qenv, conn, qemu_wrapper):
"""Start QEMU and return a subprocess.Popen object corresponding to a running
qemu process. `qenv` is a dict of environment variables that will be added
to `subprocess.Popen(..,env=)`.
qemu process.
Parameters:
* qenv is a dict of environment variables that will be added to
subprocess.Popen(..,env=).
* conn is a Fabric connection object to run SSH commands in the device.
* qemu_rwapper is an string with the path to the wrapper to launch QEMU.
Once qemu is started, a connection over ssh will attempted, so the returned
process is actually a QEMU instance with a fully booted guest OS.
Expand All @@ -49,14 +54,9 @@ def start_qemu(qenv=None, conn=None):
override the default behavior.
"""
env = dict(os.environ)
if qenv:
env.update(qenv)
env.update(qenv)

proc = subprocess.Popen(
["../../meta-mender-qemu/scripts/mender-qemu", "-snapshot"],
env=env,
start_new_session=True,
)
proc = subprocess.Popen([qemu_wrapper], env=env, start_new_session=True,)

try:
# make sure we are connected.
Expand All @@ -65,7 +65,7 @@ def start_qemu(qenv=None, conn=None):
# or do the necessary cleanup if we're not
try:
# qemu might have exited and this would raise an exception
print("cleaning up qemu instance with pid {}".format(proc.pid))
print("terminating qemu wrapper with pid {}".format(proc.pid))
proc.terminate()
except:
pass
Expand All @@ -76,7 +76,7 @@ def start_qemu(qenv=None, conn=None):
return proc


def start_qemu_block_storage(latest_sdimg, suffix, conn):
def start_qemu_block_storage(latest_sdimg, suffix, conn, qemu_wrapper):
"""Start qemu instance running block storage"""
fh, img_path = tempfile.mkstemp(suffix=suffix, prefix="test-image")
# don't need an open fd to temp file
Expand All @@ -90,15 +90,17 @@ def start_qemu_block_storage(latest_sdimg, suffix, conn):
qenv["DISK_IMG"] = img_path

try:
qemu = start_qemu(qenv, conn)
qemu = _start_qemu(qenv, conn, qemu_wrapper)
except:
# If qemu failed to start, remove the image and exit; else the image
# shall be cleaned up by the caller
os.remove(img_path)
raise

return qemu, img_path


def start_qemu_flash(latest_vexpress_nor, conn):
def start_qemu_flash(latest_vexpress_nor, conn, qemu_wrapper):
"""Start qemu instance running *.vexpress-nor image"""

print("qemu raw flash with image {}".format(latest_vexpress_nor))
Expand All @@ -122,8 +124,10 @@ def start_qemu_flash(latest_vexpress_nor, conn):
qenv["MACHINE"] = "vexpress-qemu-flash"

try:
qemu = start_qemu(qenv, conn)
qemu = _start_qemu(qenv, conn, qemu_wrapper)
except:
# If qemu failed to start, remove the image and exit; else the image
# shall be cleaned up by the caller
os.remove(img_path)
raise

Expand All @@ -143,7 +147,7 @@ def reboot(conn, wait=120):
# Make sure reboot has had time to take effect.
time.sleep(5)

for attempt in range(5):
for _ in range(5):
try:
conn.close()
break
Expand Down Expand Up @@ -222,13 +226,16 @@ def determine_active_passive_part(bitbake_variables, conn):
)


def get_ssh_common_args():
return "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
def get_ssh_common_args(conn):
args = "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
if "key_filename" in conn.connect_kwargs.keys():
args += " -i %s" % conn.connect_kwargs["key_filename"]
return args


# Yocto build SSH is lacking SFTP, let's override and use regular SCP instead.
def put_no_sftp(file, conn, remote="."):
cmd = "scp -C %s" % get_ssh_common_args()
cmd = "scp -C %s" % get_ssh_common_args(conn)

try:
conn.local(
Expand All @@ -242,7 +249,7 @@ def put_no_sftp(file, conn, remote="."):

# Yocto build SSH is lacking SFTP, let's override and use regular SCP instead.
def get_no_sftp(file, conn, local="."):
cmd = "scp -C %s" % get_ssh_common_args()
cmd = "scp -C %s" % get_ssh_common_args(conn)
conn.local(
"%s -P %s %s@%s:%s %s" % (cmd, conn.port, conn.user, conn.host, file, local)
)
Expand Down
30 changes: 27 additions & 3 deletions tests/acceptance/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/python
# Copyright 2017 Northern.tech AS
# Copyright 2020 Northern.tech AS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -15,9 +15,11 @@

import os
import subprocess
from fixtures import *

import pytest

from common import configuration
from fixtures import *


def pytest_addoption(parser):
Expand All @@ -34,6 +36,12 @@ def pytest_addoption(parser):
default="root",
help="user to log into remote hosts with (default is root)",
)
parser.addoption(
"--ssh-priv-key",
action="store",
default="",
help="Path to an SSH private key if required for login",
)
parser.addoption(
"--http-server",
action="store",
Expand All @@ -44,7 +52,13 @@ def pytest_addoption(parser):
"--sdimg-location",
action="store",
default=os.getcwd(),
help="location to the sdimg you want to install on the bbb",
help="location to the image to test (BUILDDIR for Yocto, deploy for mender-convert)",
)
parser.addoption(
"--qemu-wrapper",
action="store",
default="../../meta-mender-qemu/scripts/mender-qemu",
help="location of the shell wrapper to launch QEMU with testing image",
)
parser.addoption(
"--bitbake-image",
Expand Down Expand Up @@ -121,6 +135,11 @@ def user(request):
return request.config.getoption("--user")


@pytest.fixture(scope="session")
def ssh_priv_key(request):
return request.config.getoption("--ssh-priv-key")


@pytest.fixture(scope="session")
def http_server(request):
return request.config.getoption("--http-server")
Expand All @@ -136,6 +155,11 @@ def sdimg_location(request):
return request.config.getoption("--sdimg-location")


@pytest.fixture(scope="session")
def qemu_wrapper(request):
return request.config.getoption("--qemu-wrapper")


@pytest.fixture(scope="session")
def mender_image(request):
return request.config.getoption("--mender-image")
Expand Down
88 changes: 53 additions & 35 deletions tests/acceptance/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@ def config_host(host):


@pytest.fixture(scope="session")
def connection(request, user, host):
def connection(request, user, host, ssh_priv_key):
host, port = config_host(host)
connect_kwargs = {"password": "", "banner_timeout": 60, "auth_timeout": 60}
if ssh_priv_key != "":
connect_kwargs["key_filename"] = ssh_priv_key
conn = Connection(
host=host,
user=user,
port=port,
connect_timeout=60,
connect_kwargs={"password": "", "banner_timeout": 60, "auth_timeout": 60},
connect_kwargs=connect_kwargs,
)
conn.client.set_missing_host_key_policy(WarningPolicy())

Expand Down Expand Up @@ -118,31 +121,33 @@ def board_cleanup():
request.addfinalizer(board_cleanup)


def setup_qemu(request, build_dir, conn):
latest_sdimg = latest_build_artifact(build_dir, "core-image*.sdimg")
latest_uefiimg = latest_build_artifact(build_dir, "core-image*.uefiimg")
latest_biosimg = latest_build_artifact(build_dir, "core-image*.biosimg")
latest_gptimg = latest_build_artifact(build_dir, "core-image*.gptimg")
latest_vexpress_nor = latest_build_artifact(build_dir, "core-image*.vexpress-nor")
def setup_qemu(request, qemu_wrapper, build_dir, conn):
latest_sdimg = latest_build_artifact(build_dir, ".sdimg")
latest_uefiimg = latest_build_artifact(build_dir, ".uefiimg")
latest_biosimg = latest_build_artifact(build_dir, ".biosimg")
latest_gptimg = latest_build_artifact(build_dir, ".gptimg")
latest_vexpress_nor = latest_build_artifact(build_dir, ".vexpress-nor")

if latest_sdimg:
qemu, img_path = start_qemu_block_storage(
latest_sdimg, suffix=".sdimg", conn=conn
latest_sdimg, suffix=".sdimg", conn=conn, qemu_wrapper=qemu_wrapper
)
elif latest_uefiimg:
qemu, img_path = start_qemu_block_storage(
latest_uefiimg, suffix=".uefiimg", conn=conn
latest_uefiimg, suffix=".uefiimg", conn=conn, qemu_wrapper=qemu_wrapper
)
elif latest_biosimg:
qemu, img_path = start_qemu_block_storage(
latest_biosimg, suffix=".biosimg", conn=conn
latest_biosimg, suffix=".biosimg", conn=conn, qemu_wrapper=qemu_wrapper
)
elif latest_gptimg:
qemu, img_path = start_qemu_block_storage(
latest_gptimg, suffix=".gptimg", conn=conn
latest_gptimg, suffix=".gptimg", conn=conn, qemu_wrapper=qemu_wrapper
)
elif latest_vexpress_nor:
qemu, img_path = start_qemu_flash(latest_vexpress_nor, conn=conn)
qemu, img_path = start_qemu_flash(
latest_vexpress_nor, conn=conn, qemu_wrapper=qemu_wrapper
)
else:
pytest.fail("cannot find a suitable image type")

Expand All @@ -152,25 +157,31 @@ def setup_qemu(request, build_dir, conn):
# cases more predictable.
def qemu_finalizer():
def qemu_finalizer_impl(conn):
# Try clearing firmware variables
try:
manual_uboot_commit(conn)
# Collect the coverage files from /data/mender/ if present
try:
conn.run("ls /data/mender/cover*")
Path("coverage").mkdir(exist_ok=True)
get_no_sftp("/data/mender/cover*", conn, local="coverage")
except:
pass
except:
pass

# Collect the coverage files from /data/mender/ if present
try:
conn.run("ls /data/mender/cover*")
Path("coverage").mkdir(exist_ok=True)
get_no_sftp("/data/mender/cover*", conn, local="coverage")
except:
pass

# Try clean poweroff
try:
conn.run("poweroff")
halt_time = time.time()
# Wait up to 30 seconds for shutdown.
while halt_time + 30 > time.time() and qemu.poll() is None:
time.sleep(1)
except:
# Nothing we can do about that.
pass

# kill qemu
# Terminate qemu
try:
qemu.terminate()
except OSError as oserr:
Expand All @@ -189,13 +200,13 @@ def qemu_finalizer_impl(conn):


@pytest.fixture(scope="session")
def setup_board(request, build_image_fn, connection, board_type):
def setup_board(request, qemu_wrapper, build_image_fn, connection, board_type):

print("board type: ", board_type)

if "qemu" in board_type:
image_dir = build_image_fn()
return setup_qemu(request, image_dir, connection)
return setup_qemu(request, qemu_wrapper, image_dir, connection)
elif board_type == "beagleboneblack":
return setup_bbb(request, connection)
elif board_type == "raspberrypi3":
Expand Down Expand Up @@ -226,7 +237,7 @@ def latest_rootfs(conversion, mender_image):
def latest_sdimg():
assert os.environ.get("BUILDDIR", False), "BUILDDIR must be set"

# Find latest built rootfs.
# Find latest built sdimg.
return latest_build_artifact(os.environ["BUILDDIR"], "core-image*.sdimg")


Expand Down Expand Up @@ -305,11 +316,11 @@ def successful_image_update_mender(request, build_image_fn):
"""Provide a 'successful_image_update.mender' file in the current directory that
contains the latest built update."""

latest_mender_image = latest_build_artifact(build_image_fn(), "core-image*.mender")
latest_mender_image = latest_build_artifact(build_image_fn(), ".mender")

shutil.copy(latest_mender_image, "successful_image_update.mender")

print("Copying 'successful_image_update.mender' to '%s'" % latest_mender_image)
print("Copying '%s' to 'successful_image_update.mender'" % latest_mender_image)

def cleanup_image_dat():
os.remove("successful_image_update.mender")
Expand Down Expand Up @@ -353,15 +364,19 @@ def path_restore():


@pytest.fixture(scope="session")
def build_image_fn(request, prepared_test_build_base, bitbake_image):
"""
Returns a function which returns a clean image. The reason it does not
return the clean image directly is that it may need to be reset to a clean
state if several independent fixtures invoke it, and there have been unclean
builds in between.
def build_image_fn(request, conversion, prepared_test_build_base, bitbake_image):
"""Returns a function which returns the build dir of a clean image.
The reason it does not return the build dir directly is that it may need to
be reset to a clean state if several independent fixtures invoke it, and
there have been unclean builds in between.
"""

def img_builder():
if conversion:
assert os.environ.get("BUILDDIR", False), "BUILDDIR must be set"
return os.environ["BUILDDIR"]

reset_build_conf(prepared_test_build_base["build_dir"])
build_image(
prepared_test_build_base["build_dir"],
Expand All @@ -378,7 +393,10 @@ def img_builder():


@pytest.fixture(scope="session")
def prepared_test_build_base(request, bitbake_variables, no_tmp_build_dir):
def prepared_test_build_base(request, conversion, bitbake_variables, no_tmp_build_dir):

if conversion:
return {"build_dir": None, "bitbake_corebase": None}

if no_tmp_build_dir:
build_dir = os.environ["BUILDDIR"]
Expand Down Expand Up @@ -424,7 +442,7 @@ def cleanup_test_build():
def prepared_test_build(prepared_test_build_base):
"""
Prepares a separate test build directory where a custom build can be
made, which reuses the sstate-cache.
made, which reuses the sstate-cache.
"""

reset_build_conf(prepared_test_build_base["build_dir"])
Expand Down
2 changes: 1 addition & 1 deletion tests/acceptance/image-tests
Loading

0 comments on commit 6a1e376

Please sign in to comment.