Repository: mesonbuild/meson

Issue:
test() should have a depends arg
It is just useful for tests that depends upon targets not built by default:

```meson
foo = custom_target(...)

test(..., depends: foo)
```


Discussion:

Resolving patch:
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -709,6 +709,9 @@ def get_build_by_default_targets(self):
                 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
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -653,12 +653,14 @@ def __repr__(self):
         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
@@ -1512,7 +1514,7 @@ def get_cross_property_method(self, args, kwargs):
                     'static_library': stlib_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'},
                     }
 
@@ -2624,7 +2626,7 @@ def func_run_target(self, node, args, kwargs):
             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.')
 
@@ -2639,10 +2641,6 @@ def func_run_target(self, node, args, kwargs):
             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)
@@ -2725,7 +2723,12 @@ def add_test(self, node, args, kwargs, is_base_test):
             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='')
@@ -3032,11 +3035,9 @@ def func_add_test_setup(self, node, args, kwargs):
         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):


Test patch:
diff --git a/test cases/common/188 test depends/gen.py b/test cases/common/188 test depends/gen.py
new file mode 100755
--- /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
--- /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
--- /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
--- /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()