diff --git a/docs/markdown/Reference-manual.md b/docs/markdown/Reference-manual.md index 74e7ff2f5..32639b0f1 100644 --- a/docs/markdown/Reference-manual.md +++ b/docs/markdown/Reference-manual.md @@ -1256,6 +1256,13 @@ Keyword arguments are the following: - `workdir` absolute path that will be used as the working directory for the test +- `depends` specifies that this test depends on the specified + target(s), even though it does not take any of them as a command + line argument. This is meant for cases where test finds those + targets internally, e.g. plugins or globbing. Those targets are built + before test is executed even if they have `build_by_default : false`. + Since 0.46.0 + Defined tests can be run in a backend-agnostic way by calling `meson test` inside the build dir, or by using backend-specific commands, such as `ninja test` or `msbuild RUN_TESTS.vcxproj`. diff --git a/docs/markdown/snippets/test-depends.md b/docs/markdown/snippets/test-depends.md new file mode 100644 index 000000000..2f4b6b0a6 --- /dev/null +++ b/docs/markdown/snippets/test-depends.md @@ -0,0 +1,5 @@ +## test now supports depends keyword parameter + +Build targets and custom targets can be listed in depends argument of test +function. These targets will be built before test is run even if they have +`build_by_default : false`. diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 694700e2e..8f75dacf9 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -721,6 +721,9 @@ class Backend: if not isinstance(arg, (build.CustomTarget, build.BuildTarget)): continue result[arg.get_id()] = arg + for dep in t.depends: + assert isinstance(dep, (build.CustomTarget, build.BuildTarget)) + result[dep.get_id()] = dep return result def get_custom_target_provided_libraries(self, target): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index c7841a890..a4c93de39 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -697,12 +697,14 @@ class RunTargetHolder(InterpreterObject, ObjectHolder): return r.format(self.__class__.__name__, h.get_id(), h.command) class Test(InterpreterObject): - def __init__(self, name, project, suite, exe, is_parallel, cmd_args, env, should_fail, timeout, workdir): + def __init__(self, name, project, suite, exe, depends, is_parallel, + cmd_args, env, should_fail, timeout, workdir): InterpreterObject.__init__(self) self.name = name self.suite = suite self.project_name = project self.exe = exe + self.depends = depends self.is_parallel = is_parallel self.cmd_args = cmd_args self.env = env @@ -1555,7 +1557,7 @@ permitted_kwargs = {'add_global_arguments': {'language'}, 'library': known_library_kwargs, 'subdir': {'if_found'}, 'subproject': {'version', 'default_options'}, - 'test': {'args', 'env', 'is_parallel', 'should_fail', 'timeout', 'workdir', 'suite'}, + 'test': {'args', 'depends', 'env', 'is_parallel', 'should_fail', 'timeout', 'workdir', 'suite'}, 'vcs_tag': {'input', 'output', 'fallback', 'command', 'replace_string'}, } @@ -2734,7 +2736,7 @@ root and issuing %s. if 'command' not in kwargs: raise InterpreterException('Missing "command" keyword argument') all_args = extract_as_list(kwargs, 'command') - deps = extract_as_list(kwargs, 'depends') + deps = extract_as_list(kwargs, 'depends', unholder=True) else: raise InterpreterException('Run_target needs at least one positional argument.') @@ -2749,10 +2751,6 @@ root and issuing %s. raise InterpreterException('First argument must be a string.') cleaned_deps = [] for d in deps: - try: - d = d.held_object - except AttributeError: - pass if not isinstance(d, (build.BuildTarget, build.CustomTarget)): raise InterpreterException('Depends items must be build targets.') cleaned_deps.append(d) @@ -2835,7 +2833,12 @@ root and issuing %s. if len(s) > 0: s = ':' + s suite.append(prj.replace(' ', '_').replace(':', '_') + s) - t = Test(args[0], prj, suite, exe.held_object, par, cmd_args, env, should_fail, timeout, workdir) + depends = extract_as_list(kwargs, 'depends', unholder=True) + for dep in depends: + if not isinstance(dep, (build.CustomTarget, build.BuildTarget)): + raise InterpreterException('Depends items must be build targets.') + t = Test(args[0], prj, suite, exe.held_object, depends, par, cmd_args, + env, should_fail, timeout, workdir) if is_base_test: self.build.tests.append(t) mlog.debug('Adding test "', mlog.bold(args[0]), '".', sep='') @@ -3152,11 +3155,9 @@ different subdirectory. if ":" not in setup_name: setup_name = (self.subproject if self.subproject else self.build.project_name) + ":" + setup_name try: - inp = extract_as_list(kwargs, 'exe_wrapper') + inp = extract_as_list(kwargs, 'exe_wrapper', unholder=True) exe_wrapper = [] for i in inp: - if hasattr(i, 'held_object'): - i = i.held_object if isinstance(i, str): exe_wrapper.append(i) elif isinstance(i, dependencies.ExternalProgram): diff --git a/test cases/common/188 test depends/gen.py b/test cases/common/188 test depends/gen.py new file mode 100755 index 000000000..ee4ed9818 --- /dev/null +++ b/test cases/common/188 test depends/gen.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 + +import sys + + +def main(): + with open(sys.argv[1], 'w') as out: + out.write(sys.argv[2]) + out.write('\n') + + +if __name__ == '__main__': + main() diff --git a/test cases/common/188 test depends/main.c b/test cases/common/188 test depends/main.c new file mode 100644 index 000000000..78f2de106 --- /dev/null +++ b/test cases/common/188 test depends/main.c @@ -0,0 +1 @@ +int main(void) { return 0; } diff --git a/test cases/common/188 test depends/meson.build b/test cases/common/188 test depends/meson.build new file mode 100644 index 000000000..888c45118 --- /dev/null +++ b/test cases/common/188 test depends/meson.build @@ -0,0 +1,26 @@ +project('test depends', 'c') + +gen = find_program('gen.py') + +custom_dep = custom_target('custom_dep', + build_by_default : false, + output : 'custom_dep.txt', + command : [gen, '@OUTPUT@', 'custom_dep'], +) + +exe_dep = executable('exe_dep', 'main.c', + build_by_default : false, +) + +test_prog = find_program('test.py') +test('string dependencies', test_prog, + args : [ + # This is declared for convenience, + # real use case might have some obscure method + # to find these dependencies, e.g. automatic plugin loading. + 'custom_dep.txt', + exe_dep.full_path(), + ], + depends : [custom_dep, exe_dep], + workdir : meson.current_build_dir(), +) diff --git a/test cases/common/188 test depends/test.py b/test cases/common/188 test depends/test.py new file mode 100755 index 000000000..5b9f65c86 --- /dev/null +++ b/test cases/common/188 test depends/test.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +import os +import os.path +import sys + + +def main(): + print('Looking in:', os.getcwd()) + not_found = list() + for f in sys.argv[1:]: + if not os.path.exists(f): + not_found.append(f) + if not_found: + print('Not found:', ', '.join(not_found)) + sys.exit(1) + + +if __name__ == '__main__': + main()