diff --git a/docs/markdown/snippets/meson_test_depends.md b/docs/markdown/snippets/meson_test_depends.md new file mode 100644 index 000000000000..09d78f1a9aa5 --- /dev/null +++ b/docs/markdown/snippets/meson_test_depends.md @@ -0,0 +1,17 @@ +## `meson test` only rebuilds test dependencies + +Until now, `meson test` rebuilt the whole project independent of the +requested tests and their dependencies. With this release, `meson test` +will only rebuild what is needed for the tests or suites that will be run. +This feature can be used, for example, to speed up bisecting regressions +using commands like the following: + + git bisect start + git bisect run meson test + +This would find the broken commit automatically while at each step +rebuilding only those pieces of code needed to run the test. + +However, this change could cause failures if dependencies are not +specified correctly in `meson.build`. + diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 47bb88b68cbe..a8ec8f3b0088 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -19,7 +19,6 @@ from .scripts import depfixer from .scripts import destdir_join from .mesonlib import is_windows, Popen_safe -from .mtest import rebuild_all from .backend.backends import InstallData from .coredata import major_versions_differ, MesonVersionMismatchException from .coredata import version as coredata_version @@ -532,6 +531,23 @@ def install_targets(self, d): else: raise +def rebuild_all(wd: str) -> bool: + if not (Path(wd) / 'build.ninja').is_file(): + print('Only ninja backend is supported to rebuild the project before installation.') + return True + + ninja = environment.detect_ninja() + if not ninja: + print("Can't find ninja, can't rebuild test.") + return False + + ret = subprocess.run(ninja + ['-C', wd]).returncode + if ret != 0: + print('Could not rebuild {}'.format(wd)) + return False + + return True + def run(opts): datafilename = 'meson-private/install.dat' private_dir = os.path.dirname(datafilename) diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index a92b5cc147f9..4f687a5fc1b5 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -45,6 +45,7 @@ from .coredata import version as coredata_version from .dependencies import ExternalProgram from .mesonlib import MesonException, get_wine_shortpath, split_args, join_args +from .mintro import get_infodir, load_info_file from .backend.backends import TestProtocol, TestSerialisation # GNU autotools interprets a return code of 77 from tests it executes to @@ -1019,13 +1020,21 @@ def print_collected_logs(self) -> None: def total_failure_count(self) -> int: return self.fail_count + self.unexpectedpass_count + self.timeout_count - def doit(self) -> int: + def doit(self, options: argparse.Namespace) -> int: if self.is_run: raise RuntimeError('Test harness object can only be used once.') self.is_run = True tests = self.get_tests() if not tests: return 0 + if not options.no_rebuild and not rebuild_deps(options.wd, tests): + # 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 + # bisect without needing to handle the does-not-build case separately in a + # wrapper script. + sys.exit(125) + self.run_tests(tests) return self.total_failure_count() @@ -1272,7 +1281,7 @@ def list_tests(th: TestHarness) -> bool: print(th.get_pretty_suite(t)) return not tests -def rebuild_all(wd: str) -> bool: +def rebuild_deps(wd: str, tests: T.List[TestSerialisation]) -> bool: if not (Path(wd) / 'build.ninja').is_file(): print('Only ninja backend is supported to rebuild tests before running them.') return True @@ -1282,7 +1291,21 @@ def rebuild_all(wd: str) -> bool: print("Can't find ninja, can't rebuild test.") return False - ret = subprocess.run(ninja + ['-C', wd]).returncode + depends = set() # type: T.Set[str] + targets = set() # type: T.Set[str] + intro_targets = dict() # type: T.Dict[str, T.List[str]] + for target in load_info_file(get_infodir(wd), kind='targets'): + intro_targets[target['id']] = [ + os.path.relpath(f, wd) + 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]) + + ret = subprocess.run(ninja + ['-C', wd] + sorted(targets)).returncode if ret != 0: print('Could not rebuild {}'.format(wd)) return False @@ -1318,18 +1341,11 @@ def run(options: argparse.Namespace) -> int: print('Could not find requested program: {!r}'.format(check_bin)) return 1 - if not options.list and not options.no_rebuild: - if not rebuild_all(options.wd): - # 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 bisect without needing to handle the does-not-build case separately in a wrapper script. - return 125 - with TestHarness(options) as th: try: if options.list: return list_tests(th) - return th.doit() + return th.doit(options) except TestException as e: print('Meson test encountered an error:\n') if os.environ.get('MESON_FORCE_BACKTRACE'):