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