summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEli Schwartz <eschwartz@archlinux.org>2022-05-19 19:17:01 -0400
committerEli Schwartz <eschwartz93@gmail.com>2025-01-07 20:16:05 -0500
commiteb1e52afa142fc0f38260a9cb3413f2bd63b1675 (patch)
tree26c54730d3d586c6305da7127ed5716da78407da
parentdfe5cbb3e432bb632731f853df05e2023ab233d6 (diff)
downloadmeson-eb1e52afa142fc0f38260a9cb3413f2bd63b1675.tar.gz
mtest: fix rebuilding all before running tests
Inconsistency in the original implementation of commit 79e2c52a15e896e46ff3cfa3ec16fbf3f132ee01. If an explicit list of targets is passed on the CLI, then that is passed to rebuild_deps. If not, we pass every loaded test to rebuild_deps instead. This means we cannot distinguish between "trying to run all tests" and "trying to run specific tests". We then load all the deps for all tests, and try to build them all as explicit arguments to the underlying ninja. There are two situations where this falls flat: - given underspecified deps - given all (selected?) tests legitimately happen to have no dependencies In both cases, we calculate that there are no deps to rebuild, we run ninja without any targets, and this invokes the default "all" rule and maybe builds a few thousand targets that this specific test run does not need. Additionally, in large projects which define many tests with many dependencies, we could end up overflowing ARG_MAX when processing *all* tests. Instead, pass no tests to rebuild_deps. We then specially handle this by directly running the relevant ninja target for "all test deps", which is overall more elegant than specifying many many dependencies by name. Given a subset of tests to guarantee the freshness of, we instead skip running ninja at all if there are indeed no test dependencies.
-rw-r--r--mesonbuild/mtest.py48
-rw-r--r--test cases/unit/106 underspecified mtest/main.c1
-rw-r--r--test cases/unit/106 underspecified mtest/meson.build8
-rwxr-xr-xtest cases/unit/106 underspecified mtest/runner.py5
-rw-r--r--unittests/platformagnostictests.py15
5 files changed, 59 insertions, 18 deletions
diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py
index d0added78..39970e530 100644
--- a/mesonbuild/mtest.py
+++ b/mesonbuild/mtest.py
@@ -1825,9 +1825,10 @@ class TestHarness:
raise RuntimeError('Test harness object can only be used once.')
self.is_run = True
tests = self.get_tests()
+ rebuild_only_tests = tests if self.options.args else []
if not tests:
return 0
- if not self.options.no_rebuild and not rebuild_deps(self.ninja, self.options.wd, tests):
+ if not self.options.no_rebuild and not rebuild_deps(self.ninja, self.options.wd, rebuild_only_tests, self.options.benchmark):
# We return 125 here in case the build failed.
# The reason is that exit code 125 tells `git bisect run` that the current
# commit should be skipped. Thus users can directly use `meson test` to
@@ -2140,7 +2141,7 @@ def list_tests(th: TestHarness) -> bool:
print(th.get_pretty_suite(t))
return not tests
-def rebuild_deps(ninja: T.List[str], wd: str, tests: T.List[TestSerialisation]) -> bool:
+def rebuild_deps(ninja: T.List[str], wd: str, tests: T.List[TestSerialisation], benchmark: bool) -> bool:
def convert_path_to_target(path: str) -> str:
path = os.path.relpath(path, wd)
if os.sep != '/':
@@ -2149,23 +2150,34 @@ def rebuild_deps(ninja: T.List[str], wd: str, tests: T.List[TestSerialisation])
assert len(ninja) > 0
- targets_file = os.path.join(wd, 'meson-info/intro-targets.json')
- with open(targets_file, encoding='utf-8') as fp:
- targets_info = json.load(fp)
-
- depends: T.Set[str] = set()
targets: T.Set[str] = set()
- intro_targets: T.Dict[str, T.List[str]] = {}
- for target in targets_info:
- intro_targets[target['id']] = [
- convert_path_to_target(f)
- for f in target['filename']]
- for t in tests:
- for d in t.depends:
- if d in depends:
- continue
- depends.update(d)
- targets.update(intro_targets[d])
+ if tests:
+ targets_file = os.path.join(wd, 'meson-info/intro-targets.json')
+ with open(targets_file, encoding='utf-8') as fp:
+ targets_info = json.load(fp)
+
+ depends: T.Set[str] = set()
+ intro_targets: T.Dict[str, T.List[str]] = {}
+ for target in targets_info:
+ intro_targets[target['id']] = [
+ convert_path_to_target(f)
+ for f in target['filename']]
+ for t in tests:
+ for d in t.depends:
+ if d in depends:
+ continue
+ depends.update(d)
+ targets.update(intro_targets[d])
+ else:
+ if benchmark:
+ targets.add('meson-benchmark-prereq')
+ else:
+ targets.add('meson-test-prereq')
+
+ if not targets:
+ # We want to build minimal deps, but if the subset of targets have no
+ # deps then ninja falls back to 'all'.
+ return True
ret = subprocess.run(ninja + ['-C', wd] + sorted(targets)).returncode
if ret != 0:
diff --git a/test cases/unit/106 underspecified mtest/main.c b/test cases/unit/106 underspecified mtest/main.c
new file mode 100644
index 000000000..8842fc122
--- /dev/null
+++ b/test cases/unit/106 underspecified mtest/main.c
@@ -0,0 +1 @@
+int main(void) { return 0 ; }
diff --git a/test cases/unit/106 underspecified mtest/meson.build b/test cases/unit/106 underspecified mtest/meson.build
new file mode 100644
index 000000000..c0a88d677
--- /dev/null
+++ b/test cases/unit/106 underspecified mtest/meson.build
@@ -0,0 +1,8 @@
+project('underspecified deps', 'c')
+
+runner = find_program('runner.py')
+exe1 = executable('main1', 'main.c')
+exe2 = executable('main2', 'main.c')
+
+test('runner-with-exedep', runner, args: exe1)
+test('runner-without-dep', runner, args: exe2.full_path())
diff --git a/test cases/unit/106 underspecified mtest/runner.py b/test cases/unit/106 underspecified mtest/runner.py
new file mode 100755
index 000000000..9fb9ac40b
--- /dev/null
+++ b/test cases/unit/106 underspecified mtest/runner.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python3
+
+import sys, subprocess
+
+subprocess.run(sys.argv[1:], check=True)
diff --git a/unittests/platformagnostictests.py b/unittests/platformagnostictests.py
index 2fb75f284..ef277dee2 100644
--- a/unittests/platformagnostictests.py
+++ b/unittests/platformagnostictests.py
@@ -6,6 +6,7 @@ from __future__ import annotations
import json
import os
import pickle
+import subprocess
import tempfile
import subprocess
import textwrap
@@ -508,3 +509,17 @@ class PlatformAgnosticTests(BasePlatformTests):
f.write("option('new_option', type : 'boolean', value : false)")
self.setconf('-Dsubproject:new_option=true')
self.assertEqual(self.getconf('subproject:new_option'), True)
+
+ def test_mtest_rebuild_deps(self):
+ testdir = os.path.join(self.unit_test_dir, '106 underspecified mtest')
+ self.init(testdir)
+
+ with self.assertRaises(subprocess.CalledProcessError):
+ self._run(self.mtest_command)
+ self.clean()
+
+ with self.assertRaises(subprocess.CalledProcessError):
+ self._run(self.mtest_command + ['runner-without-dep'])
+ self.clean()
+
+ self._run(self.mtest_command + ['runner-with-exedep'])