aboutsummaryrefslogtreecommitdiff
path: root/run_unittests.py
diff options
context:
space:
mode:
Diffstat (limited to 'run_unittests.py')
-rwxr-xr-xrun_unittests.py302
1 files changed, 300 insertions, 2 deletions
diff --git a/run_unittests.py b/run_unittests.py
index d3545db..bc11732 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -26,6 +26,7 @@ import sys
import unittest
import platform
import pickle
+import functools
from itertools import chain
from unittest import mock
from configparser import ConfigParser
@@ -41,7 +42,7 @@ import mesonbuild.modules.gnome
from mesonbuild.interpreter import Interpreter, ObjectHolder
from mesonbuild.mesonlib import (
is_windows, is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku,
- windows_proof_rmtree, python_command, version_compare,
+ is_linux, windows_proof_rmtree, python_command, version_compare,
BuildDirLock, Version
)
from mesonbuild.environment import detect_ninja
@@ -108,12 +109,41 @@ def skipIfNoPkgconfig(f):
Note: Yes, we provide pkg-config even while running Windows CI
'''
+ @functools.wraps(f)
def wrapped(*args, **kwargs):
if not is_ci() and shutil.which('pkg-config') is None:
raise unittest.SkipTest('pkg-config not found')
return f(*args, **kwargs)
return wrapped
+def skip_if_not_language(lang):
+ def wrapper(func):
+ @functools.wraps(func)
+ def wrapped(*args, **kwargs):
+ try:
+ env = get_fake_env('', '', '')
+ f = getattr(env, 'detect_{}_compiler'.format(lang))
+ if lang in ['cs', 'vala', 'java', 'swift']:
+ f()
+ else:
+ f(False)
+ except EnvironmentException:
+ raise unittest.SkipTest('No {} compiler found.'.format(lang))
+ return func(*args, **kwargs)
+ return wrapped
+ return wrapper
+
+def skip_if_env_value(value):
+ def wrapper(func):
+ @functools.wraps(func)
+ def wrapped(*args, **kwargs):
+ if value in os.environ:
+ raise unittest.SkipTest(
+ 'Environment variable "{}" set, skipping.'.format(value))
+ return func(*args, **kwargs)
+ return wrapped
+ return wrapper
+
class PatchModule:
'''
Fancy monkey-patching! Whee! Can't use mock.patch because it only
@@ -4446,6 +4476,273 @@ class RewriterTests(unittest.TestCase):
self.assertEqual(s2, self.read_contents('sub2/meson.build'))
+class NativeFileTests(BasePlatformTests):
+
+ def setUp(self):
+ super().setUp()
+ self.testcase = os.path.join(self.unit_test_dir, '46 native file binary')
+ self.current_config = 0
+ self.current_wrapper = 0
+
+ def helper_create_native_file(self, values):
+ """Create a config file as a temporary file.
+
+ values should be a nested dictionary structure of {section: {key:
+ value}}
+ """
+ filename = os.path.join(self.builddir, 'generated{}.config'.format(self.current_config))
+ self.current_config += 1
+ with open(filename, 'wt') as f:
+ for section, entries in values.items():
+ f.write('[{}]\n'.format(section))
+ for k, v in entries.items():
+ f.write("{}='{}'\n".format(k, v))
+ return filename
+
+ def helper_create_binary_wrapper(self, binary, **kwargs):
+ """Creates a wrapper around a binary that overrides specific values."""
+ filename = os.path.join(self.builddir, 'binary_wrapper{}.py'.format(self.current_wrapper))
+ self.current_wrapper += 1
+ if is_haiku():
+ chbang = '#!/bin/env python3'
+ else:
+ chbang = '#!/usr/bin/env python3'
+
+ with open(filename, 'wt') as f:
+ f.write(textwrap.dedent('''\
+ {}
+ import argparse
+ import subprocess
+ import sys
+
+ def main():
+ parser = argparse.ArgumentParser()
+ '''.format(chbang)))
+ for name in kwargs:
+ f.write(' parser.add_argument("-{0}", "--{0}", action="store_true")\n'.format(name))
+ f.write(' args, extra_args = parser.parse_known_args()\n')
+ for name, value in kwargs.items():
+ f.write(' if args.{}:\n'.format(name))
+ f.write(' print("{}", file=sys.{})\n'.format(value, kwargs.get('outfile', 'stdout')))
+ f.write(' sys.exit(0)\n')
+ f.write(textwrap.dedent('''
+ ret = subprocess.run(
+ ["{}"] + extra_args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ encoding='utf-8')
+ print(ret.stdout)
+ print(ret.stderr, file=sys.stderr)
+ sys.exit(ret.returncode)
+
+ if __name__ == '__main__':
+ main()
+ '''.format(binary)))
+
+ if not is_windows():
+ os.chmod(filename, 0o755)
+ return filename
+
+ # On windows we need yet another level of indirection, as cmd cannot
+ # invoke python files itself, so instead we generate a .bat file, which
+ # invokes our python wrapper
+ batfile = os.path.join(self.builddir, 'binary_wrapper{}.bat'.format(self.current_wrapper))
+ with open(batfile, 'wt') as f:
+ f.write('py -3 {} %*'.format(filename))
+ return batfile
+
+ def helper_for_compiler(self, lang, cb):
+ """Helper for generating tests for overriding compilers for langaugages
+ with more than one implementation, such as C, C++, ObjC, ObjC++, and D.
+ """
+ env = get_fake_env('', '', '')
+ getter = getattr(env, 'detect_{}_compiler'.format(lang))
+ if lang not in ['cs']:
+ getter = functools.partial(getter, False)
+ cc = getter()
+ binary, newid = cb(cc)
+ env.config_info.binaries = {lang: binary}
+ compiler = getter()
+ self.assertEqual(compiler.id, newid)
+
+ def test_multiple_native_files_override(self):
+ wrapper = self.helper_create_binary_wrapper('bash', version='foo')
+ config = self.helper_create_native_file({'binaries': {'bash': wrapper}})
+ wrapper = self.helper_create_binary_wrapper('bash', version='12345')
+ config2 = self.helper_create_native_file({'binaries': {'bash': wrapper}})
+ self.init(self.testcase, extra_args=[
+ '--native-file', config, '--native-file', config2,
+ '-Dcase=find_program'])
+
+ def test_multiple_native_files(self):
+ wrapper = self.helper_create_binary_wrapper('bash', version='12345')
+ config = self.helper_create_native_file({'binaries': {'bash': wrapper}})
+ wrapper = self.helper_create_binary_wrapper('python')
+ config2 = self.helper_create_native_file({'binaries': {'python': wrapper}})
+ self.init(self.testcase, extra_args=[
+ '--native-file', config, '--native-file', config2,
+ '-Dcase=find_program'])
+
+ def _simple_test(self, case, binary):
+ wrapper = self.helper_create_binary_wrapper(binary, version='12345')
+ config = self.helper_create_native_file({'binaries': {binary: wrapper}})
+ self.init(self.testcase, extra_args=['--native-file', config, '-Dcase={}'.format(case)])
+
+ def test_find_program(self):
+ self._simple_test('find_program', 'bash')
+
+ def test_config_tool_dep(self):
+ # Do the skip at this level to avoid screwing up the cache
+ if not shutil.which('llvm-config'):
+ raise unittest.SkipTest('No llvm-installed, cannot test')
+ self._simple_test('config_dep', 'llvm-config')
+
+ def test_python3_module(self):
+ self._simple_test('python3', 'python3')
+
+ def test_python_module(self):
+ if is_windows():
+ # Bat adds extra crap to stdout, so the version check logic in the
+ # python module breaks. This is fine on other OSes because they
+ # don't need the extra indirection.
+ raise unittest.SkipTest('bat indirection breaks internal sanity checks.')
+ self._simple_test('python', 'python')
+
+ @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard')
+ @skip_if_env_value('CC')
+ def test_c_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if not shutil.which('clang'):
+ raise unittest.SkipTest('Only one compiler found, cannot test.')
+ return 'clang', 'clang'
+ if not shutil.which('gcc'):
+ raise unittest.SkipTest('Only one compiler found, cannot test.')
+ return 'gcc', 'gcc'
+ self.helper_for_compiler('c', cb)
+
+ @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard')
+ @skip_if_env_value('CXX')
+ def test_cpp_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if not shutil.which('clang++'):
+ raise unittest.SkipTest('Only one compiler found, cannot test.')
+ return 'clang++', 'clang'
+ if not shutil.which('g++'):
+ raise unittest.SkipTest('Only one compiler found, cannot test.')
+ return 'g++', 'gcc'
+ self.helper_for_compiler('cpp', cb)
+
+ @skip_if_not_language('objc')
+ @skip_if_env_value('OBJC')
+ def test_objc_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if not shutil.which('clang'):
+ raise unittest.SkipTest('Only one compiler found, cannot test.')
+ return 'clang', 'clang'
+ if not shutil.which('gcc'):
+ raise unittest.SkipTest('Only one compiler found, cannot test.')
+ return 'gcc', 'gcc'
+ self.helper_for_compiler('objc', cb)
+
+ @skip_if_not_language('objcpp')
+ @skip_if_env_value('OBJCXX')
+ def test_objcpp_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if not shutil.which('clang++'):
+ raise unittest.SkipTest('Only one compiler found, cannot test.')
+ return 'clang++', 'clang'
+ if not shutil.which('g++'):
+ raise unittest.SkipTest('Only one compiler found, cannot test.')
+ return 'g++', 'gcc'
+ self.helper_for_compiler('objcpp', cb)
+
+ @skip_if_not_language('d')
+ @skip_if_env_value('DC')
+ def test_d_compiler(self):
+ def cb(comp):
+ if comp.id == 'dmd':
+ if shutil.which('ldc'):
+ return 'ldc', 'ldc'
+ elif shutil.which('gdc'):
+ return 'gdc', 'gdc'
+ else:
+ raise unittest.SkipTest('No alternative dlang compiler found.')
+ return 'dmd', 'dmd'
+ self.helper_for_compiler('d', cb)
+
+ @skip_if_not_language('cs')
+ @skip_if_env_value('CSC')
+ def test_cs_compiler(self):
+ def cb(comp):
+ if comp.id == 'csc':
+ if not shutil.which('mcs'):
+ raise unittest.SkipTest('No alternate C# implementation.')
+ return 'mcs', 'mcs'
+ if not shutil.which('csc'):
+ raise unittest.SkipTest('No alternate C# implementation.')
+ return 'csc', 'csc'
+ self.helper_for_compiler('cs', cb)
+
+ @skip_if_not_language('fortran')
+ @skip_if_env_value('FC')
+ def test_fortran_compiler(self):
+ def cb(comp):
+ if comp.id == 'gcc':
+ if shutil.which('ifort'):
+ return 'ifort', 'intel'
+ # XXX: there are several other fortran compilers meson
+ # supports, but I don't have any of them to test with
+ raise unittest.SkipTest('No alternate Fortran implementation.')
+ if not shutil.which('gfortran'):
+ raise unittest.SkipTest('No alternate C# implementation.')
+ return 'gfortran', 'gcc'
+ self.helper_for_compiler('fortran', cb)
+
+ def _single_implementation_compiler(self, lang, binary, version_str, version):
+ """Helper for languages with a single (supported) implementation.
+
+ Builds a wrapper around the compiler to override the version.
+ """
+ wrapper = self.helper_create_binary_wrapper(binary, version=version_str)
+ env = get_fake_env('', '', '')
+ getter = getattr(env, 'detect_{}_compiler'.format(lang))
+ if lang in ['rust']:
+ getter = functools.partial(getter, False)
+ env.config_info.binaries = {lang: wrapper}
+ compiler = getter()
+ self.assertEqual(compiler.version, version)
+
+ @skip_if_not_language('vala')
+ @skip_if_env_value('VALAC')
+ def test_vala_compiler(self):
+ self._single_implementation_compiler(
+ 'vala', 'valac', 'Vala 1.2345', '1.2345')
+
+ @skip_if_not_language('rust')
+ @skip_if_env_value('RUSTC')
+ def test_rust_compiler(self):
+ self._single_implementation_compiler(
+ 'rust', 'rustc', 'rustc 1.2345', '1.2345')
+
+ @skip_if_not_language('java')
+ def test_java_compiler(self):
+ self._single_implementation_compiler(
+ 'java', 'javac', 'javac 9.99.77', '9.99.77')
+
+ @skip_if_not_language('swift')
+ def test_swift_compiler(self):
+ wrapper = self.helper_create_binary_wrapper(
+ 'swiftc', version='Swift 1.2345', outfile='stderr')
+ env = get_fake_env('', '', '')
+ env.config_info.binaries = {'swift': wrapper}
+ compiler = env.detect_swift_compiler()
+ self.assertEqual(compiler.version, '1.2345')
+
+
def unset_envs():
# For unit tests we must fully control all command lines
# so that there are no unexpected changes coming from the
@@ -4463,7 +4760,8 @@ def should_run_cross_mingw_tests():
def main():
unset_envs()
- cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', 'PythonTests']
+ cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests',
+ 'PythonTests', 'NativeFileTests']
if not is_windows():
cases += ['LinuxlikeTests']
if should_run_cross_arm_tests():