Repository: mesonbuild/meson

Issue:
Native tests in crossbuild
**Describe the bug**
In some cases useful is build and run tests on build system without any wrappers. It not possible by now, because information about "crossing" of binary builds [from environment](https://github.com/mesonbuild/meson/blob/021d242f9ce8e6cd804af0c1eb4179b8c83fd470/mesonbuild/backend/backends.py#L848). It can be getted from `executable` flag `native` (if `true` test can be runned without wrappers). By now if test `executable` have flag `native: true` it builds for build machine, but [`'Not run because can not execute cross compiled binaries.'`](https://github.com/mesonbuild/meson/blob/c9b7e5bd403971a268530838b25a051ab6310fef/mesonbuild/mtest.py#L642)

**To Reproduce**
`app.cpp`
```cpp
#include <cstdio>
#include "func.h"
int main(int argc, char** argv) { printf("func: %d\n", func(10, 32)); }
```
`func.d`
```d
module func;
extern (C) int func(int a, int b) nothrow @nogc { return a + b; }
unittest { assert (func(10, 12) == 10 + 12); }
```
`func.h`
```cpp
extern "C" int func(int, int);
```
`meson.build`
```
project('app')
add_languages('c', native: false)
add_languages('cpp', native: false)
add_languages('d', native: false)
add_languages('d', native: true)
c_args = ['-fstack-protector-strong', '-fno-plt', '-fno-lto', '-Wno-psabi']
add_global_arguments(c_args, native: false, language: 'c')
add_global_arguments(c_args, native: false, language: 'cpp')
c_link_args = ['-Wl,-O1,-z,relro,-z,now', '-fno-lto']
add_global_link_arguments(c_link_args, native: false, language: 'c')
add_global_link_arguments(c_link_args, native: false, language: 'cpp')
phobos2 = meson.get_compiler('cpp').find_library('phobos2-ldc-shared', static: false, dirs: '/d/ldc2/arm-lib/')
druntime = meson.get_compiler('cpp').find_library('druntime-ldc-shared', static: false, dirs: '/d/ldc2/arm-lib/')
d_dep = declare_dependency(dependencies: [ phobos2, druntime ])
lib = static_library('lib', 'func.d', dependencies: d_dep, native: false)
lib_dep = declare_dependency(link_with: [lib], dependencies: d_dep, include_directories: include_directories('.'))
app_exe = executable('app', 'app.cpp', dependencies: lib_dep, native: false, link_language: 'cpp')
general_test = executable('test_bin', 'func.d', d_unittest: true, native: true, d_args: ['-main'])
test('general', general_test)
```
`cross.ini`
```
[constants]
sysroot = '/sysroot'
toolchain_prefix = '/arm-gcc/bin/arm-linux-gnueabihf-'
dtriple = 'armv7l-linux-gnueabihf'
[properties]
sys_root = sysroot
pkg_config_libdir = sysroot / 'usr/lib/pkgconfig'
[built-in options]
c_args = ['-mcpu=cortex-a7',
          '-mfloat-abi=hard',
          '-mfpu=vfpv4',
          '--sysroot='+sysroot,
          '-L' + sysroot / 'usr/lib'
         ]
cpp_args = c_args
d_args = ['-mtriple='+dtriple ]
cpp_std = 'c++14'
[host_machine]
system = 'linux'
cpu_family = 'arm'
cpu = 'armv7hl'
endian = 'little'
[binaries]
d = 'ldc2'
c = toolchain_prefix + 'gcc'
c_ld = 'gold'
cpp = toolchain_prefix + 'g++'
cpp_ld = 'gold'
ar = toolchain_prefix + 'ar'
strip = toolchain_prefix + 'strip'
pkgconfig = 'pkg-config'
```
steps:

1. `meson _bld --cross-file=cross.ini`
2. `cd _bld && meson test`

result:
```
ninja: Entering directory `/workdir/_bld'
[6/6] Linking target test_bin
1/1 general SKIP           0.00s

Summary of Failures:

1/1 general SKIP           0.00s

Ok:                 0   
Expected Fail:      0   
Fail:               0   
Unexpected Pass:    0   
Skipped:            1   
Timeout:            0   

Full log written to /workdir/_bld/meson-logs/testlog.txt
```
`testlog.txt`
```
...
--- command ---
NONE
--- stdout ---
Not run because can not execute cross compiled binaries.
-------
...
```

**Expected behavior**
Execute native tests on build machine without wrappers.

**system parameters**
* Is this a `cross build`
* fedora 32
* `python --version` is `3.8.6`
* `meson --version` is `0.56.0`
* `ninja --version` is `1.10.1`



Discussion:
**Minimal example**
meson.build
```
project('check native tests', 'c')

executable('no-file-offset-bits', 'test.c')
e = executable('test_exec', 'test.c', native: true)
test('test_ex', e)
```

test.c:
```
int main(int argc, char *argv[])
{
  return 0;
}
```

crossfile.txt
```
[binaries]
# we could set exe_wrapper = qemu-arm-static but to test the case
# when cross compiled binaries can't be run we don't do that
c = '/usr/bin/arm-linux-gnueabihf-gcc'
cpp = '/usr/bin/arm-linux-gnueabihf-g++'
rust = ['rustc', '--target', 'arm-unknown-linux-gnueabihf', '-C', 'linker=/usr/bin/arm-linux-gnueabihf-gcc-7']
ar = '/usr/arm-linux-gnueabihf/bin/ar'
strip = '/usr/arm-linux-gnueabihf/bin/strip'
pkgconfig = '/usr/bin/arm-linux-gnueabihf-pkg-config'

[properties]
root = '/usr/arm-linux-gnueabihf'
c_args = ['-U_FILE_OFFSET_BITS']

[host_machine]
system = 'linux'
cpu_family = 'arm'
cpu = 'armv7' # Not sure if correct.
endian = 'little'
```

**expected**
`ninja test` run  `test_exec`

**actual**
`ninja test` skiped  `test_exec` 
`Not run because can not execute cross compiled binaries.`

https://github.com/mesonbuild/meson/blob/1db800bf67fc80abee313381aac0528ee33103c9/mesonbuild/mtest.py#L653
maybe need add check ` self.test.is_cross_built` to this line, like 
```
        elif self.test.cmd_is_built and self.test.is_cross_built and self.test.needs_exe_wrapper:
```

Resolving patch:
diff --git a/mesonbuild/mtest.py b/mesonbuild/mtest.py
--- a/mesonbuild/mtest.py
+++ b/mesonbuild/mtest.py
@@ -650,7 +650,7 @@ def _get_cmd(self) -> T.Optional[T.List[str]]:
             return ['java', '-jar'] + self.test.fname
         elif not self.test.is_cross_built and run_with_mono(self.test.fname[0]):
             return ['mono'] + self.test.fname
-        elif self.test.cmd_is_built and self.test.needs_exe_wrapper:
+        elif self.test.cmd_is_built and self.test.is_cross_built and self.test.needs_exe_wrapper:
             if self.test.exe_runner is None:
                 # Can not run test on cross compiled executable
                 # because there is no execute wrapper.
diff --git a/run_unittests.py b/run_unittests.py
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -7507,6 +7507,19 @@ def test_pkg_config_option(self):
             '-Dbuild.pkg_config_path=' + os.path.join(testdir, 'build_extra_path'),
             '-Dpkg_config_path=' + os.path.join(testdir, 'host_extra_path'),
         ])
+    
+    def test_run_native_test(self):
+        '''
+        https://github.com/mesonbuild/meson/issues/7997
+        check run native test in crossbuild without exe wrapper
+        '''
+        testdir = os.path.join(self.unit_test_dir, '88 run native test')
+        stamp_file = os.path.join(self.builddir, 'native_test_has_run.stamp')
+        self.init(testdir)
+        self.build()
+        self.assertPathDoesNotExist(stamp_file)
+        self.run_tests()
+        self.assertPathExists(stamp_file)
 
 
 def should_run_cross_mingw_tests():


Test patch:
diff --git a/test cases/unit/88 run native test/main.c b/test cases/unit/88 run native test/main.c
new file mode 100644
--- /dev/null
+++ b/test cases/unit/88 run native test/main.c	
@@ -0,0 +1,17 @@
+#include <stdio.h>
+
+int main (int argc, char * argv[])
+{
+  const char *out = "SUCCESS!";
+
+  if (argc != 2) {
+    printf ("%s\n", out);
+  } else {
+    int ret;
+    FILE *f = fopen (argv[1], "w");
+    ret = fwrite (out, sizeof (out), 1, f);
+    if (ret != 1)
+      return -1;
+  }
+  return 0;
+}
diff --git a/test cases/unit/88 run native test/meson.build b/test cases/unit/88 run native test/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/unit/88 run native test/meson.build	
@@ -0,0 +1,6 @@
+project('run native test', ['c'])
+
+executable('terget_exe', 'main.c')
+
+native_exe = executable('native_exe', 'main.c', native: true)
+test('native_exe', native_exe, args: ['native_test_has_run.stamp'])