aboutsummaryrefslogtreecommitdiff
path: root/meson/setup-functional-tests.py
diff options
context:
space:
mode:
authorQyriad <qyriad@qyriad.me>2024-03-25 12:12:56 -0600
committerQyriad <qyriad@qyriad.me>2024-03-27 18:37:50 -0600
commit038daad2182a22c81861ee7cbb5f0c85f6bb16ba (patch)
tree653f303e26bd99106a4d39d5cb9f880d4ba0a0a3 /meson/setup-functional-tests.py
parentedba570664b952facde43fd0414e60f0a42851da (diff)
meson: implement functional tests
Functional tests can be run with `meson test -C build --suite installcheck`. Notably, functional tests must be run *after* running `meson install` (Lix's derivation runs the installcheck suite in installCheckPhase so it does this correctly), due to some quirks between Meson and the testing system. As far as I can tell the functional tests are meant to be run after installing anyway, but unfortunately I can't transparently make `meson test --suite installcheck` depend on the install targets. The script that runs the functional tests, meson/run-test.py, checks that `meson install` has happened and fails fast with a (hopefully) helpful error message if any of the functional tests are run before installing. TODO: this change needs reflection in developer documentation Change-Id: I8dcb5fdfc0b6cb17580973d24ad930abd57018f6
Diffstat (limited to 'meson/setup-functional-tests.py')
-rwxr-xr-xmeson/setup-functional-tests.py105
1 files changed, 105 insertions, 0 deletions
diff --git a/meson/setup-functional-tests.py b/meson/setup-functional-tests.py
new file mode 100755
index 000000000..344bdc92e
--- /dev/null
+++ b/meson/setup-functional-tests.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+
+"""
+So like. This script is cursed.
+It's a helper for this project's Meson buildsystem for Lix's functional tests.
+The functional tests are a bunch of bash scripts, that each expect to be run from the
+directory from the directory that that script is in, and also expect modifications to have
+happened to the source tree, and even splork files around. The last is against the spirit
+of Meson (and personally annoying), to have build processes that aren't self-contained in the
+out-of-source build directory, but more problematically we need configured files in the test
+tree.
+So. We copy the tests tree into the build directory.
+Meson doesn't have a good way of doing this natively -- the best you could do is subdir()
+into every directory in the tests tree and configure_file(copy : true) on every file,
+but this won't copy symlinks as symlinks, which we need since the test suite has, well,
+tests on symlinks.
+However, the functional tests are normally run during Lix's derivation's installCheckPhase,
+after Lix has already been "installed" somewhere. So in Meson we setup add this file as an
+install script and copy everything in tests/functional to the build directory, preserving
+things like symlinks, even broken ones (which are intentional).
+
+TODO(Qyriad): when we remove the old build system entirely, we can instead fix the tests.
+"""
+
+from pathlib import Path
+import os, os.path
+import shutil
+import sys
+import traceback
+
+name = 'setup-functional-tests.py'
+
+if 'MESON_SOURCE_ROOT' not in os.environ or 'MESON_BUILD_ROOT' not in os.environ:
+ raise ValueError(f'{name}: this script must be run from the Meson build system')
+
+print(f'{name}: mirroring tests/functional to build directory')
+
+tests_source = Path(os.environ['MESON_SOURCE_ROOT']) / 'tests/functional'
+tests_build = Path(os.environ['MESON_BUILD_ROOT']) / 'tests/functional'
+
+def main():
+
+ os.chdir(tests_build)
+
+ for src_dirpath, src_dirnames, src_filenames in os.walk(tests_source):
+ src_dirpath = Path(src_dirpath)
+ assert src_dirpath.is_absolute(), f'{src_dirpath=} is not absolute'
+
+ # os.walk() gives us the absolute path to the directory we're currently in as src_dirpath.
+ # We want to mirror from the perspective of `tests_source`.
+ rel_subdir = src_dirpath.relative_to(tests_source)
+ assert (not rel_subdir.is_absolute()), f'{rel_subdir=} is not relative'
+
+ # And then join that relative path on `tests_build` to get the absolute
+ # path in the build directory that corresponds to `src_dirpath`.
+ build_dirpath = tests_build / rel_subdir
+ assert build_dirpath.is_absolute(), f'{build_dirpath=} is not absolute'
+
+ # More concretely, for the test file tests/functional/ca/build.sh:
+ # - src_dirpath is `$MESON_SOURCE_ROOT/tests/functional/ca`
+ # - rel_subidr is `ca`
+ # - build_dirpath is `$MESON_BUILD_ROOT/tests/functional/ca`
+
+ # `src_dirname` are directories underneath `src_dirpath`, and will be relative
+ # to `src_dirpath`.
+ for src_dirname in src_dirnames:
+ # Take the name of the directory in the tests source and join it on `src_dirpath`
+ # to get the full path to this specific directory in the tests source.
+ src = src_dirpath / src_dirname
+ # If there isn't *something* here, then our logic is wrong.
+ # Path.exists(follow_symlinks=False) wasn't added until Python 3.12, so we use
+ # os.path.lexists() here.
+ assert os.path.lexists(src), f'{src=} does not exist'
+
+ # Take the name of this directory and join it on `build_dirpath` to get the full
+ # path to the directory in `build/tests/functional` that we need to create.
+ dest = build_dirpath / src_dirname
+ if src.is_symlink():
+ src_target = src.readlink()
+ dest.unlink(missing_ok=True)
+ dest.symlink_to(src_target)
+ else:
+ dest.mkdir(parents=True, exist_ok=True)
+
+ for src_filename in src_filenames:
+ # os.walk() should be giving us relative filenames.
+ # If it isn't, our path joins will be veeeery wrong.
+ assert (not Path(src_filename).is_absolute()), f'{src_filename=} is not relative'
+
+ src = src_dirpath / src_filename
+ dst = build_dirpath / src_filename
+ # Mildly misleading name -- unlink removes ordinary files as well as symlinks.
+ dst.unlink(missing_ok=True)
+ # shutil.copy2() best-effort preserves metadata.
+ shutil.copy2(src, dst, follow_symlinks=False)
+
+
+try:
+ sys.exit(main())
+except Exception as e:
+ # Any error is likely a bug in this script.
+ print(f'{name}: INTERNAL ERROR setting up functional tests: {e}', file=sys.stderr)
+ print(traceback.format_exc())
+ print(f'this is a bug in {name}')
+ sys.exit(1)