From 038daad2182a22c81861ee7cbb5f0c85f6bb16ba Mon Sep 17 00:00:00 2001 From: Qyriad Date: Mon, 25 Mar 2024 12:12:56 -0600 Subject: 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 --- meson/setup-functional-tests.py | 105 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100755 meson/setup-functional-tests.py (limited to 'meson/setup-functional-tests.py') 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) -- cgit v1.2.3