diff --git a/docs/markdown/snippets/test_using_custom_target_executable.md b/docs/markdown/snippets/test_using_custom_target_executable.md new file mode 100644 index 000000000..e8b08da6b --- /dev/null +++ b/docs/markdown/snippets/test_using_custom_target_executable.md @@ -0,0 +1,4 @@ +## Use `custom_target` as test executable + +The [[test]] function now accepts [[@custom_tgt]] and [[@custom_idx]] for the +command to execute. diff --git a/docs/yaml/functions/benchmark.yaml b/docs/yaml/functions/benchmark.yaml index 9d0ca3116..0323b26e4 100644 --- a/docs/yaml/functions/benchmark.yaml +++ b/docs/yaml/functions/benchmark.yaml @@ -22,8 +22,9 @@ posargs: description: The *unique* test id executable: - type: exe | jar | external_program | file - description: The program to execute + type: exe | jar | external_program | file | custom_tgt | custom_idx + description: | + The program to execute. *(Since 1.4.0)* A CustomTarget is also accepted. kwargs: args: diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 92cc9fb51..c3be900ab 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -1160,7 +1160,7 @@ class Backend: return results def determine_windows_extra_paths( - self, target: T.Union[build.BuildTarget, build.CustomTarget, programs.ExternalProgram, mesonlib.File, str], + self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, programs.ExternalProgram, mesonlib.File, str], extra_bdeps: T.Sequence[T.Union[build.BuildTarget, build.CustomTarget]]) -> T.List[str]: """On Windows there is no such thing as an rpath. @@ -1269,7 +1269,7 @@ class Backend: t.is_parallel, cmd_args, t_env, t.should_fail, t.timeout, t.workdir, extra_paths, t.protocol, t.priority, - isinstance(exe, build.Target), + isinstance(exe, (build.Target, build.CustomTargetIndex)), isinstance(exe, build.Executable), [x.get_id() for x in depends], self.environment.coredata.version, diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 5603b9937..aaffec0b9 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -2151,17 +2151,17 @@ class Interpreter(InterpreterBase, HoldableObject): self.generators.append(gen) return gen - @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File)) + @typed_pos_args('benchmark', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex)) @typed_kwargs('benchmark', *TEST_KWS) def func_benchmark(self, node: mparser.BaseNode, args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], kwargs: 'kwtypes.FuncBenchmark') -> None: self.add_test(node, args, kwargs, False) - @typed_pos_args('test', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File)) + @typed_pos_args('test', str, (build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex)) @typed_kwargs('test', *TEST_KWS, KwargInfo('is_parallel', bool, default=True)) def func_test(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]], kwargs: 'kwtypes.FuncTest') -> None: self.add_test(node, args, kwargs, True) @@ -2175,7 +2175,7 @@ class Interpreter(InterpreterBase, HoldableObject): return ENV_KW.convertor(envlist) def make_test(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]], kwargs: 'kwtypes.BaseTest') -> Test: name = args[0] if ':' in name: @@ -2188,6 +2188,10 @@ class Interpreter(InterpreterBase, HoldableObject): raise InvalidArguments('Tried to use not-found external program as test exe') elif isinstance(exe, mesonlib.File): exe = self.find_program_impl([exe]) + elif isinstance(exe, build.CustomTarget): + kwargs.setdefault('depends', []).append(exe) + elif isinstance(exe, build.CustomTargetIndex): + kwargs.setdefault('depends', []).append(exe.target) env = self.unpack_env_kwarg(kwargs) @@ -2218,8 +2222,11 @@ class Interpreter(InterpreterBase, HoldableObject): kwargs['verbose']) def add_test(self, node: mparser.BaseNode, - args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File]], + args: T.Tuple[str, T.Union[build.Executable, build.Jar, ExternalProgram, mesonlib.File, build.CustomTarget, build.CustomTargetIndex]], kwargs: T.Dict[str, T.Any], is_base_test: bool): + if isinstance(args[1], (build.CustomTarget, build.CustomTargetIndex)): + FeatureNew.single_use('test with CustomTarget as command', '1.4.0', self.subproject) + t = self.make_test(node, args, kwargs) if is_base_test: self.build.tests.append(t) diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 9aefc2f97..28e7170c9 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -742,7 +742,7 @@ class GeneratedObjectsHolder(ObjectHolder[build.ExtractedObjects]): class Test(MesonInterpreterObject): def __init__(self, name: str, project: str, suite: T.List[str], - exe: T.Union[ExternalProgram, build.Executable, build.CustomTarget], + exe: T.Union[ExternalProgram, build.Executable, build.CustomTarget, build.CustomTargetIndex], depends: T.List[T.Union[build.CustomTarget, build.BuildTarget]], is_parallel: bool, cmd_args: T.List[T.Union[str, mesonlib.File, build.Target]], @@ -765,7 +765,7 @@ class Test(MesonInterpreterObject): self.priority = priority self.verbose = verbose - def get_exe(self) -> T.Union[ExternalProgram, build.Executable, build.CustomTarget]: + def get_exe(self) -> T.Union[ExternalProgram, build.Executable, build.CustomTarget, build.CustomTargetIndex]: return self.exe def get_name(self) -> str: diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py index b43f165ed..21414d2c5 100644 --- a/mesonbuild/mtest.py +++ b/mesonbuild/mtest.py @@ -1458,6 +1458,11 @@ class SingleTestRunner: 'found. Please check the command and/or add it to PATH.') raise TestException(msg.format(self.test.exe_wrapper.name)) return self.test.exe_wrapper.get_command() + self.test.fname + elif self.test.cmd_is_built and not self.test.cmd_is_exe and is_windows(): + test_cmd = ExternalProgram._shebang_to_cmd(self.test.fname[0]) + if test_cmd is not None: + test_cmd += self.test.fname[1:] + return test_cmd return self.test.fname def _get_cmd(self) -> T.Optional[T.List[str]]: diff --git a/test cases/common/273 customtarget exe for test/generate.py b/test cases/common/273 customtarget exe for test/generate.py new file mode 100644 index 000000000..e66f1db70 --- /dev/null +++ b/test cases/common/273 customtarget exe for test/generate.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import os +import sys + +program = '''#!/usr/bin/env python3 + +raise SystemExit({}) +''' + +for i, a in enumerate(sys.argv[1:]): + with open(a, 'w') as f: + print(program.format(i), file=f) + os.chmod(a, 0o755) diff --git a/test cases/common/273 customtarget exe for test/meson.build b/test cases/common/273 customtarget exe for test/meson.build new file mode 100644 index 000000000..089d70dee --- /dev/null +++ b/test cases/common/273 customtarget exe for test/meson.build @@ -0,0 +1,14 @@ +project('test customtarget') + +ct1 = custom_target( + command: ['generate.py', '@OUTPUT@'], + output: 'a.py', +) +ct2 = custom_target( + command: ['generate.py', '@OUTPUT@'], + output: ['b.py', 'c.py'], +) + +test('using_custom_target', ct1) +test('using_custom_target_index', ct2[0]) +test('using_custom_target_index_1', ct2[1], should_fail: true)