Skip to content
Snippets Groups Projects
Commit 1ae2a113 authored by Simon McVittie's avatar Simon McVittie
Browse files

Merge branch 'wip/smcv/test-soldier' into 'master'

tests: Add support for basic testing on a soldier container

See merge request steam/pressure-vessel!58
parents e4517e5b 68303f6b
No related branches found
No related tags found
No related merge requests found
......@@ -21,7 +21,9 @@ ideally contains at least:
on the host system
* scout/files:
The Platform merged-/usr from the SteamLinuxRuntime depot
* scout_sysroot:
* soldier/files:
The Platform merged-/usr from the SteamLinuxRuntime depot
* scout_sysroot, soldier_sysroot:
An SDK sysroot like the one recommended for the Docker container
and run (for example) 'meson test -v -C _build' as usual.
......@@ -262,6 +264,8 @@ class TestContainers(BaseTest):
) as writer:
run_subprocess(
[
'env',
'LD_BIND_NOW=1',
host_srsi,
'--verbose',
],
......@@ -312,7 +316,7 @@ class TestContainers(BaseTest):
# need into that directory.
os.makedirs(os.path.join(cls.artifacts, 'tmp'), exist_ok=True)
for f in ('testutils.py', 'inside-scout.py'):
for f in ('testutils.py', 'inside-runtime.py'):
shutil.copy2(
os.path.join(cls.G_TEST_SRCDIR, f),
os.path.join(cls.artifacts, 'tmp', f),
......@@ -359,18 +363,60 @@ class TestContainers(BaseTest):
def _test_scout(
self,
test_name: str,
scout: str,
runtime: str,
*,
copy: bool = False,
gc: bool = True,
locales: bool = False,
only_prepare: bool = False
) -> None:
self._test_container(
test_name,
runtime,
copy=copy,
gc=gc,
is_scout=True,
locales=locales,
only_prepare=only_prepare,
)
def _test_soldier(
self,
test_name: str,
runtime: str,
*,
copy: bool = False,
gc: bool = True,
locales: bool = False,
only_prepare: bool = False
) -> None:
self._test_container(
test_name,
runtime,
copy=copy,
gc=gc,
is_soldier=True,
locales=locales,
only_prepare=only_prepare,
)
def _test_container(
self,
test_name: str,
runtime: str,
*,
copy: bool = False,
gc: bool = True,
is_scout: bool = False,
is_soldier: bool = False,
locales: bool = False,
only_prepare: bool = False
) -> None:
if self.bwrap is None and not only_prepare:
self.skipTest('Unable to run bwrap (in a container?)')
if not os.path.isdir(scout):
self.skipTest('{} not found'.format(scout))
if not os.path.isdir(runtime):
self.skipTest('{} not found'.format(runtime))
artifacts = os.path.join(
self.artifacts,
......@@ -380,7 +426,7 @@ class TestContainers(BaseTest):
argv = [
self.pv_wrap,
'--runtime', scout,
'--runtime', runtime,
'--verbose',
]
......@@ -397,17 +443,28 @@ class TestContainers(BaseTest):
if not gc:
argv.append('--no-gc-runtimes')
if is_scout:
python = 'python3.5'
else:
python = 'python3'
if only_prepare:
argv.append('--only-prepare')
else:
argv.extend([
'--',
'env',
'TEST_INSIDE_SCOUT_ARTIFACTS=' + artifacts,
'TEST_INSIDE_SCOUT_IS_COPY=' + ('1' if copy else ''),
'TEST_INSIDE_SCOUT_LOCALES=' + ('1' if locales else ''),
'python3.5',
os.path.join(self.artifacts, 'tmp', 'inside-scout.py'),
'TEST_INSIDE_RUNTIME_ARTIFACTS=' + artifacts,
'TEST_INSIDE_RUNTIME_IS_COPY=' + ('1' if copy else ''),
'TEST_INSIDE_RUNTIME_IS_SCOUT=' + (
'1' if is_scout else ''
),
'TEST_INSIDE_RUNTIME_IS_SOLDIER=' + (
'1' if is_soldier else ''
),
'TEST_INSIDE_RUNTIME_LOCALES=' + ('1' if locales else ''),
python,
os.path.join(self.artifacts, 'tmp', 'inside-runtime.py'),
])
# Create directories representing previous runs of
......@@ -444,7 +501,7 @@ class TestContainers(BaseTest):
# Put this in a subtest so that if it fails, we still get
# to inspect the copied sysroot
with self.subTest('run', copy=copy, scout=scout):
with self.subTest('run', copy=copy, runtime=runtime):
completed = self.run_subprocess(
argv,
cwd=self.artifacts,
......@@ -486,7 +543,8 @@ class TestContainers(BaseTest):
self._assert_mutable_sysroot(
tree,
artifacts,
is_scout=True,
is_scout=is_scout,
is_soldier=is_soldier,
)
def _assert_mutable_sysroot(
......@@ -494,7 +552,8 @@ class TestContainers(BaseTest):
tree: str,
artifacts: str,
*,
is_scout: bool = True
is_scout: bool = False,
is_soldier: bool = False
) -> None:
with open(
os.path.join(artifacts, 'contents.txt'),
......@@ -851,6 +910,42 @@ class TestContainers(BaseTest):
with self.subTest('transient'):
self._test_scout('scout', scout)
def test_soldier_sysroot(self) -> None:
soldier = os.path.join(self.containers_dir, 'soldier_sysroot')
if os.path.isdir(os.path.join(soldier, 'files')):
soldier = os.path.join(soldier, 'files')
with self.subTest('only-prepare'):
self._test_soldier(
'soldier_sysroot_prep', soldier,
copy=True, only_prepare=True,
)
with self.subTest('copy'):
self._test_soldier(
'soldier_sysroot_copy', soldier, copy=True, gc=False,
)
with self.subTest('transient'):
self._test_soldier('soldier_sysroot', soldier, locales=True)
def test_soldier_usr(self) -> None:
soldier = os.path.join(self.containers_dir, 'soldier', 'files')
with self.subTest('only-prepare'):
self._test_soldier(
'soldier_prep', soldier, copy=True, only_prepare=True,
)
with self.subTest('copy'):
self._test_soldier(
'soldier_copy', soldier, copy=True, locales=True,
)
with self.subTest('transient'):
self._test_soldier('soldier', soldier)
if __name__ == '__main__':
assert sys.version_info >= (3, 4), \
......
#!/usr/bin/python3.5
#!/usr/bin/python3
# Copyright 2020 Collabora Ltd.
#
# SPDX-License-Identifier: MIT
......@@ -21,11 +21,11 @@ from testutils import (
)
"""
Test script intended to be run inside a SteamRT 1 'scout' container,
Test script intended to be run inside a SteamRT container
to assert that everything is as it should be.
"""
logger = logging.getLogger('test-inside-scout')
logger = logging.getLogger('test-inside-runtime')
class HostInfo:
......@@ -70,12 +70,12 @@ class HostInfo:
break
class TestInsideScout(BaseTest):
class TestInsideRuntime(BaseTest):
def setUp(self) -> None:
super().setUp()
self.host = HostInfo()
artifacts = os.getenv('TEST_INSIDE_SCOUT_ARTIFACTS')
artifacts = os.getenv('TEST_INSIDE_RUNTIME_ARTIFACTS')
if artifacts is not None:
self.artifacts = Path(artifacts)
......@@ -135,9 +135,16 @@ class TestInsideScout(BaseTest):
assert len(tokens) == 1, tokens
data[key] = tokens[0]
self.assertEqual(data.get('VERSION_ID'), '1')
self.assertEqual(data.get('ID'), 'steamrt')
self.assertEqual(data.get('ID_LIKE'), 'ubuntu')
if os.environ.get('TEST_INSIDE_RUNTIME_IS_SCOUT'):
self.assertEqual(data.get('VERSION_ID'), '1')
self.assertEqual(data.get('ID'), 'steamrt')
self.assertEqual(data.get('ID_LIKE'), 'ubuntu')
elif os.environ.get('TEST_INSIDE_RUNTIME_IS_SOLDIER'):
self.assertEqual(data.get('VERSION_ID'), '2')
self.assertEqual(data.get('ID'), 'steamrt')
self.assertEqual(data.get('ID_LIKE'), 'debian')
self.assertIsNotNone(data.get('BUILD_ID'))
def test_environ(self) -> None:
......@@ -148,7 +155,7 @@ class TestInsideScout(BaseTest):
# No actual *tests* here just yet - we just log what's there.
def test_overrides(self) -> None:
if os.getenv('TEST_INSIDE_SCOUT_IS_COPY'):
if os.getenv('TEST_INSIDE_RUNTIME_IS_COPY'):
target = os.readlink('/overrides')
self.assertEqual(target, 'usr/lib/pressure-vessel/overrides')
......@@ -156,76 +163,64 @@ class TestInsideScout(BaseTest):
self.assertTrue(Path('/overrides/lib').is_dir())
def test_glibc(self) -> None:
"""
Assert that we took the glibc version from the host OS.
We assume this will always be true for scout, because scout
is based on Ubuntu 12.04, the oldest operating system we support;
and in cases where our glibc is the same version as the glibc of
the host OS, we prefer the host.
"""
overrides = Path('/overrides').resolve()
glibc = ctypes.cdll.LoadLibrary('libc.so.6')
gnu_get_libc_version = glibc.gnu_get_libc_version
gnu_get_libc_version.restype = ctypes.c_char_p
glibc_version = gnu_get_libc_version().decode('ascii')
logger.info('glibc version in use: %s', glibc_version)
major, minor, *rest = glibc_version.split('.')
self.assertGreaterEqual((int(major), int(minor)), (2, 15))
# This assumes that uname -m matches the multiarch tuple
# closely enough. On x86_64 it does, and on i386 we have
# symlinks /overrides/lib/i[456]86-linux-gnu.
host_glibc = ctypes.cdll.LoadLibrary(
'/{}/lib/{}-linux-gnu/libc.so.6'.format(
overrides,
os.uname().machine,
),
)
gnu_get_libc_version = host_glibc.gnu_get_libc_version
gnu_get_libc_version.restype = ctypes.c_char_p
host_glibc_version = gnu_get_libc_version().decode('ascii')
logger.info('host glibc version: %s', host_glibc_version)
self.assertEqual(host_glibc_version, glibc_version)
if (
'HOST_LD_LINUX_SO_REALPATH' in os.environ
and Path('/usr/lib/i386-linux-gnu').is_dir()
):
host_path = os.environ['HOST_LD_LINUX_SO_REALPATH']
expected = self.host.path / host_path.lstrip('/')
expected_stat = expected.stat()
logger.info('host ld-linux.so.2: %s', host_path)
for really in (
'/lib/ld-linux.so.2',
'/lib/i386-linux-gnu/ld-linux.so.2',
'/lib/i386-linux-gnu/ld-2.15.so',
if os.environ.get('TEST_INSIDE_RUNTIME_IS_SCOUT'):
# Assert that we took the glibc version from the host OS.
#
# We assume this will always be true for scout, because scout
# is based on Ubuntu 12.04, the oldest operating system we support;
# and in cases where our glibc is the same version as the glibc of
# the host OS, we prefer the host.
if (
'HOST_LD_LINUX_SO_REALPATH' in os.environ
and Path('/usr/lib/i386-linux-gnu').is_dir()
):
really_stat = Path(really).stat()
# Either it's a symlink to the same file, or the same file
# was mounted over it
self.assertEqual(really_stat.st_dev, expected_stat.st_dev)
self.assertEqual(really_stat.st_ino, expected_stat.st_ino)
if (
'HOST_LD_LINUX_X86_64_SO_REALPATH' in os.environ
and Path('/usr/lib/x86_64-linux-gnu').is_dir()
):
host_path = os.environ['HOST_LD_LINUX_X86_64_SO_REALPATH']
expected = self.host.path / host_path.lstrip('/')
expected_stat = expected.stat()
logger.info('host ld-linux-x86-64.so.2: %s', host_path)
for really in (
'/lib64/ld-linux-x86-64.so.2',
'/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2',
'/lib/x86_64-linux-gnu/ld-2.15.so',
host_path = os.environ['HOST_LD_LINUX_SO_REALPATH']
expected = self.host.path / host_path.lstrip('/')
expected_stat = expected.stat()
logger.info('host ld-linux.so.2: %s', host_path)
for really in (
'/lib/ld-linux.so.2',
'/lib/i386-linux-gnu/ld-linux.so.2',
'/lib/i386-linux-gnu/ld-2.15.so',
):
really_stat = Path(really).stat()
# Either it's a symlink to the same file, or the same file
# was mounted over it
self.assertEqual(really_stat.st_dev, expected_stat.st_dev)
self.assertEqual(really_stat.st_ino, expected_stat.st_ino)
if (
'HOST_LD_LINUX_X86_64_SO_REALPATH' in os.environ
and Path('/usr/lib/x86_64-linux-gnu').is_dir()
):
really_stat = Path(really).stat()
self.assertEqual(really_stat.st_dev, expected_stat.st_dev)
self.assertEqual(really_stat.st_ino, expected_stat.st_ino)
host_path = os.environ['HOST_LD_LINUX_X86_64_SO_REALPATH']
expected = self.host.path / host_path.lstrip('/')
expected_stat = expected.stat()
logger.info('host ld-linux-x86-64.so.2: %s', host_path)
for really in (
'/lib64/ld-linux-x86-64.so.2',
'/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2',
'/lib/x86_64-linux-gnu/ld-2.15.so',
):
really_stat = Path(really).stat()
self.assertEqual(really_stat.st_dev, expected_stat.st_dev)
self.assertEqual(really_stat.st_ino, expected_stat.st_ino)
elif os.environ.get('TEST_INSIDE_RUNTIME_IS_SOLDIER'):
# We don't know whether it's soldier's glibc 2.28 or something
# newer from the host, but it should definitely be at least
# soldier's version
self.assertGreaterEqual((int(major), int(minor)), (2, 28))
def test_srsi(self) -> None:
overrides = Path('/overrides').resolve()
......@@ -324,10 +319,21 @@ class TestInsideScout(BaseTest):
self.assertIn('name', parsed['os-release'])
self.assertIn('pretty_name', parsed['os-release'])
self.assertIn('version_id', parsed['os-release'])
self.assertEqual('1', parsed['os-release']['version_id'])
self.assertEqual('scout', parsed['os-release']['version_codename'])
self.assertIn('build_id', parsed['os-release'])
if os.environ.get('TEST_INSIDE_RUNTIME_IS_SCOUT'):
self.assertEqual('1', parsed['os-release']['version_id'])
self.assertEqual(
'scout',
parsed['os-release']['version_codename'],
)
elif os.environ.get('TEST_INSIDE_RUNTIME_IS_SOLDIER'):
self.assertEqual('2', parsed['os-release']['version_id'])
self.assertEqual(
'soldier',
parsed['os-release']['version_codename'],
)
with self.catch(
'container info',
diagnostic=parsed.get('container'),
......@@ -342,7 +348,7 @@ class TestInsideScout(BaseTest):
host_parsed['os-release'],
)
if os.environ.get('TEST_INSIDE_SCOUT_LOCALES'):
if os.environ.get('TEST_INSIDE_RUNTIME_LOCALES'):
for locale, host_details in host_parsed.get(
'locales', {}
).items():
......@@ -403,6 +409,12 @@ class TestInsideScout(BaseTest):
expect_library_issues |= set(details.get('issues', []))
continue
if soname == 'libOSMesa.so.8':
# T22540: C++ ABI issues around std::string in the
# ABI of libLLVM-7.so.1
expect_library_issues |= set(details.get('issues', []))
continue
self.assertIn('path', details)
self.assertEqual(
[],
......@@ -414,37 +426,53 @@ class TestInsideScout(BaseTest):
)
self.assertEqual([], details.get('issues', []))
for soname in (
'libBrokenLocale.so.1',
'libanl.so.1',
'libc.so.6',
'libcrypt.so.1',
'libdl.so.2',
'libm.so.6',
'libnsl.so.1',
'libpthread.so.0',
'libresolv.so.2',
'librt.so.1',
'libutil.so.1',
):
# These are from glibc, which is depended on by Mesa, and
# is at least as new as scout's version in every supported
# version of the Steam Runtime.
self.assertEqual(
arch_info['library-details'][soname]['path'],
'{}/lib/{}/{}'.format(overrides, multiarch, soname),
)
if os.environ.get('TEST_INSIDE_RUNTIME_IS_SCOUT'):
for soname in (
'libBrokenLocale.so.1',
'libanl.so.1',
'libc.so.6',
'libcrypt.so.1',
'libdl.so.2',
'libm.so.6',
'libnsl.so.1',
'libpthread.so.0',
'libresolv.so.2',
'librt.so.1',
'libutil.so.1',
):
# These are from glibc, which is depended on by Mesa, and
# is at least as new as scout's version in every supported
# version of the Steam Runtime.
self.assertEqual(
arch_info['library-details'][soname]['path'],
'{}/lib/{}/{}'.format(overrides, multiarch, soname),
)
for soname in (
'libSDL-1.2.so.0',
'libfltk.so.1.1',
):
if os.environ.get('TEST_INSIDE_RUNTIME_IS_SCOUT'):
not_graphics_drivers = [
'libSDL-1.2.so.0',
'libfltk.so.1.1',
]
elif os.environ.get('TEST_INSIDE_RUNTIME_IS_SOLDIER'):
not_graphics_drivers = [
'libfltk.so.1.1',
'libgdk-3.so.0',
'libSDL2-2.0.so.0',
]
else:
not_graphics_drivers = []
for soname in not_graphics_drivers:
# These libraries are definitely not part of the graphics
# driver stack
self.assertEqual(
arch_info['library-details'][soname]['path'],
'/usr/lib/{}/{}'.format(multiarch, soname),
)
if (
arch_info['library-details'][soname]['path']
!= '/lib/{}/{}'.format(multiarch, soname)
):
self.assertEqual(
arch_info['library-details'][soname]['path'],
'/usr/lib/{}/{}'.format(multiarch, soname),
)
if host_info:
expect_symlinks = {
......@@ -583,7 +611,7 @@ class TestInsideScout(BaseTest):
with self.subTest(read_only_place):
if (
read_only_place.startswith('/overrides')
and not os.getenv('TEST_INSIDE_SCOUT_IS_COPY')
and not os.getenv('TEST_INSIDE_RUNTIME_IS_COPY')
):
# If we aren't working from a temporary copy of the
# runtime, /overrides is on a tmpfs
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment