From 7eb4c231561dfc5a322814e587b9b89969745367 Mon Sep 17 00:00:00 2001 From: Xavier Claessens Date: Sat, 24 Jul 2021 18:31:45 -0400 Subject: Split run_unittests.py file --- run_unittests.py | 10223 +---------------------------------------------------- 1 file changed, 14 insertions(+), 10209 deletions(-) (limited to 'run_unittests.py') diff --git a/run_unittests.py b/run_unittests.py index 8306782..2421e8d 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -19,31 +19,9 @@ import sys sys.modules['pathlib'] = _pathlib import time -import stat import subprocess -import re -import json -import tempfile -import textwrap import os -import shutil import unittest -import platform -import pickle -import functools -import io -import operator -import threading -import zipfile, tarfile -import hashlib -from itertools import chain -from unittest import mock -from configparser import ConfigParser -from contextlib import contextmanager -from glob import glob -from pathlib import (PurePath, Path) -from distutils.dir_util import copy_tree -import typing as T import mesonbuild.mlog import mesonbuild.depfile @@ -55,10200 +33,27 @@ import mesonbuild.environment import mesonbuild.mesonlib import mesonbuild.coredata import mesonbuild.modules.gnome -from mesonbuild.interpreter import Interpreter -from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments, ObjectHolder -from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments, typed_kwargs, ContainerTypeInfo, KwargInfo -from mesonbuild.ast import AstInterpreter from mesonbuild.mesonlib import ( - BuildDirLock, LibType, MachineChoice, PerMachine, Version, is_windows, - is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, is_sunos, - windows_proof_rmtree, windows_proof_rm, python_command, - version_compare, split_args, quote_arg, relpath, is_linux, git, - search_version + python_command ) -from mesonbuild.compilers import ( - detect_static_linker, detect_c_compiler, detect_cpp_compiler, - detect_objc_compiler, detect_objcpp_compiler, detect_d_compiler, - detect_swift_compiler, compiler_from_language -) -from mesonbuild.environment import detect_ninja -from mesonbuild.mesonlib import MesonException, EnvironmentException, OptionKey -from mesonbuild.dependencies import PkgConfigDependency -from mesonbuild.programs import ExternalProgram import mesonbuild.dependencies.base -from mesonbuild.build import Target, ConfigurationData import mesonbuild.modules.pkgconfig -from mesonbuild.scripts import destdir_join -from mesonbuild.mtest import TAPParser, TestResult from mesonbuild.mesonmain import setup_vsenv -from mesonbuild.wrap.wrap import PackageDefinition, WrapException - -from run_tests import ( - Backend, FakeBuild, FakeCompilerOptions, - ensure_backend_detects_changes, exe_suffix, get_backend_commands, - get_builddir_target_args, get_fake_env, get_fake_options, get_meson_script, - run_configure_inprocess, run_mtest_inprocess -) - -if T.TYPE_CHECKING: - from mesonbuild.compilers import Compiler - -URLOPEN_TIMEOUT = 5 - -@contextmanager -def chdir(path: str): - curdir = os.getcwd() - os.chdir(path) - try: - yield - finally: - os.chdir(curdir) - - -def get_dynamic_section_entry(fname: str, entry: str) -> T.Optional[str]: - if is_cygwin() or is_osx(): - raise unittest.SkipTest('Test only applicable to ELF platforms') - - try: - raw_out = subprocess.check_output(['readelf', '-d', fname], - universal_newlines=True) - except FileNotFoundError: - # FIXME: Try using depfixer.py:Elf() as a fallback - raise unittest.SkipTest('readelf not found') - pattern = re.compile(entry + r': \[(.*?)\]') - for line in raw_out.split('\n'): - m = pattern.search(line) - if m is not None: - return str(m.group(1)) - return None # The file did not contain the specified entry. - -def get_soname(fname: str) -> T.Optional[str]: - return get_dynamic_section_entry(fname, 'soname') - -def get_rpath(fname: str) -> T.Optional[str]: - raw = get_dynamic_section_entry(fname, r'(?:rpath|runpath)') - # Get both '' and None here - if not raw: - return None - # nix/nixos adds a bunch of stuff to the rpath out of necessity that we - # don't check for, so clear those - final = ':'.join([e for e in raw.split(':') if not e.startswith('/nix')]) - return final - -def is_tarball(): - if not os.path.isdir('docs'): - return True - return False - -def is_ci(): - if 'CI' in os.environ: - return True - return False - -def _git_init(project_dir): - # If a user has git configuration init.defaultBranch set we want to override that - with tempfile.TemporaryDirectory() as d: - out = git(['--version'], str(d))[1] - if version_compare(search_version(out), '>= 2.28'): - extra_cmd = ['--initial-branch', 'master'] - else: - extra_cmd = [] - - subprocess.check_call(['git', 'init'] + extra_cmd, cwd=project_dir, stdout=subprocess.DEVNULL) - subprocess.check_call(['git', 'config', - 'user.name', 'Author Person'], cwd=project_dir) - subprocess.check_call(['git', 'config', - 'user.email', 'teh_coderz@example.com'], cwd=project_dir) - _git_add_all(project_dir) - -def _git_add_all(project_dir): - subprocess.check_call('git add *', cwd=project_dir, shell=True, - stdout=subprocess.DEVNULL) - subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir, - stdout=subprocess.DEVNULL) - -@functools.lru_cache() -def is_real_gnu_compiler(path): - ''' - Check if the gcc we have is a real gcc and not a macOS wrapper around clang - ''' - if not path: - return False - out = subprocess.check_output([path, '--version'], universal_newlines=True, stderr=subprocess.STDOUT) - return 'Free Software Foundation' in out - -def skipIfNoExecutable(exename): - ''' - Skip this test if the given executable is not found. - ''' - def wrapper(func): - @functools.wraps(func) - def wrapped(*args, **kwargs): - if shutil.which(exename) is None: - raise unittest.SkipTest(exename + ' not found') - return func(*args, **kwargs) - return wrapped - return wrapper - -def skipIfNoPkgconfig(f): - ''' - Skip this test if no pkg-config is found, unless we're on CI. - This allows users to run our test suite without having - pkg-config installed on, f.ex., macOS, while ensuring that our CI does not - silently skip the test because of misconfiguration. - - 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 skipIfNoPkgconfigDep(depname): - ''' - Skip this test if the given pkg-config dep is not found, unless we're on CI. - ''' - def wrapper(func): - @functools.wraps(func) - def wrapped(*args, **kwargs): - if not is_ci() and shutil.which('pkg-config') is None: - raise unittest.SkipTest('pkg-config not found') - if not is_ci() and subprocess.call(['pkg-config', '--exists', depname]) != 0: - raise unittest.SkipTest(f'pkg-config dependency {depname} not found.') - return func(*args, **kwargs) - return wrapped - return wrapper - -def skip_if_no_cmake(f): - ''' - Skip this test if no cmake is found, unless we're on CI. - This allows users to run our test suite without having - cmake installed on, f.ex., macOS, while ensuring that our CI does not - silently skip the test because of misconfiguration. - ''' - @functools.wraps(f) - def wrapped(*args, **kwargs): - if not is_ci() and shutil.which('cmake') is None: - raise unittest.SkipTest('cmake not found') - return f(*args, **kwargs) - return wrapped - -def skip_if_not_language(lang: str): - def wrapper(func): - @functools.wraps(func) - def wrapped(*args, **kwargs): - try: - compiler_from_language(get_fake_env(), lang, MachineChoice.HOST) - except EnvironmentException: - raise unittest.SkipTest(f'No {lang} compiler found.') - return func(*args, **kwargs) - return wrapped - return wrapper - -def skip_if_env_set(key): - ''' - Skip a test if a particular env is set, except when running under CI - ''' - def wrapper(func): - @functools.wraps(func) - def wrapped(*args, **kwargs): - old = None - if key in os.environ: - if not is_ci(): - raise unittest.SkipTest(f'Env var {key!r} set, skipping') - old = os.environ.pop(key) - try: - return func(*args, **kwargs) - finally: - if old is not None: - os.environ[key] = old - return wrapped - return wrapper - -def skip_if_not_base_option(feature): - """Skip tests if The compiler does not support a given base option. - - for example, ICC doesn't currently support b_sanitize. - """ - def actual(f): - @functools.wraps(f) - def wrapped(*args, **kwargs): - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - key = OptionKey(feature) - if key not in cc.base_options: - raise unittest.SkipTest( - f'{feature} not available with {cc.id}') - return f(*args, **kwargs) - return wrapped - return actual - - -@contextmanager -def temp_filename(): - '''A context manager which provides a filename to an empty temporary file. - - On exit the file will be deleted. - ''' - - fd, filename = tempfile.mkstemp() - os.close(fd) - try: - yield filename - finally: - try: - os.remove(filename) - except OSError: - pass - -@contextmanager -def no_pkgconfig(): - ''' - A context manager that overrides shutil.which and ExternalProgram to force - them to return None for pkg-config to simulate it not existing. - ''' - old_which = shutil.which - old_search = ExternalProgram._search - - def new_search(self, name, search_dir): - if name == 'pkg-config': - return [None] - return old_search(self, name, search_dir) - - def new_which(cmd, *kwargs): - if cmd == 'pkg-config': - return None - return old_which(cmd, *kwargs) - - shutil.which = new_which - ExternalProgram._search = new_search - try: - yield - finally: - shutil.which = old_which - ExternalProgram._search = old_search - - -class InternalTests(unittest.TestCase): - - def test_version_number(self): - self.assertEqual(search_version('foobar 1.2.3'), '1.2.3') - self.assertEqual(search_version('1.2.3'), '1.2.3') - self.assertEqual(search_version('foobar 2016.10.28 1.2.3'), '1.2.3') - self.assertEqual(search_version('2016.10.28 1.2.3'), '1.2.3') - self.assertEqual(search_version('foobar 2016.10.128'), '2016.10.128') - self.assertEqual(search_version('2016.10.128'), '2016.10.128') - self.assertEqual(search_version('2016.10'), '2016.10') - self.assertEqual(search_version('2016.10 1.2.3'), '1.2.3') - self.assertEqual(search_version('oops v1.2.3'), '1.2.3') - self.assertEqual(search_version('2016.oops 1.2.3'), '1.2.3') - self.assertEqual(search_version('2016.x'), 'unknown version') - - def test_mode_symbolic_to_bits(self): - modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits - self.assertEqual(modefunc('---------'), 0) - self.assertEqual(modefunc('r--------'), stat.S_IRUSR) - self.assertEqual(modefunc('---r-----'), stat.S_IRGRP) - self.assertEqual(modefunc('------r--'), stat.S_IROTH) - self.assertEqual(modefunc('-w-------'), stat.S_IWUSR) - self.assertEqual(modefunc('----w----'), stat.S_IWGRP) - self.assertEqual(modefunc('-------w-'), stat.S_IWOTH) - self.assertEqual(modefunc('--x------'), stat.S_IXUSR) - self.assertEqual(modefunc('-----x---'), stat.S_IXGRP) - self.assertEqual(modefunc('--------x'), stat.S_IXOTH) - self.assertEqual(modefunc('--S------'), stat.S_ISUID) - self.assertEqual(modefunc('-----S---'), stat.S_ISGID) - self.assertEqual(modefunc('--------T'), stat.S_ISVTX) - self.assertEqual(modefunc('--s------'), stat.S_ISUID | stat.S_IXUSR) - self.assertEqual(modefunc('-----s---'), stat.S_ISGID | stat.S_IXGRP) - self.assertEqual(modefunc('--------t'), stat.S_ISVTX | stat.S_IXOTH) - self.assertEqual(modefunc('rwx------'), stat.S_IRWXU) - self.assertEqual(modefunc('---rwx---'), stat.S_IRWXG) - self.assertEqual(modefunc('------rwx'), stat.S_IRWXO) - # We could keep listing combinations exhaustively but that seems - # tedious and pointless. Just test a few more. - self.assertEqual(modefunc('rwxr-xr-x'), - stat.S_IRWXU | - stat.S_IRGRP | stat.S_IXGRP | - stat.S_IROTH | stat.S_IXOTH) - self.assertEqual(modefunc('rw-r--r--'), - stat.S_IRUSR | stat.S_IWUSR | - stat.S_IRGRP | - stat.S_IROTH) - self.assertEqual(modefunc('rwsr-x---'), - stat.S_IRWXU | stat.S_ISUID | - stat.S_IRGRP | stat.S_IXGRP) - - def test_compiler_args_class_none_flush(self): - cc = mesonbuild.compilers.ClangCCompiler([], 'fake', MachineChoice.HOST, False, mock.Mock()) - a = cc.compiler_args(['-I.']) - #first we are checking if the tree construction deduplicates the correct -I argument - a += ['-I..'] - a += ['-I./tests/'] - a += ['-I./tests2/'] - #think this here as assertion, we cannot apply it, otherwise the CompilerArgs would already flush the changes: - # assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..', '-I.']) - a += ['-I.'] - a += ['-I.', '-I./tests/'] - self.assertEqual(a, ['-I.', '-I./tests/', '-I./tests2/', '-I..']) - - #then we are checking that when CompilerArgs already have a build container list, that the deduplication is taking the correct one - a += ['-I.', '-I./tests2/'] - self.assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..']) - - def test_compiler_args_class_d(self): - d = mesonbuild.compilers.DmdDCompiler([], 'fake', MachineChoice.HOST, 'info', 'arch') - # check include order is kept when deduplicating - a = d.compiler_args(['-Ifirst', '-Isecond', '-Ithird']) - a += ['-Ifirst'] - self.assertEqual(a, ['-Ifirst', '-Isecond', '-Ithird']) - - def test_compiler_args_class_clike(self): - cc = mesonbuild.compilers.ClangCCompiler([], 'fake', MachineChoice.HOST, False, mock.Mock()) - # Test that empty initialization works - a = cc.compiler_args() - self.assertEqual(a, []) - # Test that list initialization works - a = cc.compiler_args(['-I.', '-I..']) - self.assertEqual(a, ['-I.', '-I..']) - # Test that there is no de-dup on initialization - self.assertEqual(cc.compiler_args(['-I.', '-I.']), ['-I.', '-I.']) - - ## Test that appending works - a.append('-I..') - self.assertEqual(a, ['-I..', '-I.']) - a.append('-O3') - self.assertEqual(a, ['-I..', '-I.', '-O3']) - - ## Test that in-place addition works - a += ['-O2', '-O2'] - self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2', '-O2']) - # Test that removal works - a.remove('-O2') - self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2']) - # Test that de-dup happens on addition - a += ['-Ifoo', '-Ifoo'] - self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2']) - - # .extend() is just +=, so we don't test it - - ## Test that addition works - # Test that adding a list with just one old arg works and yields the same array - a = a + ['-Ifoo'] - self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2']) - # Test that adding a list with one arg new and one old works - a = a + ['-Ifoo', '-Ibaz'] - self.assertEqual(a, ['-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2']) - # Test that adding args that must be prepended and appended works - a = a + ['-Ibar', '-Wall'] - self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall']) - - ## Test that reflected addition works - # Test that adding to a list with just one old arg works and yields the same array - a = ['-Ifoo'] + a - self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall']) - # Test that adding to a list with just one new arg that is not pre-pended works - a = ['-Werror'] + a - self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Werror', '-O3', '-O2', '-Wall']) - # Test that adding to a list with two new args preserves the order - a = ['-Ldir', '-Lbah'] + a - self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall']) - # Test that adding to a list with old args does nothing - a = ['-Ibar', '-Ibaz', '-Ifoo'] + a - self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall']) - - ## Test that adding libraries works - l = cc.compiler_args(['-Lfoodir', '-lfoo']) - self.assertEqual(l, ['-Lfoodir', '-lfoo']) - # Adding a library and a libpath appends both correctly - l += ['-Lbardir', '-lbar'] - self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar']) - # Adding the same library again does nothing - l += ['-lbar'] - self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar']) - - ## Test that 'direct' append and extend works - l = cc.compiler_args(['-Lfoodir', '-lfoo']) - self.assertEqual(l, ['-Lfoodir', '-lfoo']) - # Direct-adding a library and a libpath appends both correctly - l.extend_direct(['-Lbardir', '-lbar']) - self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar']) - # Direct-adding the same library again still adds it - l.append_direct('-lbar') - self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar']) - # Direct-adding with absolute path deduplicates - l.append_direct('/libbaz.a') - self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a']) - # Adding libbaz again does nothing - l.append_direct('/libbaz.a') - self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a']) - - def test_compiler_args_class_gnuld(self): - ## Test --start/end-group - linker = mesonbuild.linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', []) - gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker) - ## Ensure that the fake compiler is never called by overriding the relevant function - gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include'] - ## Test that 'direct' append and extend works - l = gcc.compiler_args(['-Lfoodir', '-lfoo']) - self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group']) - # Direct-adding a library and a libpath appends both correctly - l.extend_direct(['-Lbardir', '-lbar']) - self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-Wl,--end-group']) - # Direct-adding the same library again still adds it - l.append_direct('-lbar') - self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '-Wl,--end-group']) - # Direct-adding with absolute path deduplicates - l.append_direct('/libbaz.a') - self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group']) - # Adding libbaz again does nothing - l.append_direct('/libbaz.a') - self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group']) - # Adding a non-library argument doesn't include it in the group - l += ['-Lfoo', '-Wl,--export-dynamic'] - self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group', '-Wl,--export-dynamic']) - # -Wl,-lfoo is detected as a library and gets added to the group - l.append('-Wl,-ldl') - self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--export-dynamic', '-Wl,-ldl', '-Wl,--end-group']) - - def test_compiler_args_remove_system(self): - ## Test --start/end-group - linker = mesonbuild.linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', []) - gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker) - ## Ensure that the fake compiler is never called by overriding the relevant function - gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include'] - ## Test that 'direct' append and extend works - l = gcc.compiler_args(['-Lfoodir', '-lfoo']) - self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group']) - ## Test that to_native removes all system includes - l += ['-isystem/usr/include', '-isystem=/usr/share/include', '-DSOMETHING_IMPORTANT=1', '-isystem', '/usr/local/include'] - self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group', '-DSOMETHING_IMPORTANT=1']) - - def test_string_templates_substitution(self): - dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict - substfunc = mesonbuild.mesonlib.substitute_values - ME = mesonbuild.mesonlib.MesonException - - # Identity - self.assertEqual(dictfunc([], []), {}) - - # One input, no outputs - inputs = ['bar/foo.c.in'] - outputs = [] - ret = dictfunc(inputs, outputs) - d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], - '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'} - # Check dictionary - self.assertEqual(ret, d) - # Check substitutions - cmd = ['some', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), cmd) - cmd = ['@INPUT@.out', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:]) - cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', 'strings'] - self.assertEqual(substfunc(cmd, d), - [inputs[0] + '.out'] + [d['@PLAINNAME@'] + '.ok'] + cmd[2:]) - cmd = ['@INPUT@', '@BASENAME@.hah', 'strings'] - self.assertEqual(substfunc(cmd, d), - inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:]) - cmd = ['@OUTPUT@'] - self.assertRaises(ME, substfunc, cmd, d) - - # One input, one output - inputs = ['bar/foo.c.in'] - outputs = ['out.c'] - ret = dictfunc(inputs, outputs) - d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], - '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', - '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'} - # Check dictionary - self.assertEqual(ret, d) - # Check substitutions - cmd = ['some', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), cmd) - cmd = ['@INPUT@.out', '@OUTPUT@', 'strings'] - self.assertEqual(substfunc(cmd, d), - [inputs[0] + '.out'] + outputs + cmd[2:]) - cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', '@OUTPUT0@'] - self.assertEqual(substfunc(cmd, d), - [inputs[0] + '.out', d['@PLAINNAME@'] + '.ok'] + outputs) - cmd = ['@INPUT@', '@BASENAME@.hah', 'strings'] - self.assertEqual(substfunc(cmd, d), - inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:]) - - # One input, one output with a subdir - outputs = ['dir/out.c'] - ret = dictfunc(inputs, outputs) - d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], - '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', - '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'} - # Check dictionary - self.assertEqual(ret, d) - - # Two inputs, no outputs - inputs = ['bar/foo.c.in', 'baz/foo.c.in'] - outputs = [] - ret = dictfunc(inputs, outputs) - d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1]} - # Check dictionary - self.assertEqual(ret, d) - # Check substitutions - cmd = ['some', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), cmd) - cmd = ['@INPUT@', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), inputs + cmd[1:]) - cmd = ['@INPUT0@.out', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:]) - cmd = ['@INPUT0@.out', '@INPUT1@.ok', 'strings'] - self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:]) - cmd = ['@INPUT0@', '@INPUT1@', 'strings'] - self.assertEqual(substfunc(cmd, d), inputs + cmd[2:]) - # Many inputs, can't use @INPUT@ like this - cmd = ['@INPUT@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - # Not enough inputs - cmd = ['@INPUT2@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - # Too many inputs - cmd = ['@PLAINNAME@'] - self.assertRaises(ME, substfunc, cmd, d) - cmd = ['@BASENAME@'] - self.assertRaises(ME, substfunc, cmd, d) - # No outputs - cmd = ['@OUTPUT@'] - self.assertRaises(ME, substfunc, cmd, d) - cmd = ['@OUTPUT0@'] - self.assertRaises(ME, substfunc, cmd, d) - cmd = ['@OUTDIR@'] - self.assertRaises(ME, substfunc, cmd, d) - - # Two inputs, one output - outputs = ['dir/out.c'] - ret = dictfunc(inputs, outputs) - d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1], - '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'} - # Check dictionary - self.assertEqual(ret, d) - # Check substitutions - cmd = ['some', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), cmd) - cmd = ['@OUTPUT@', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), outputs + cmd[1:]) - cmd = ['@OUTPUT@.out', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out'] + cmd[1:]) - cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', 'strings'] - self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:]) - # Many inputs, can't use @INPUT@ like this - cmd = ['@INPUT@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - # Not enough inputs - cmd = ['@INPUT2@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - # Not enough outputs - cmd = ['@OUTPUT2@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - - # Two inputs, two outputs - outputs = ['dir/out.c', 'dir/out2.c'] - ret = dictfunc(inputs, outputs) - d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1], - '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1], - '@OUTDIR@': 'dir'} - # Check dictionary - self.assertEqual(ret, d) - # Check substitutions - cmd = ['some', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), cmd) - cmd = ['@OUTPUT@', 'ordinary', 'strings'] - self.assertEqual(substfunc(cmd, d), outputs + cmd[1:]) - cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings'] - self.assertEqual(substfunc(cmd, d), outputs + cmd[2:]) - cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@'] - self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir']) - # Many inputs, can't use @INPUT@ like this - cmd = ['@INPUT@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - # Not enough inputs - cmd = ['@INPUT2@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - # Not enough outputs - cmd = ['@OUTPUT2@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - # Many outputs, can't use @OUTPUT@ like this - cmd = ['@OUTPUT@.out', 'ordinary', 'strings'] - self.assertRaises(ME, substfunc, cmd, d) - - def test_needs_exe_wrapper_override(self): - config = ConfigParser() - config['binaries'] = { - 'c': '\'/usr/bin/gcc\'', - } - config['host_machine'] = { - 'system': '\'linux\'', - 'cpu_family': '\'arm\'', - 'cpu': '\'armv7\'', - 'endian': '\'little\'', - } - # Can not be used as context manager because we need to - # open it a second time and this is not possible on - # Windows. - configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False) - configfilename = configfile.name - config.write(configfile) - configfile.flush() - configfile.close() - opts = get_fake_options() - opts.cross_file = (configfilename,) - env = get_fake_env(opts=opts) - detected_value = env.need_exe_wrapper() - os.unlink(configfilename) - - desired_value = not detected_value - config['properties'] = { - 'needs_exe_wrapper': 'true' if desired_value else 'false' - } - - configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False) - configfilename = configfile.name - config.write(configfile) - configfile.close() - opts = get_fake_options() - opts.cross_file = (configfilename,) - env = get_fake_env(opts=opts) - forced_value = env.need_exe_wrapper() - os.unlink(configfilename) - - self.assertEqual(forced_value, desired_value) - - def test_listify(self): - listify = mesonbuild.mesonlib.listify - # Test sanity - self.assertEqual([1], listify(1)) - self.assertEqual([], listify([])) - self.assertEqual([1], listify([1])) - # Test flattening - self.assertEqual([1, 2, 3], listify([1, [2, 3]])) - self.assertEqual([1, 2, 3], listify([1, [2, [3]]])) - self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False)) - # Test flattening and unholdering - class TestHeldObj(mesonbuild.mesonlib.HoldableObject): - def __init__(self, val: int) -> None: - self._val = val - class MockInterpreter: - def __init__(self) -> None: - self.subproject = '' - self.environment = None - heldObj1 = TestHeldObj(1) - holder1 = ObjectHolder(heldObj1, MockInterpreter()) - self.assertEqual([holder1], listify(holder1)) - self.assertEqual([holder1], listify([holder1])) - self.assertEqual([holder1, 2], listify([holder1, 2])) - self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]])) - - def test_extract_as_list(self): - extract = mesonbuild.mesonlib.extract_as_list - # Test sanity - kwargs = {'sources': [1, 2, 3]} - self.assertEqual([1, 2, 3], extract(kwargs, 'sources')) - self.assertEqual(kwargs, {'sources': [1, 2, 3]}) - self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True)) - self.assertEqual(kwargs, {}) - - class TestHeldObj(mesonbuild.mesonlib.HoldableObject): - pass - class MockInterpreter: - def __init__(self) -> None: - self.subproject = '' - self.environment = None - heldObj = TestHeldObj() - - # Test unholding - holder3 = ObjectHolder(heldObj, MockInterpreter()) - kwargs = {'sources': [1, 2, holder3]} - self.assertEqual(kwargs, {'sources': [1, 2, holder3]}) - - # flatten nested lists - kwargs = {'sources': [1, [2, [3]]]} - self.assertEqual([1, 2, 3], extract(kwargs, 'sources')) - - def _test_all_naming(self, cc, env, patterns, platform): - shr = patterns[platform]['shared'] - stc = patterns[platform]['static'] - shrstc = shr + tuple([x for x in stc if x not in shr]) - stcshr = stc + tuple([x for x in shr if x not in stc]) - p = cc.get_library_naming(env, LibType.SHARED) - self.assertEqual(p, shr) - p = cc.get_library_naming(env, LibType.STATIC) - self.assertEqual(p, stc) - p = cc.get_library_naming(env, LibType.PREFER_STATIC) - self.assertEqual(p, stcshr) - p = cc.get_library_naming(env, LibType.PREFER_SHARED) - self.assertEqual(p, shrstc) - # Test find library by mocking up openbsd - if platform != 'openbsd': - return - with tempfile.TemporaryDirectory() as tmpdir: - for i in ['libfoo.so.6.0', 'libfoo.so.5.0', 'libfoo.so.54.0', 'libfoo.so.66a.0b', 'libfoo.so.70.0.so.1']: - libpath = Path(tmpdir) / i - libpath.write_text('', encoding='utf-8') - found = cc._find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED) - self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0') - - def test_find_library_patterns(self): - ''' - Unit test for the library search patterns used by find_library() - ''' - unix_static = ('lib{}.a', '{}.a') - msvc_static = ('lib{}.a', 'lib{}.lib', '{}.a', '{}.lib') - # This is the priority list of pattern matching for library searching - patterns = {'openbsd': {'shared': ('lib{}.so', '{}.so', 'lib{}.so.[0-9]*.[0-9]*', '{}.so.[0-9]*.[0-9]*'), - 'static': unix_static}, - 'linux': {'shared': ('lib{}.so', '{}.so'), - 'static': unix_static}, - 'darwin': {'shared': ('lib{}.dylib', 'lib{}.so', '{}.dylib', '{}.so'), - 'static': unix_static}, - 'cygwin': {'shared': ('cyg{}.dll', 'cyg{}.dll.a', 'lib{}.dll', - 'lib{}.dll.a', '{}.dll', '{}.dll.a'), - 'static': ('cyg{}.a',) + unix_static}, - 'windows-msvc': {'shared': ('lib{}.lib', '{}.lib'), - 'static': msvc_static}, - 'windows-mingw': {'shared': ('lib{}.dll.a', 'lib{}.lib', 'lib{}.dll', - '{}.dll.a', '{}.lib', '{}.dll'), - 'static': msvc_static}} - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - if is_osx(): - self._test_all_naming(cc, env, patterns, 'darwin') - elif is_cygwin(): - self._test_all_naming(cc, env, patterns, 'cygwin') - elif is_windows(): - if cc.get_argument_syntax() == 'msvc': - self._test_all_naming(cc, env, patterns, 'windows-msvc') - else: - self._test_all_naming(cc, env, patterns, 'windows-mingw') - elif is_openbsd(): - self._test_all_naming(cc, env, patterns, 'openbsd') - else: - self._test_all_naming(cc, env, patterns, 'linux') - env.machines.host.system = 'openbsd' - self._test_all_naming(cc, env, patterns, 'openbsd') - env.machines.host.system = 'darwin' - self._test_all_naming(cc, env, patterns, 'darwin') - env.machines.host.system = 'cygwin' - self._test_all_naming(cc, env, patterns, 'cygwin') - env.machines.host.system = 'windows' - self._test_all_naming(cc, env, patterns, 'windows-mingw') - - @skipIfNoPkgconfig - def test_pkgconfig_parse_libs(self): - ''' - Unit test for parsing of pkg-config output to search for libraries - - https://github.com/mesonbuild/meson/issues/3951 - ''' - def create_static_lib(name): - if not is_osx(): - name.open('w', encoding='utf-8').close() - return - src = name.with_suffix('.c') - out = name.with_suffix('.o') - with src.open('w', encoding='utf-8') as f: - f.write('int meson_foobar (void) { return 0; }') - subprocess.check_call(['clang', '-c', str(src), '-o', str(out)]) - subprocess.check_call(['ar', 'csr', str(name), str(out)]) - - with tempfile.TemporaryDirectory() as tmpdir: - pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True) - env = get_fake_env() - compiler = detect_c_compiler(env, MachineChoice.HOST) - env.coredata.compilers.host = {'c': compiler} - env.coredata.options[OptionKey('link_args', lang='c')] = FakeCompilerOptions() - p1 = Path(tmpdir) / '1' - p2 = Path(tmpdir) / '2' - p1.mkdir() - p2.mkdir() - # libfoo.a is in one prefix - create_static_lib(p1 / 'libfoo.a') - # libbar.a is in both prefixes - create_static_lib(p1 / 'libbar.a') - create_static_lib(p2 / 'libbar.a') - # Ensure that we never statically link to these - create_static_lib(p1 / 'libpthread.a') - create_static_lib(p1 / 'libm.a') - create_static_lib(p1 / 'libc.a') - create_static_lib(p1 / 'libdl.a') - create_static_lib(p1 / 'librt.a') - - def fake_call_pkgbin(self, args, env=None): - if '--libs' not in args: - return 0, '', '' - if args[-1] == 'foo': - return 0, f'-L{p2.as_posix()} -lfoo -L{p1.as_posix()} -lbar', '' - if args[-1] == 'bar': - return 0, f'-L{p2.as_posix()} -lbar', '' - if args[-1] == 'internal': - return 0, f'-L{p1.as_posix()} -lpthread -lm -lc -lrt -ldl', '' - - old_call = PkgConfigDependency._call_pkgbin - old_check = PkgConfigDependency.check_pkgconfig - PkgConfigDependency._call_pkgbin = fake_call_pkgbin - PkgConfigDependency.check_pkgconfig = lambda x, _: pkgbin - # Test begins - try: - kwargs = {'required': True, 'silent': True} - foo_dep = PkgConfigDependency('foo', env, kwargs) - self.assertEqual(foo_dep.get_link_args(), - [(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()]) - bar_dep = PkgConfigDependency('bar', env, kwargs) - self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()]) - internal_dep = PkgConfigDependency('internal', env, kwargs) - if compiler.get_argument_syntax() == 'msvc': - self.assertEqual(internal_dep.get_link_args(), []) - else: - link_args = internal_dep.get_link_args() - for link_arg in link_args: - for lib in ('pthread', 'm', 'c', 'dl', 'rt'): - self.assertNotIn(f'lib{lib}.a', link_arg, msg=link_args) - finally: - # Test ends - PkgConfigDependency._call_pkgbin = old_call - PkgConfigDependency.check_pkgconfig = old_check - # Reset dependency class to ensure that in-process configure doesn't mess up - PkgConfigDependency.pkgbin_cache = {} - PkgConfigDependency.class_pkgbin = PerMachine(None, None) - - def test_version_compare(self): - comparefunc = mesonbuild.mesonlib.version_compare_many - for (a, b, result) in [ - ('0.99.beta19', '>= 0.99.beta14', True), - ]: - self.assertEqual(comparefunc(a, b)[0], result) - - for (a, b, op) in [ - # examples from https://fedoraproject.org/wiki/Archive:Tools/RPM/VersionComparison - ("1.0010", "1.9", operator.gt), - ("1.05", "1.5", operator.eq), - ("1.0", "1", operator.gt), - ("2.50", "2.5", operator.gt), - ("fc4", "fc.4", operator.eq), - ("FC5", "fc4", operator.lt), - ("2a", "2.0", operator.lt), - ("1.0", "1.fc4", operator.gt), - ("3.0.0_fc", "3.0.0.fc", operator.eq), - # from RPM tests - ("1.0", "1.0", operator.eq), - ("1.0", "2.0", operator.lt), - ("2.0", "1.0", operator.gt), - ("2.0.1", "2.0.1", operator.eq), - ("2.0", "2.0.1", operator.lt), - ("2.0.1", "2.0", operator.gt), - ("2.0.1a", "2.0.1a", operator.eq), - ("2.0.1a", "2.0.1", operator.gt), - ("2.0.1", "2.0.1a", operator.lt), - ("5.5p1", "5.5p1", operator.eq), - ("5.5p1", "5.5p2", operator.lt), - ("5.5p2", "5.5p1", operator.gt), - ("5.5p10", "5.5p10", operator.eq), - ("5.5p1", "5.5p10", operator.lt), - ("5.5p10", "5.5p1", operator.gt), - ("10xyz", "10.1xyz", operator.lt), - ("10.1xyz", "10xyz", operator.gt), - ("xyz10", "xyz10", operator.eq), - ("xyz10", "xyz10.1", operator.lt), - ("xyz10.1", "xyz10", operator.gt), - ("xyz.4", "xyz.4", operator.eq), - ("xyz.4", "8", operator.lt), - ("8", "xyz.4", operator.gt), - ("xyz.4", "2", operator.lt), - ("2", "xyz.4", operator.gt), - ("5.5p2", "5.6p1", operator.lt), - ("5.6p1", "5.5p2", operator.gt), - ("5.6p1", "6.5p1", operator.lt), - ("6.5p1", "5.6p1", operator.gt), - ("6.0.rc1", "6.0", operator.gt), - ("6.0", "6.0.rc1", operator.lt), - ("10b2", "10a1", operator.gt), - ("10a2", "10b2", operator.lt), - ("1.0aa", "1.0aa", operator.eq), - ("1.0a", "1.0aa", operator.lt), - ("1.0aa", "1.0a", operator.gt), - ("10.0001", "10.0001", operator.eq), - ("10.0001", "10.1", operator.eq), - ("10.1", "10.0001", operator.eq), - ("10.0001", "10.0039", operator.lt), - ("10.0039", "10.0001", operator.gt), - ("4.999.9", "5.0", operator.lt), - ("5.0", "4.999.9", operator.gt), - ("20101121", "20101121", operator.eq), - ("20101121", "20101122", operator.lt), - ("20101122", "20101121", operator.gt), - ("2_0", "2_0", operator.eq), - ("2.0", "2_0", operator.eq), - ("2_0", "2.0", operator.eq), - ("a", "a", operator.eq), - ("a+", "a+", operator.eq), - ("a+", "a_", operator.eq), - ("a_", "a+", operator.eq), - ("+a", "+a", operator.eq), - ("+a", "_a", operator.eq), - ("_a", "+a", operator.eq), - ("+_", "+_", operator.eq), - ("_+", "+_", operator.eq), - ("_+", "_+", operator.eq), - ("+", "_", operator.eq), - ("_", "+", operator.eq), - # other tests - ('0.99.beta19', '0.99.beta14', operator.gt), - ("1.0.0", "2.0.0", operator.lt), - (".0.0", "2.0.0", operator.lt), - ("alpha", "beta", operator.lt), - ("1.0", "1.0.0", operator.lt), - ("2.456", "2.1000", operator.lt), - ("2.1000", "3.111", operator.lt), - ("2.001", "2.1", operator.eq), - ("2.34", "2.34", operator.eq), - ("6.1.2", "6.3.8", operator.lt), - ("1.7.3.0", "2.0.0", operator.lt), - ("2.24.51", "2.25", operator.lt), - ("2.1.5+20120813+gitdcbe778", "2.1.5", operator.gt), - ("3.4.1", "3.4b1", operator.gt), - ("041206", "200090325", operator.lt), - ("0.6.2+git20130413", "0.6.2", operator.gt), - ("2.6.0+bzr6602", "2.6.0", operator.gt), - ("2.6.0", "2.6b2", operator.gt), - ("2.6.0+bzr6602", "2.6b2x", operator.gt), - ("0.6.7+20150214+git3a710f9", "0.6.7", operator.gt), - ("15.8b", "15.8.0.1", operator.lt), - ("1.2rc1", "1.2.0", operator.lt), - ]: - ver_a = Version(a) - ver_b = Version(b) - if op is operator.eq: - for o, name in [(op, 'eq'), (operator.ge, 'ge'), (operator.le, 'le')]: - self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}') - if op is operator.lt: - for o, name in [(op, 'lt'), (operator.le, 'le'), (operator.ne, 'ne')]: - self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}') - for o, name in [(operator.gt, 'gt'), (operator.ge, 'ge'), (operator.eq, 'eq')]: - self.assertFalse(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}') - if op is operator.gt: - for o, name in [(op, 'gt'), (operator.ge, 'ge'), (operator.ne, 'ne')]: - self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}') - for o, name in [(operator.lt, 'lt'), (operator.le, 'le'), (operator.eq, 'eq')]: - self.assertFalse(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}') - - def test_msvc_toolset_version(self): - ''' - Ensure that the toolset version returns the correct value for this MSVC - ''' - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_argument_syntax() != 'msvc': - raise unittest.SkipTest('Test only applies to MSVC-like compilers') - toolset_ver = cc.get_toolset_version() - self.assertIsNotNone(toolset_ver) - # Visual Studio 2015 and older versions do not define VCToolsVersion - # TODO: ICL doesn't set this in the VSC2015 profile either - if cc.id == 'msvc' and int(''.join(cc.version.split('.')[0:2])) < 1910: - return - if 'VCToolsVersion' in os.environ: - vctools_ver = os.environ['VCToolsVersion'] - else: - self.assertIn('VCINSTALLDIR', os.environ) - # See https://devblogs.microsoft.com/cppblog/finding-the-visual-c-compiler-tools-in-visual-studio-2017/ - vctools_ver = (Path(os.environ['VCINSTALLDIR']) / 'Auxiliary' / 'Build' / 'Microsoft.VCToolsVersion.default.txt').read_text(encoding='utf-8') - self.assertTrue(vctools_ver.startswith(toolset_ver), - msg=f'{vctools_ver!r} does not start with {toolset_ver!r}') - - def test_split_args(self): - split_args = mesonbuild.mesonlib.split_args - join_args = mesonbuild.mesonlib.join_args - if is_windows(): - test_data = [ - # examples from https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments - (r'"a b c" d e', ['a b c', 'd', 'e'], True), - (r'"ab\"c" "\\" d', ['ab"c', '\\', 'd'], False), - (r'a\\\b d"e f"g h', [r'a\\\b', 'de fg', 'h'], False), - (r'a\\\"b c d', [r'a\"b', 'c', 'd'], False), - (r'a\\\\"b c" d e', [r'a\\b c', 'd', 'e'], False), - # other basics - (r'""', [''], True), - (r'a b c d "" e', ['a', 'b', 'c', 'd', '', 'e'], True), - (r"'a b c' d e", ["'a", 'b', "c'", 'd', 'e'], True), - (r"'a&b&c' d e", ["'a&b&c'", 'd', 'e'], True), - (r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], True), - (r"'a & b & c d e'", ["'a", '&', 'b', '&', 'c', 'd', "e'"], True), - ('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False), - # more illustrative tests - (r'cl test.cpp /O1 /Fe:test.exe', ['cl', 'test.cpp', '/O1', '/Fe:test.exe'], True), - (r'cl "test.cpp /O1 /Fe:test.exe"', ['cl', 'test.cpp /O1 /Fe:test.exe'], True), - (r'cl /DNAME=\"Bob\" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], False), - (r'cl "/DNAME=\"Bob\"" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], True), - (r'cl /DNAME=\"Bob, Alice\" test.cpp', ['cl', '/DNAME="Bob,', 'Alice"', 'test.cpp'], False), - (r'cl "/DNAME=\"Bob, Alice\"" test.cpp', ['cl', '/DNAME="Bob, Alice"', 'test.cpp'], True), - (r'cl C:\path\with\backslashes.cpp', ['cl', r'C:\path\with\backslashes.cpp'], True), - (r'cl C:\\path\\with\\double\\backslashes.cpp', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], True), - (r'cl "C:\\path\\with\\double\\backslashes.cpp"', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], False), - (r'cl C:\path with spaces\test.cpp', ['cl', r'C:\path', 'with', r'spaces\test.cpp'], False), - (r'cl "C:\path with spaces\test.cpp"', ['cl', r'C:\path with spaces\test.cpp'], True), - (r'cl /DPATH="C:\path\with\backslashes test.cpp', ['cl', r'/DPATH=C:\path\with\backslashes test.cpp'], False), - (r'cl /DPATH=\"C:\\ends\\with\\backslashes\\\" test.cpp', ['cl', r'/DPATH="C:\\ends\\with\\backslashes\"', 'test.cpp'], False), - (r'cl /DPATH="C:\\ends\\with\\backslashes\\" test.cpp', ['cl', '/DPATH=C:\\\\ends\\\\with\\\\backslashes\\', 'test.cpp'], False), - (r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\"', 'test.cpp'], True), - (r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\ test.cpp'], False), - (r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\"', 'test.cpp'], True), - ] - else: - test_data = [ - (r"'a b c' d e", ['a b c', 'd', 'e'], True), - (r"a/b/c d e", ['a/b/c', 'd', 'e'], True), - (r"a\b\c d e", [r'abc', 'd', 'e'], False), - (r"a\\b\\c d e", [r'a\b\c', 'd', 'e'], False), - (r'"a b c" d e', ['a b c', 'd', 'e'], False), - (r'"a\\b\\c\\" d e', ['a\\b\\c\\', 'd', 'e'], False), - (r"'a\b\c\' d e", ['a\\b\\c\\', 'd', 'e'], True), - (r"'a&b&c' d e", ['a&b&c', 'd', 'e'], True), - (r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], False), - (r"'a & b & c d e'", ['a & b & c d e'], True), - (r"abd'e f'g h", [r'abde fg', 'h'], False), - ('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False), - - ('g++ -DNAME="Bob" test.cpp', ['g++', '-DNAME=Bob', 'test.cpp'], False), - ("g++ '-DNAME=\"Bob\"' test.cpp", ['g++', '-DNAME="Bob"', 'test.cpp'], True), - ('g++ -DNAME="Bob, Alice" test.cpp', ['g++', '-DNAME=Bob, Alice', 'test.cpp'], False), - ("g++ '-DNAME=\"Bob, Alice\"' test.cpp", ['g++', '-DNAME="Bob, Alice"', 'test.cpp'], True), - ] - - for (cmd, expected, roundtrip) in test_data: - self.assertEqual(split_args(cmd), expected) - if roundtrip: - self.assertEqual(join_args(expected), cmd) - - def test_quote_arg(self): - split_args = mesonbuild.mesonlib.split_args - quote_arg = mesonbuild.mesonlib.quote_arg - if is_windows(): - test_data = [ - ('', '""'), - ('arg1', 'arg1'), - ('/option1', '/option1'), - ('/Ovalue', '/Ovalue'), - ('/OBob&Alice', '/OBob&Alice'), - ('/Ovalue with spaces', r'"/Ovalue with spaces"'), - (r'/O"value with spaces"', r'"/O\"value with spaces\""'), - (r'/OC:\path with spaces\test.exe', r'"/OC:\path with spaces\test.exe"'), - ('/LIBPATH:C:\\path with spaces\\ends\\with\\backslashes\\', r'"/LIBPATH:C:\path with spaces\ends\with\backslashes\\"'), - ('/LIBPATH:"C:\\path with spaces\\ends\\with\\backslashes\\\\"', r'"/LIBPATH:\"C:\path with spaces\ends\with\backslashes\\\\\""'), - (r'/DMSG="Alice said: \"Let\'s go\""', r'"/DMSG=\"Alice said: \\\"Let\'s go\\\"\""'), - ] - else: - test_data = [ - ('arg1', 'arg1'), - ('--option1', '--option1'), - ('-O=value', '-O=value'), - ('-O=Bob&Alice', "'-O=Bob&Alice'"), - ('-O=value with spaces', "'-O=value with spaces'"), - ('-O="value with spaces"', '\'-O=\"value with spaces\"\''), - ('-O=/path with spaces/test', '\'-O=/path with spaces/test\''), - ('-DMSG="Alice said: \\"Let\'s go\\""', "'-DMSG=\"Alice said: \\\"Let'\"'\"'s go\\\"\"'"), - ] - - for (arg, expected) in test_data: - self.assertEqual(quote_arg(arg), expected) - self.assertEqual(split_args(expected)[0], arg) - - def test_depfile(self): - for (f, target, expdeps) in [ - # empty, unknown target - ([''], 'unknown', set()), - # simple target & deps - (['meson/foo.o : foo.c foo.h'], 'meson/foo.o', set({'foo.c', 'foo.h'})), - (['meson/foo.o: foo.c foo.h'], 'foo.c', set()), - # get all deps - (['meson/foo.o: foo.c foo.h', - 'foo.c: gen.py'], 'meson/foo.o', set({'foo.c', 'foo.h', 'gen.py'})), - (['meson/foo.o: foo.c foo.h', - 'foo.c: gen.py'], 'foo.c', set({'gen.py'})), - # linue continuation, multiple targets - (['foo.o \\', 'foo.h: bar'], 'foo.h', set({'bar'})), - (['foo.o \\', 'foo.h: bar'], 'foo.o', set({'bar'})), - # \\ handling - (['foo: Program\\ F\\iles\\\\X'], 'foo', set({'Program Files\\X'})), - # $ handling - (['f$o.o: c/b'], 'f$o.o', set({'c/b'})), - (['f$$o.o: c/b'], 'f$o.o', set({'c/b'})), - # cycles - (['a: b', 'b: a'], 'a', set({'a', 'b'})), - (['a: b', 'b: a'], 'b', set({'a', 'b'})), - ]: - d = mesonbuild.depfile.DepFile(f) - deps = d.get_all_dependencies(target) - self.assertEqual(sorted(deps), sorted(expdeps)) - - def test_log_once(self): - f = io.StringIO() - with mock.patch('mesonbuild.mlog.log_file', f), \ - mock.patch('mesonbuild.mlog._logged_once', set()): - mesonbuild.mlog.log_once('foo') - mesonbuild.mlog.log_once('foo') - actual = f.getvalue().strip() - self.assertEqual(actual, 'foo', actual) - - def test_log_once_ansi(self): - f = io.StringIO() - with mock.patch('mesonbuild.mlog.log_file', f), \ - mock.patch('mesonbuild.mlog._logged_once', set()): - mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo')) - mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo')) - actual = f.getvalue().strip() - self.assertEqual(actual.count('foo'), 1, actual) - - mesonbuild.mlog.log_once('foo') - actual = f.getvalue().strip() - self.assertEqual(actual.count('foo'), 1, actual) - - f.truncate() - - mesonbuild.mlog.warning('bar', once=True) - mesonbuild.mlog.warning('bar', once=True) - actual = f.getvalue().strip() - self.assertEqual(actual.count('bar'), 1, actual) - - def test_sort_libpaths(self): - sort_libpaths = mesonbuild.dependencies.base.sort_libpaths - self.assertEqual(sort_libpaths( - ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'], - ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']), - ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib']) - self.assertEqual(sort_libpaths( - ['/usr/local/lib', '/home/mesonuser/.local/lib', '/usr/lib'], - ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']), - ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib']) - self.assertEqual(sort_libpaths( - ['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'], - ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']), - ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib']) - self.assertEqual(sort_libpaths( - ['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'], - ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/libdata/pkgconfig']), - ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib']) - - def test_dependency_factory_order(self): - b = mesonbuild.dependencies.base - F = mesonbuild.dependencies.factory - with tempfile.TemporaryDirectory() as tmpdir: - with chdir(tmpdir): - env = get_fake_env() - env.scratch_dir = tmpdir - - f = F.DependencyFactory( - 'test_dep', - methods=[b.DependencyMethods.PKGCONFIG, b.DependencyMethods.CMAKE] - ) - actual = [m() for m in f(env, MachineChoice.HOST, {'required': False})] - self.assertListEqual([m.type_name for m in actual], ['pkgconfig', 'cmake']) - - f = F.DependencyFactory( - 'test_dep', - methods=[b.DependencyMethods.CMAKE, b.DependencyMethods.PKGCONFIG] - ) - actual = [m() for m in f(env, MachineChoice.HOST, {'required': False})] - self.assertListEqual([m.type_name for m in actual], ['cmake', 'pkgconfig']) - - def test_validate_json(self) -> None: - """Validate the json schema for the test cases.""" - try: - from jsonschema import validate, ValidationError - except ImportError: - if is_ci(): - raise - raise unittest.SkipTest('Python jsonschema module not found.') - - schema = json.loads(Path('data/test.schema.json').read_text(encoding='utf-8')) - - errors = [] # type: T.Tuple[str, Exception] - for p in Path('test cases').glob('**/test.json'): - try: - validate(json.loads(p.read_text(encoding='utf-8')), schema=schema) - except ValidationError as e: - errors.append((p.resolve(), e)) - - for f, e in errors: - print(f'Failed to validate: "{f}"') - print(str(e)) - - self.assertFalse(errors) - - def test_typed_pos_args_types(self) -> None: - @typed_pos_args('foo', str, int, bool) - def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None: - self.assertIsInstance(args, tuple) - self.assertIsInstance(args[0], str) - self.assertIsInstance(args[1], int) - self.assertIsInstance(args[2], bool) - - _(None, mock.Mock(), ['string', 1, False], None) - - def test_typed_pos_args_types_invalid(self) -> None: - @typed_pos_args('foo', str, int, bool) - def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string', 1.0, False], None) - self.assertEqual(str(cm.exception), 'foo argument 2 was of type "float" but should have been "int"') - - def test_typed_pos_args_types_wrong_number(self) -> None: - @typed_pos_args('foo', str, int, bool) - def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string', 1], None) - self.assertEqual(str(cm.exception), 'foo takes exactly 3 arguments, but got 2.') - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string', 1, True, True], None) - self.assertEqual(str(cm.exception), 'foo takes exactly 3 arguments, but got 4.') - - def test_typed_pos_args_varargs(self) -> None: - @typed_pos_args('foo', str, varargs=str) - def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None: - self.assertIsInstance(args, tuple) - self.assertIsInstance(args[0], str) - self.assertIsInstance(args[1], list) - self.assertIsInstance(args[1][0], str) - self.assertIsInstance(args[1][1], str) - - _(None, mock.Mock(), ['string', 'var', 'args'], None) - - def test_typed_pos_args_varargs_not_given(self) -> None: - @typed_pos_args('foo', str, varargs=str) - def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None: - self.assertIsInstance(args, tuple) - self.assertIsInstance(args[0], str) - self.assertIsInstance(args[1], list) - self.assertEqual(args[1], []) - - _(None, mock.Mock(), ['string'], None) - - def test_typed_pos_args_varargs_invalid(self) -> None: - @typed_pos_args('foo', str, varargs=str) - def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string', 'var', 'args', 0], None) - self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been "str"') - - def test_typed_pos_args_varargs_invalid_mulitple_types(self) -> None: - @typed_pos_args('foo', str, varargs=(str, list)) - def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string', 'var', 'args', 0], None) - self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been one of: "str", "list"') - - def test_typed_pos_args_max_varargs(self) -> None: - @typed_pos_args('foo', str, varargs=str, max_varargs=5) - def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None: - self.assertIsInstance(args, tuple) - self.assertIsInstance(args[0], str) - self.assertIsInstance(args[1], list) - self.assertIsInstance(args[1][0], str) - self.assertIsInstance(args[1][1], str) - - _(None, mock.Mock(), ['string', 'var', 'args'], None) - - def test_typed_pos_args_max_varargs_exceeded(self) -> None: - @typed_pos_args('foo', str, varargs=str, max_varargs=1) - def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string', 'var', 'args'], None) - self.assertEqual(str(cm.exception), 'foo takes between 1 and 2 arguments, but got 3.') - - def test_typed_pos_args_min_varargs(self) -> None: - @typed_pos_args('foo', varargs=str, max_varargs=2, min_varargs=1) - def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None: - self.assertIsInstance(args, tuple) - self.assertIsInstance(args[0], list) - self.assertIsInstance(args[0][0], str) - self.assertIsInstance(args[0][1], str) - - _(None, mock.Mock(), ['string', 'var'], None) - - def test_typed_pos_args_min_varargs_not_met(self) -> None: - @typed_pos_args('foo', str, varargs=str, min_varargs=1) - def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string'], None) - self.assertEqual(str(cm.exception), 'foo takes at least 2 arguments, but got 1.') - - def test_typed_pos_args_min_and_max_varargs_exceeded(self) -> None: - @typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2) - def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string', 'var', 'args', 'bar'], None) - self.assertEqual(str(cm.exception), 'foo takes between 2 and 3 arguments, but got 4.') - - def test_typed_pos_args_min_and_max_varargs_not_met(self) -> None: - @typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2) - def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string'], None) - self.assertEqual(str(cm.exception), 'foo takes between 2 and 3 arguments, but got 1.') - - def test_typed_pos_args_variadic_and_optional(self) -> None: - @typed_pos_args('foo', str, optargs=[str], varargs=str, min_varargs=0) - def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(AssertionError) as cm: - _(None, mock.Mock(), ['string'], None) - self.assertEqual( - str(cm.exception), - 'varargs and optargs not supported together as this would be ambiguous') - - def test_typed_pos_args_min_optargs_not_met(self) -> None: - @typed_pos_args('foo', str, str, optargs=[str]) - def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string'], None) - self.assertEqual(str(cm.exception), 'foo takes at least 2 arguments, but got 1.') - - def test_typed_pos_args_min_optargs_max_exceeded(self) -> None: - @typed_pos_args('foo', str, optargs=[str]) - def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None: - self.assertTrue(False) # should not be reachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), ['string', '1', '2'], None) - self.assertEqual(str(cm.exception), 'foo takes at most 2 arguments, but got 3.') - - def test_typed_pos_args_optargs_not_given(self) -> None: - @typed_pos_args('foo', str, optargs=[str]) - def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None: - self.assertEqual(len(args), 2) - self.assertIsInstance(args[0], str) - self.assertEqual(args[0], 'string') - self.assertIsNone(args[1]) - - _(None, mock.Mock(), ['string'], None) - - def test_typed_pos_args_optargs_some_given(self) -> None: - @typed_pos_args('foo', str, optargs=[str, int]) - def _(obj, node, args: T.Tuple[str, T.Optional[str], T.Optional[int]], kwargs) -> None: - self.assertEqual(len(args), 3) - self.assertIsInstance(args[0], str) - self.assertEqual(args[0], 'string') - self.assertIsInstance(args[1], str) - self.assertEqual(args[1], '1') - self.assertIsNone(args[2]) - - _(None, mock.Mock(), ['string', '1'], None) - - def test_typed_pos_args_optargs_all_given(self) -> None: - @typed_pos_args('foo', str, optargs=[str]) - def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None: - self.assertEqual(len(args), 2) - self.assertIsInstance(args[0], str) - self.assertEqual(args[0], 'string') - self.assertIsInstance(args[1], str) - - _(None, mock.Mock(), ['string', '1'], None) - - def test_typed_kwarg_basic(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', str) - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None: - self.assertIsInstance(kwargs['input'], str) - self.assertEqual(kwargs['input'], 'foo') - - _(None, mock.Mock(), [], {'input': 'foo'}) - - def test_typed_kwarg_missing_required(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', str, required=True), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None: - self.assertTrue(False) # should be unreachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), [], {}) - self.assertEqual(str(cm.exception), 'testfunc is missing required keyword argument "input"') - - def test_typed_kwarg_missing_optional(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', str), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.Optional[str]]) -> None: - self.assertIsNone(kwargs['input']) - - _(None, mock.Mock(), [], {}) - - def test_typed_kwarg_default(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', str, default='default'), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None: - self.assertEqual(kwargs['input'], 'default') - - _(None, mock.Mock(), [], {}) - - def test_typed_kwarg_container_valid(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', ContainerTypeInfo(list, str), required=True), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None: - self.assertEqual(kwargs['input'], ['str']) - - _(None, mock.Mock(), [], {'input': ['str']}) - - def test_typed_kwarg_container_invalid(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', ContainerTypeInfo(list, str), required=True), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None: - self.assertTrue(False) # should be unreachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), [], {'input': {}}) - self.assertEqual(str(cm.exception), 'testfunc keyword argument "input" container type was "dict", but should have been "list"') - - def test_typed_kwarg_contained_invalid(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', ContainerTypeInfo(dict, str), required=True), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.Dict[str, str]]) -> None: - self.assertTrue(False) # should be unreachable - - with self.assertRaises(InvalidArguments) as cm: - _(None, mock.Mock(), [], {'input': {'key': 1}}) - self.assertEqual(str(cm.exception), 'testfunc keyword argument "input" contained a value of type "int" but should have been "str"') - - def test_typed_kwarg_container_listify(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', ContainerTypeInfo(list, str), listify=True), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None: - self.assertEqual(kwargs['input'], ['str']) - - _(None, mock.Mock(), [], {'input': 'str'}) - - def test_typed_kwarg_container_default_copy(self) -> None: - default: T.List[str] = [] - @typed_kwargs( - 'testfunc', - KwargInfo('input', ContainerTypeInfo(list, str), listify=True, default=default), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None: - self.assertIsNot(kwargs['input'], default) - - _(None, mock.Mock(), [], {}) - - def test_typed_kwarg_container_pairs(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', ContainerTypeInfo(list, str, pairs=True), listify=True), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None: - self.assertEqual(kwargs['input'], ['a', 'b']) - - _(None, mock.Mock(), [], {'input': ['a', 'b']}) - - with self.assertRaises(MesonException) as cm: - _(None, mock.Mock(), [], {'input': ['a']}) - self.assertEqual(str(cm.exception), "testfunc keyword argument \"input\" container should be of even length, but is not") - - @mock.patch.dict(mesonbuild.mesonlib.project_meson_versions, {}) - def test_typed_kwarg_since(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', str, since='1.0', deprecated='2.0') - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None: - self.assertIsInstance(kwargs['input'], str) - self.assertEqual(kwargs['input'], 'foo') - - with mock.patch('sys.stdout', io.StringIO()) as out: - # With Meson 0.1 it should trigger the "introduced" warning but not the "deprecated" warning - mesonbuild.mesonlib.project_meson_versions[''] = '0.1' - _(None, mock.Mock(subproject=''), [], {'input': 'foo'}) - self.assertRegex(out.getvalue(), r'WARNING:.*introduced.*input arg in testfunc') - self.assertNotRegex(out.getvalue(), r'WARNING:.*deprecated.*input arg in testfunc') - - with mock.patch('sys.stdout', io.StringIO()) as out: - # With Meson 1.5 it shouldn't trigger any warning - mesonbuild.mesonlib.project_meson_versions[''] = '1.5' - _(None, mock.Mock(subproject=''), [], {'input': 'foo'}) - self.assertNotRegex(out.getvalue(), r'WARNING:.*') - self.assertNotRegex(out.getvalue(), r'WARNING:.*') - - with mock.patch('sys.stdout', io.StringIO()) as out: - # With Meson 2.0 it should trigger the "deprecated" warning but not the "introduced" warning - mesonbuild.mesonlib.project_meson_versions[''] = '2.0' - _(None, mock.Mock(subproject=''), [], {'input': 'foo'}) - self.assertRegex(out.getvalue(), r'WARNING:.*deprecated.*input arg in testfunc') - self.assertNotRegex(out.getvalue(), r'WARNING:.*introduced.*input arg in testfunc') - - def test_typed_kwarg_validator(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', str, validator=lambda x: 'invalid!' if x != 'foo' else None) - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None: - pass - - # Should be valid - _(None, mock.Mock(), tuple(), dict(input='foo')) - - with self.assertRaises(MesonException) as cm: - _(None, mock.Mock(), tuple(), dict(input='bar')) - self.assertEqual(str(cm.exception), "testfunc keyword argument \"input\" invalid!") - - def test_typed_kwarg_convertor(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('native', bool, convertor=lambda n: MachineChoice.BUILD if n else MachineChoice.HOST) - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, MachineChoice]) -> None: - assert isinstance(kwargs['native'], MachineChoice) - - _(None, mock.Mock(), tuple(), dict(native=True)) - - @mock.patch.dict(mesonbuild.mesonlib.project_meson_versions, {'': '1.0'}) - def test_typed_kwarg_since_values(self) -> None: - @typed_kwargs( - 'testfunc', - KwargInfo('input', ContainerTypeInfo(list, str), listify=True, default=[], deprecated_values={'foo': '0.9'}, since_values={'bar': '1.1'}), - KwargInfo('output', ContainerTypeInfo(dict, str), default={}, deprecated_values={'foo': '0.9'}, since_values={'bar': '1.1'}), - KwargInfo( - 'mode', str, - validator=lambda x: 'Should be one of "clean", "build", "rebuild"' if x not in {'clean', 'build', 'rebuild', 'deprecated', 'since'} else None, - deprecated_values={'deprecated': '1.0'}, - since_values={'since': '1.1'}), - ) - def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None: - pass - - with mock.patch('sys.stdout', io.StringIO()) as out: - _(None, mock.Mock(subproject=''), [], {'input': ['foo']}) - self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '0.9': "testfunc" keyword argument "input" value "foo".*""") - - with mock.patch('sys.stdout', io.StringIO()) as out: - _(None, mock.Mock(subproject=''), [], {'input': ['bar']}) - self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "input" value "bar".*""") - - with mock.patch('sys.stdout', io.StringIO()) as out: - _(None, mock.Mock(subproject=''), [], {'output': {'foo': 'a'}}) - self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo".*""") - - with mock.patch('sys.stdout', io.StringIO()) as out: - _(None, mock.Mock(subproject=''), [], {'output': {'bar': 'b'}}) - self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar".*""") - - with mock.patch('sys.stdout', io.StringIO()) as out: - _(None, mock.Mock(subproject=''), [], {'mode': 'deprecated'}) - self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '1.0': "testfunc" keyword argument "mode" value "deprecated".*""") - - with mock.patch('sys.stdout', io.StringIO()) as out: - _(None, mock.Mock(subproject=''), [], {'mode': 'since'}) - self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "mode" value "since".*""") - - def test_typed_kwarg_evolve(self) -> None: - k = KwargInfo('foo', str, required=True, default='foo') - v = k.evolve(default='bar') - self.assertEqual(k.name, 'foo') - self.assertEqual(k.name, v.name) - self.assertEqual(k.types, str) - self.assertEqual(k.types, v.types) - self.assertEqual(k.required, True) - self.assertEqual(k.required, v.required) - self.assertEqual(k.default, 'foo') - self.assertEqual(v.default, 'bar') - - -@unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release') -class DataTests(unittest.TestCase): - - def test_snippets(self): - hashcounter = re.compile('^ *(#)+') - snippet_dir = Path('docs/markdown/snippets') - self.assertTrue(snippet_dir.is_dir()) - for f in snippet_dir.glob('*'): - self.assertTrue(f.is_file()) - if f.parts[-1].endswith('~'): - continue - if f.suffix == '.md': - in_code_block = False - with f.open(encoding='utf-8') as snippet: - for line in snippet: - if line.startswith(' '): - continue - if line.startswith('```'): - in_code_block = not in_code_block - if in_code_block: - continue - m = re.match(hashcounter, line) - if m: - self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name) - self.assertFalse(in_code_block, 'Unclosed code block.') - else: - if f.name != 'add_release_note_snippets_here': - self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name) - - def test_compiler_options_documented(self): - ''' - Test that C and C++ compiler options and base options are documented in - Builtin-Options.md. Only tests the default compiler for the current - platform on the CI. - ''' - md = None - with open('docs/markdown/Builtin-options.md', encoding='utf-8') as f: - md = f.read() - self.assertIsNotNone(md) - env = get_fake_env() - # FIXME: Support other compilers - cc = detect_c_compiler(env, MachineChoice.HOST) - cpp = detect_cpp_compiler(env, MachineChoice.HOST) - for comp in (cc, cpp): - for opt in comp.get_options(): - self.assertIn(str(opt), md) - for opt in comp.base_options: - self.assertIn(str(opt), md) - self.assertNotIn('b_unknown', md) - - @staticmethod - def _get_section_content(name, sections, md): - for section in sections: - if section and section.group(1) == name: - try: - next_section = next(sections) - end = next_section.start() - except StopIteration: - end = len(md) - # Extract the content for this section - return md[section.end():end] - raise RuntimeError(f'Could not find "{name}" heading') - - def test_builtin_options_documented(self): - ''' - Test that universal options and base options are documented in - Builtin-Options.md. - ''' - from itertools import tee - md = None - with open('docs/markdown/Builtin-options.md', encoding='utf-8') as f: - md = f.read() - self.assertIsNotNone(md) - - found_entries = set() - sections = re.finditer(r"^## (.+)$", md, re.MULTILINE) - # Extract the content for this section - content = self._get_section_content("Universal options", sections, md) - subsections = tee(re.finditer(r"^### (.+)$", content, re.MULTILINE)) - subcontent1 = self._get_section_content("Directories", subsections[0], content) - subcontent2 = self._get_section_content("Core options", subsections[1], content) - for subcontent in (subcontent1, subcontent2): - # Find the option names - options = set() - # Match either a table row or a table heading separator: | ------ | - rows = re.finditer(r"^\|(?: (\w+) .* | *-+ *)\|", subcontent, re.MULTILINE) - # Skip the header of the first table - next(rows) - # Skip the heading separator of the first table - next(rows) - for m in rows: - value = m.group(1) - # End when the `buildtype` table starts - if value is None: - break - options.add(value) - self.assertEqual(len(found_entries & options), 0) - found_entries |= options - - self.assertEqual(found_entries, { - *[str(k) for k in mesonbuild.coredata.BUILTIN_OPTIONS], - *[str(k) for k in mesonbuild.coredata.BUILTIN_OPTIONS_PER_MACHINE], - }) - - # Check that `buildtype` table inside `Core options` matches how - # setting of builtin options behaves - # - # Find all tables inside this subsection - tables = re.finditer(r"^\| (\w+) .* \|\n\| *[-|\s]+ *\|$", subcontent2, re.MULTILINE) - # Get the table we want using the header of the first column - table = self._get_section_content('buildtype', tables, subcontent2) - # Get table row data - rows = re.finditer(r"^\|(?: (\w+)\s+\| (\w+)\s+\| (\w+) .* | *-+ *)\|", table, re.MULTILINE) - env = get_fake_env() - for m in rows: - buildtype, debug, opt = m.groups() - if debug == 'true': - debug = True - elif debug == 'false': - debug = False - else: - raise RuntimeError(f'Invalid debug value {debug!r} in row:\n{m.group()}') - env.coredata.set_option(OptionKey('buildtype'), buildtype) - self.assertEqual(env.coredata.options[OptionKey('buildtype')].value, buildtype) - self.assertEqual(env.coredata.options[OptionKey('optimization')].value, opt) - self.assertEqual(env.coredata.options[OptionKey('debug')].value, debug) - - def test_cpu_families_documented(self): - with open("docs/markdown/Reference-tables.md", encoding='utf-8') as f: - md = f.read() - self.assertIsNotNone(md) - - sections = re.finditer(r"^## (.+)$", md, re.MULTILINE) - content = self._get_section_content("CPU families", sections, md) - # Find the list entries - arches = [m.group(1) for m in re.finditer(r"^\| (\w+) +\|", content, re.MULTILINE)] - # Drop the header - arches = set(arches[1:]) - self.assertEqual(arches, set(mesonbuild.environment.known_cpu_families)) - - def test_markdown_files_in_sitemap(self): - ''' - Test that each markdown files in docs/markdown is referenced in sitemap.txt - ''' - with open("docs/sitemap.txt", encoding='utf-8') as f: - md = f.read() - self.assertIsNotNone(md) - toc = list(m.group(1) for m in re.finditer(r"^\s*(\w.*)$", md, re.MULTILINE)) - markdownfiles = [f.name for f in Path("docs/markdown").iterdir() if f.is_file() and f.suffix == '.md'] - exceptions = ['_Sidebar.md'] - for f in markdownfiles: - if f not in exceptions and not f.startswith('_include'): - self.assertIn(f, toc) - - def test_modules_in_navbar(self): - ''' - Test that each module is referenced in navbar_links.html - ''' - with open("docs/theme/extra/templates/navbar_links.html", encoding='utf-8') as f: - html = f.read().lower() - self.assertIsNotNone(html) - for f in Path('mesonbuild/modules').glob('*.py'): - if f.name in {'modtest.py', 'qt.py', '__init__.py'}: - continue - name = f'{f.stem}-module.html' - name = name.replace('unstable_', '') - name = name.replace('python3', 'python-3') - name = name.replace('_', '-') - self.assertIn(name, html) - - def test_vim_syntax_highlighting(self): - ''' - Ensure that vim syntax highlighting files were updated for new - functions in the global namespace in build files. - ''' - env = get_fake_env() - interp = Interpreter(FakeBuild(env), mock=True) - with open('data/syntax-highlighting/vim/syntax/meson.vim', encoding='utf-8') as f: - res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE) - defined = set([a.strip() for a in res.group().split('\\')][1:]) - self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys()))) - - def test_all_functions_defined_in_ast_interpreter(self): - ''' - Ensure that the all functions defined in the Interpreter are also defined - in the AstInterpreter (and vice versa). - ''' - env = get_fake_env() - interp = Interpreter(FakeBuild(env), mock=True) - astint = AstInterpreter('.', '', '') - self.assertEqual(set(interp.funcs.keys()), set(astint.funcs.keys())) - - def test_mesondata_is_up_to_date(self): - from mesonbuild.mesondata import mesondata - err_msg = textwrap.dedent(''' - - ########################################################### - ### mesonbuild.mesondata is not up-to-date ### - ### Please regenerate it by running tools/gen_data.py ### - ########################################################### - - ''') - - root_dir = Path(__file__).resolve().parent - mesonbuild_dir = root_dir / 'mesonbuild' - - data_dirs = mesonbuild_dir.glob('**/data') - data_files = [] # type: T.List[T.Tuple(str, str)] - - for i in data_dirs: - for p in i.iterdir(): - data_files += [(p.relative_to(mesonbuild_dir).as_posix(), hashlib.sha256(p.read_bytes()).hexdigest())] - - current_files = set(mesondata.keys()) - scanned_files = {x[0] for x in data_files} - - self.assertSetEqual(current_files, scanned_files, err_msg + 'Data files were added or removed\n') - errors = [] - for i in data_files: - if mesondata[i[0]].sha256sum != i[1]: - errors += [i[0]] - - self.assertListEqual(errors, [], err_msg + 'Files were changed') - -class BasePlatformTests(unittest.TestCase): - prefix = '/usr' - libdir = 'lib' - - def setUp(self): - super().setUp() - self.maxDiff = None - src_root = os.path.dirname(__file__) - src_root = os.path.join(os.getcwd(), src_root) - self.src_root = src_root - # Get the backend - self.backend = getattr(Backend, os.environ['MESON_UNIT_TEST_BACKEND']) - self.meson_args = ['--backend=' + self.backend.name] - self.meson_native_file = None - self.meson_cross_file = None - self.meson_command = python_command + [get_meson_script()] - self.setup_command = self.meson_command + self.meson_args - self.mconf_command = self.meson_command + ['configure'] - self.mintro_command = self.meson_command + ['introspect'] - self.wrap_command = self.meson_command + ['wrap'] - self.rewrite_command = self.meson_command + ['rewrite'] - # Backend-specific build commands - self.build_command, self.clean_command, self.test_command, self.install_command, \ - self.uninstall_command = get_backend_commands(self.backend) - # Test directories - self.common_test_dir = os.path.join(src_root, 'test cases/common') - self.vala_test_dir = os.path.join(src_root, 'test cases/vala') - self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks') - self.unit_test_dir = os.path.join(src_root, 'test cases/unit') - self.rewrite_test_dir = os.path.join(src_root, 'test cases/rewrite') - self.linuxlike_test_dir = os.path.join(src_root, 'test cases/linuxlike') - self.objc_test_dir = os.path.join(src_root, 'test cases/objc') - self.objcpp_test_dir = os.path.join(src_root, 'test cases/objcpp') - - # Misc stuff - self.orig_env = os.environ.copy() - if self.backend is Backend.ninja: - self.no_rebuild_stdout = ['ninja: no work to do.', 'samu: nothing to do'] - else: - # VS doesn't have a stable output when no changes are done - # XCode backend is untested with unit tests, help welcome! - self.no_rebuild_stdout = [f'UNKNOWN BACKEND {self.backend.name!r}'] - - self.builddirs = [] - self.new_builddir() - - def change_builddir(self, newdir): - self.builddir = newdir - self.privatedir = os.path.join(self.builddir, 'meson-private') - self.logdir = os.path.join(self.builddir, 'meson-logs') - self.installdir = os.path.join(self.builddir, 'install') - self.distdir = os.path.join(self.builddir, 'meson-dist') - self.mtest_command = self.meson_command + ['test', '-C', self.builddir] - self.builddirs.append(self.builddir) - - def new_builddir(self): - if not is_cygwin(): - # Keep builddirs inside the source tree so that virus scanners - # don't complain - newdir = tempfile.mkdtemp(dir=os.getcwd()) - else: - # But not on Cygwin because that breaks the umask tests. See: - # https://github.com/mesonbuild/meson/pull/5546#issuecomment-509666523 - newdir = tempfile.mkdtemp() - # In case the directory is inside a symlinked directory, find the real - # path otherwise we might not find the srcdir from inside the builddir. - newdir = os.path.realpath(newdir) - self.change_builddir(newdir) - - def _print_meson_log(self): - log = os.path.join(self.logdir, 'meson-log.txt') - if not os.path.isfile(log): - print(f"{log!r} doesn't exist") - return - with open(log, encoding='utf-8') as f: - print(f.read()) - - def tearDown(self): - for path in self.builddirs: - try: - windows_proof_rmtree(path) - except FileNotFoundError: - pass - os.environ.clear() - os.environ.update(self.orig_env) - super().tearDown() - - def _run(self, command, *, workdir=None, override_envvars=None): - ''' - Run a command while printing the stdout and stderr to stdout, - and also return a copy of it - ''' - # If this call hangs CI will just abort. It is very hard to distinguish - # between CI issue and test bug in that case. Set timeout and fail loud - # instead. - if override_envvars is None: - env = None - else: - env = os.environ.copy() - env.update(override_envvars) - - p = subprocess.run(command, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, env=env, - encoding='utf-8', - universal_newlines=True, cwd=workdir, timeout=60 * 5) - print(p.stdout) - if p.returncode != 0: - if 'MESON_SKIP_TEST' in p.stdout: - raise unittest.SkipTest('Project requested skipping.') - raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout) - return p.stdout - - def init(self, srcdir, *, - extra_args=None, - default_args=True, - inprocess=False, - override_envvars=None, - workdir=None): - self.assertPathExists(srcdir) - if extra_args is None: - extra_args = [] - if not isinstance(extra_args, list): - extra_args = [extra_args] - args = [srcdir, self.builddir] - if default_args: - args += ['--prefix', self.prefix] - if self.libdir: - args += ['--libdir', self.libdir] - if self.meson_native_file: - args += ['--native-file', self.meson_native_file] - if self.meson_cross_file: - args += ['--cross-file', self.meson_cross_file] - self.privatedir = os.path.join(self.builddir, 'meson-private') - if inprocess: - try: - (returncode, out, err) = run_configure_inprocess(self.meson_args + args + extra_args, override_envvars) - if 'MESON_SKIP_TEST' in out: - raise unittest.SkipTest('Project requested skipping.') - if returncode != 0: - self._print_meson_log() - print('Stdout:\n') - print(out) - print('Stderr:\n') - print(err) - raise RuntimeError('Configure failed') - except Exception: - self._print_meson_log() - raise - finally: - # Close log file to satisfy Windows file locking - mesonbuild.mlog.shutdown() - mesonbuild.mlog.log_dir = None - mesonbuild.mlog.log_file = None - else: - try: - out = self._run(self.setup_command + args + extra_args, override_envvars=override_envvars, workdir=workdir) - except unittest.SkipTest: - raise unittest.SkipTest('Project requested skipping: ' + srcdir) - except Exception: - self._print_meson_log() - raise - return out - - def build(self, target=None, *, extra_args=None, override_envvars=None): - if extra_args is None: - extra_args = [] - # Add arguments for building the target (if specified), - # and using the build dir (if required, with VS) - args = get_builddir_target_args(self.backend, self.builddir, target) - return self._run(self.build_command + args + extra_args, workdir=self.builddir, override_envvars=override_envvars) - - def clean(self, *, override_envvars=None): - dir_args = get_builddir_target_args(self.backend, self.builddir, None) - self._run(self.clean_command + dir_args, workdir=self.builddir, override_envvars=override_envvars) - - def run_tests(self, *, inprocess=False, override_envvars=None): - if not inprocess: - self._run(self.test_command, workdir=self.builddir, override_envvars=override_envvars) - else: - with mock.patch.dict(os.environ, override_envvars): - run_mtest_inprocess(['-C', self.builddir]) - - def install(self, *, use_destdir=True, override_envvars=None): - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'{self.backend.name!r} backend can\'t install files') - if use_destdir: - destdir = {'DESTDIR': self.installdir} - if override_envvars is None: - override_envvars = destdir - else: - override_envvars.update(destdir) - self._run(self.install_command, workdir=self.builddir, override_envvars=override_envvars) - - def uninstall(self, *, override_envvars=None): - self._run(self.uninstall_command, workdir=self.builddir, override_envvars=override_envvars) - - def run_target(self, target, *, override_envvars=None): - ''' - Run a Ninja target while printing the stdout and stderr to stdout, - and also return a copy of it - ''' - return self.build(target=target, override_envvars=override_envvars) - - def setconf(self, arg, will_build=True): - if not isinstance(arg, list): - arg = [arg] - if will_build: - ensure_backend_detects_changes(self.backend) - self._run(self.mconf_command + arg + [self.builddir]) - - def wipe(self): - windows_proof_rmtree(self.builddir) - - def utime(self, f): - ensure_backend_detects_changes(self.backend) - os.utime(f) - - def get_compdb(self): - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'Compiler db not available with {self.backend.name} backend') - try: - with open(os.path.join(self.builddir, 'compile_commands.json'), encoding='utf-8') as ifile: - contents = json.load(ifile) - except FileNotFoundError: - raise unittest.SkipTest('Compiler db not found') - # If Ninja is using .rsp files, generate them, read their contents, and - # replace it as the command for all compile commands in the parsed json. - if len(contents) > 0 and contents[0]['command'].endswith('.rsp'): - # Pretend to build so that the rsp files are generated - self.build(extra_args=['-d', 'keeprsp', '-n']) - for each in contents: - # Extract the actual command from the rsp file - compiler, rsp = each['command'].split(' @') - rsp = os.path.join(self.builddir, rsp) - # Replace the command with its contents - with open(rsp, encoding='utf-8') as f: - each['command'] = compiler + ' ' + f.read() - return contents - - def get_meson_log(self): - with open(os.path.join(self.builddir, 'meson-logs', 'meson-log.txt'), encoding='utf-8') as f: - return f.readlines() - - def get_meson_log_compiler_checks(self): - ''' - Fetch a list command-lines run by meson for compiler checks. - Each command-line is returned as a list of arguments. - ''' - log = self.get_meson_log() - prefix = 'Command line:' - cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)] - return cmds - - def get_meson_log_sanitychecks(self): - ''' - Same as above, but for the sanity checks that were run - ''' - log = self.get_meson_log() - prefix = 'Sanity check compiler command line:' - cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)] - return cmds - - def introspect(self, args): - if isinstance(args, str): - args = [args] - out = subprocess.check_output(self.mintro_command + args + [self.builddir], - universal_newlines=True) - return json.loads(out) - - def introspect_directory(self, directory, args): - if isinstance(args, str): - args = [args] - out = subprocess.check_output(self.mintro_command + args + [directory], - universal_newlines=True) - try: - obj = json.loads(out) - except Exception as e: - print(out) - raise e - return obj - - def assertPathEqual(self, path1, path2): - ''' - Handles a lot of platform-specific quirks related to paths such as - separator, case-sensitivity, etc. - ''' - self.assertEqual(PurePath(path1), PurePath(path2)) - - def assertPathListEqual(self, pathlist1, pathlist2): - self.assertEqual(len(pathlist1), len(pathlist2)) - worklist = list(zip(pathlist1, pathlist2)) - for i in worklist: - if i[0] is None: - self.assertEqual(i[0], i[1]) - else: - self.assertPathEqual(i[0], i[1]) - - def assertPathBasenameEqual(self, path, basename): - msg = f'{path!r} does not end with {basename!r}' - # We cannot use os.path.basename because it returns '' when the path - # ends with '/' for some silly reason. This is not how the UNIX utility - # `basename` works. - path_basename = PurePath(path).parts[-1] - self.assertEqual(PurePath(path_basename), PurePath(basename), msg) - - def assertReconfiguredBuildIsNoop(self): - 'Assert that we reconfigured and then there was nothing to do' - ret = self.build() - self.assertIn('The Meson build system', ret) - if self.backend is Backend.ninja: - for line in ret.split('\n'): - if line in self.no_rebuild_stdout: - break - else: - raise AssertionError('build was reconfigured, but was not no-op') - elif self.backend is Backend.vs: - # Ensure that some target said that no rebuild was done - # XXX: Note CustomBuild did indeed rebuild, because of the regen checker! - self.assertIn('ClCompile:\n All outputs are up-to-date.', ret) - self.assertIn('Link:\n All outputs are up-to-date.', ret) - # Ensure that no targets were built - self.assertNotRegex(ret, re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE)) - self.assertNotRegex(ret, re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE)) - elif self.backend is Backend.xcode: - raise unittest.SkipTest('Please help us fix this test on the xcode backend') - else: - raise RuntimeError(f'Invalid backend: {self.backend.name!r}') - - def assertBuildIsNoop(self): - ret = self.build() - if self.backend is Backend.ninja: - self.assertIn(ret.split('\n')[-2], self.no_rebuild_stdout) - elif self.backend is Backend.vs: - # Ensure that some target of each type said that no rebuild was done - # We always have at least one CustomBuild target for the regen checker - self.assertIn('CustomBuild:\n All outputs are up-to-date.', ret) - self.assertIn('ClCompile:\n All outputs are up-to-date.', ret) - self.assertIn('Link:\n All outputs are up-to-date.', ret) - # Ensure that no targets were built - self.assertNotRegex(ret, re.compile('CustomBuild:\n [^\n]*cl', flags=re.IGNORECASE)) - self.assertNotRegex(ret, re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE)) - self.assertNotRegex(ret, re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE)) - elif self.backend is Backend.xcode: - raise unittest.SkipTest('Please help us fix this test on the xcode backend') - else: - raise RuntimeError(f'Invalid backend: {self.backend.name!r}') - - def assertRebuiltTarget(self, target): - ret = self.build() - if self.backend is Backend.ninja: - self.assertIn(f'Linking target {target}', ret) - elif self.backend is Backend.vs: - # Ensure that this target was rebuilt - linkre = re.compile('Link:\n [^\n]*link[^\n]*' + target, flags=re.IGNORECASE) - self.assertRegex(ret, linkre) - elif self.backend is Backend.xcode: - raise unittest.SkipTest('Please help us fix this test on the xcode backend') - else: - raise RuntimeError(f'Invalid backend: {self.backend.name!r}') - - @staticmethod - def get_target_from_filename(filename): - base = os.path.splitext(filename)[0] - if base.startswith(('lib', 'cyg')): - return base[3:] - return base - - def assertBuildRelinkedOnlyTarget(self, target): - ret = self.build() - if self.backend is Backend.ninja: - linked_targets = [] - for line in ret.split('\n'): - if 'Linking target' in line: - fname = line.rsplit('target ')[-1] - linked_targets.append(self.get_target_from_filename(fname)) - self.assertEqual(linked_targets, [target]) - elif self.backend is Backend.vs: - # Ensure that this target was rebuilt - linkre = re.compile(r'Link:\n [^\n]*link.exe[^\n]*/OUT:".\\([^"]*)"', flags=re.IGNORECASE) - matches = linkre.findall(ret) - self.assertEqual(len(matches), 1, msg=matches) - self.assertEqual(self.get_target_from_filename(matches[0]), target) - elif self.backend is Backend.xcode: - raise unittest.SkipTest('Please help us fix this test on the xcode backend') - else: - raise RuntimeError(f'Invalid backend: {self.backend.name!r}') - - def assertPathExists(self, path): - m = f'Path {path!r} should exist' - self.assertTrue(os.path.exists(path), msg=m) - - def assertPathDoesNotExist(self, path): - m = f'Path {path!r} should not exist' - self.assertFalse(os.path.exists(path), msg=m) - - -class AllPlatformTests(BasePlatformTests): - ''' - Tests that should run on all platforms - ''' - - def test_default_options_prefix(self): - ''' - Tests that setting a prefix in default_options in project() works. - Can't be an ordinary test because we pass --prefix to meson there. - https://github.com/mesonbuild/meson/issues/1349 - ''' - testdir = os.path.join(self.common_test_dir, '87 default options') - self.init(testdir, default_args=False, inprocess=True) - opts = self.introspect('--buildoptions') - for opt in opts: - if opt['name'] == 'prefix': - prefix = opt['value'] - break - else: - raise self.fail('Did not find option "prefix"') - self.assertEqual(prefix, '/absoluteprefix') - - def test_do_conf_file_preserve_newlines(self): - - def conf_file(in_data, confdata): - with temp_filename() as fin: - with open(fin, 'wb') as fobj: - fobj.write(in_data.encode('utf-8')) - with temp_filename() as fout: - mesonbuild.mesonlib.do_conf_file(fin, fout, confdata, 'meson') - with open(fout, 'rb') as fobj: - return fobj.read().decode('utf-8') - - confdata = {'VAR': ('foo', 'bar')} - self.assertEqual(conf_file('@VAR@\n@VAR@\n', confdata), 'foo\nfoo\n') - self.assertEqual(conf_file('@VAR@\r\n@VAR@\r\n', confdata), 'foo\r\nfoo\r\n') - - def test_do_conf_file_by_format(self): - def conf_str(in_data, confdata, vformat): - (result, missing_variables, confdata_useless) = mesonbuild.mesonlib.do_conf_str('configuration_file', in_data, confdata, variable_format = vformat) - return '\n'.join(result) - - def check_formats(confdata, result): - self.assertEqual(conf_str(['#mesondefine VAR'], confdata, 'meson'), result) - self.assertEqual(conf_str(['#cmakedefine VAR ${VAR}'], confdata, 'cmake'), result) - self.assertEqual(conf_str(['#cmakedefine VAR @VAR@'], confdata, 'cmake@'), result) - - confdata = ConfigurationData() - # Key error as they do not exists - check_formats(confdata, '/* #undef VAR */\n') - - # Check boolean - confdata.values = {'VAR': (False, 'description')} - check_formats(confdata, '#undef VAR\n') - confdata.values = {'VAR': (True, 'description')} - check_formats(confdata, '#define VAR\n') - - # Check string - confdata.values = {'VAR': ('value', 'description')} - check_formats(confdata, '#define VAR value\n') - - # Check integer - confdata.values = {'VAR': (10, 'description')} - check_formats(confdata, '#define VAR 10\n') - - # Check multiple string with cmake formats - confdata.values = {'VAR': ('value', 'description')} - self.assertEqual(conf_str(['#cmakedefine VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value\n') - self.assertEqual(conf_str(['#define VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value') - self.assertEqual(conf_str(['#cmakedefine VAR xxx ${VAR} yyy ${VAR}'], confdata, 'cmake'), '#define VAR xxx value yyy value\n') - self.assertEqual(conf_str(['#define VAR xxx ${VAR} yyy ${VAR}'], confdata, 'cmake'), '#define VAR xxx value yyy value') - - # Handles meson format exceptions - # Unknown format - self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR xxx'], confdata, 'unknown_format') - # More than 2 params in mesondefine - self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR xxx'], confdata, 'meson') - # Mismatched line with format - self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#cmakedefine VAR'], confdata, 'meson') - self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR'], confdata, 'cmake') - self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR'], confdata, 'cmake@') - # Dict value in confdata - confdata.values = {'VAR': (['value'], 'description')} - self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR'], confdata, 'meson') - - def test_absolute_prefix_libdir(self): - ''' - Tests that setting absolute paths for --prefix and --libdir work. Can't - be an ordinary test because these are set via the command-line. - https://github.com/mesonbuild/meson/issues/1341 - https://github.com/mesonbuild/meson/issues/1345 - ''' - testdir = os.path.join(self.common_test_dir, '87 default options') - # on Windows, /someabs is *not* an absolute path - prefix = 'x:/someabs' if is_windows() else '/someabs' - libdir = 'libdir' - extra_args = ['--prefix=' + prefix, - # This can just be a relative path, but we want to test - # that passing this as an absolute path also works - '--libdir=' + prefix + '/' + libdir] - self.init(testdir, extra_args=extra_args, default_args=False) - opts = self.introspect('--buildoptions') - for opt in opts: - if opt['name'] == 'prefix': - self.assertEqual(prefix, opt['value']) - elif opt['name'] == 'libdir': - self.assertEqual(libdir, opt['value']) - - def test_libdir_must_be_inside_prefix(self): - ''' - Tests that libdir is forced to be inside prefix no matter how it is set. - Must be a unit test for obvious reasons. - ''' - testdir = os.path.join(self.common_test_dir, '1 trivial') - # libdir being inside prefix is ok - if is_windows(): - args = ['--prefix', 'x:/opt', '--libdir', 'x:/opt/lib32'] - else: - args = ['--prefix', '/opt', '--libdir', '/opt/lib32'] - self.init(testdir, extra_args=args) - self.wipe() - # libdir not being inside prefix is not ok - if is_windows(): - args = ['--prefix', 'x:/usr', '--libdir', 'x:/opt/lib32'] - else: - args = ['--prefix', '/usr', '--libdir', '/opt/lib32'] - self.assertRaises(subprocess.CalledProcessError, self.init, testdir, extra_args=args) - self.wipe() - # libdir must be inside prefix even when set via mesonconf - self.init(testdir) - if is_windows(): - self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=x:/opt', False) - else: - self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt', False) - - def test_prefix_dependent_defaults(self): - ''' - Tests that configured directory paths are set to prefix dependent - defaults. - ''' - testdir = os.path.join(self.common_test_dir, '1 trivial') - expected = { - '/opt': {'prefix': '/opt', - 'bindir': 'bin', 'datadir': 'share', 'includedir': 'include', - 'infodir': 'share/info', - 'libexecdir': 'libexec', 'localedir': 'share/locale', - 'localstatedir': 'var', 'mandir': 'share/man', - 'sbindir': 'sbin', 'sharedstatedir': 'com', - 'sysconfdir': 'etc'}, - '/usr': {'prefix': '/usr', - 'bindir': 'bin', 'datadir': 'share', 'includedir': 'include', - 'infodir': 'share/info', - 'libexecdir': 'libexec', 'localedir': 'share/locale', - 'localstatedir': '/var', 'mandir': 'share/man', - 'sbindir': 'sbin', 'sharedstatedir': '/var/lib', - 'sysconfdir': '/etc'}, - '/usr/local': {'prefix': '/usr/local', - 'bindir': 'bin', 'datadir': 'share', - 'includedir': 'include', 'infodir': 'share/info', - 'libexecdir': 'libexec', - 'localedir': 'share/locale', - 'localstatedir': '/var/local', 'mandir': 'share/man', - 'sbindir': 'sbin', 'sharedstatedir': '/var/local/lib', - 'sysconfdir': 'etc'}, - # N.B. We don't check 'libdir' as it's platform dependent, see - # default_libdir(): - } - - if mesonbuild.mesonlib.default_prefix() == '/usr/local': - expected[None] = expected['/usr/local'] - - for prefix in expected: - args = [] - if prefix: - args += ['--prefix', prefix] - self.init(testdir, extra_args=args, default_args=False) - opts = self.introspect('--buildoptions') - for opt in opts: - name = opt['name'] - value = opt['value'] - if name in expected[prefix]: - self.assertEqual(value, expected[prefix][name]) - self.wipe() - - def test_default_options_prefix_dependent_defaults(self): - ''' - Tests that setting a prefix in default_options in project() sets prefix - dependent defaults for other options, and that those defaults can - be overridden in default_options or by the command line. - ''' - testdir = os.path.join(self.common_test_dir, '163 default options prefix dependent defaults') - expected = { - '': - {'prefix': '/usr', - 'sysconfdir': '/etc', - 'localstatedir': '/var', - 'sharedstatedir': '/sharedstate'}, - '--prefix=/usr': - {'prefix': '/usr', - 'sysconfdir': '/etc', - 'localstatedir': '/var', - 'sharedstatedir': '/sharedstate'}, - '--sharedstatedir=/var/state': - {'prefix': '/usr', - 'sysconfdir': '/etc', - 'localstatedir': '/var', - 'sharedstatedir': '/var/state'}, - '--sharedstatedir=/var/state --prefix=/usr --sysconfdir=sysconf': - {'prefix': '/usr', - 'sysconfdir': 'sysconf', - 'localstatedir': '/var', - 'sharedstatedir': '/var/state'}, - } - for args in expected: - self.init(testdir, extra_args=args.split(), default_args=False) - opts = self.introspect('--buildoptions') - for opt in opts: - name = opt['name'] - value = opt['value'] - if name in expected[args]: - self.assertEqual(value, expected[args][name]) - self.wipe() - - def test_clike_get_library_dirs(self): - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - for d in cc.get_library_dirs(env): - self.assertTrue(os.path.exists(d)) - self.assertTrue(os.path.isdir(d)) - self.assertTrue(os.path.isabs(d)) - - def test_static_library_overwrite(self): - ''' - Tests that static libraries are never appended to, always overwritten. - Has to be a unit test because this involves building a project, - reconfiguring, and building it again so that `ar` is run twice on the - same static library. - https://github.com/mesonbuild/meson/issues/1355 - ''' - testdir = os.path.join(self.common_test_dir, '3 static') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - static_linker = detect_static_linker(env, cc) - if is_windows(): - raise unittest.SkipTest('https://github.com/mesonbuild/meson/issues/1526') - if not isinstance(static_linker, mesonbuild.linkers.ArLinker): - raise unittest.SkipTest('static linker is not `ar`') - # Configure - self.init(testdir) - # Get name of static library - targets = self.introspect('--targets') - self.assertEqual(len(targets), 1) - libname = targets[0]['filename'][0] - # Build and get contents of static library - self.build() - before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split() - # Filter out non-object-file contents - before = [f for f in before if f.endswith(('.o', '.obj'))] - # Static library should contain only one object - self.assertEqual(len(before), 1, msg=before) - # Change the source to be built into the static library - self.setconf('-Dsource=libfile2.c') - self.build() - after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split() - # Filter out non-object-file contents - after = [f for f in after if f.endswith(('.o', '.obj'))] - # Static library should contain only one object - self.assertEqual(len(after), 1, msg=after) - # and the object must have changed - self.assertNotEqual(before, after) - - def test_static_compile_order(self): - ''' - Test that the order of files in a compiler command-line while compiling - and linking statically is deterministic. This can't be an ordinary test - case because we need to inspect the compiler database. - https://github.com/mesonbuild/meson/pull/951 - ''' - testdir = os.path.join(self.common_test_dir, '5 linkstatic') - self.init(testdir) - compdb = self.get_compdb() - # Rules will get written out in this order - self.assertTrue(compdb[0]['file'].endswith("libfile.c")) - self.assertTrue(compdb[1]['file'].endswith("libfile2.c")) - self.assertTrue(compdb[2]['file'].endswith("libfile3.c")) - self.assertTrue(compdb[3]['file'].endswith("libfile4.c")) - # FIXME: We don't have access to the linker command - - def test_run_target_files_path(self): - ''' - Test that run_targets are run from the correct directory - https://github.com/mesonbuild/meson/issues/957 - ''' - testdir = os.path.join(self.common_test_dir, '51 run target') - self.init(testdir) - self.run_target('check_exists') - self.run_target('check-env') - self.run_target('check-env-ct') - - def test_run_target_subdir(self): - ''' - Test that run_targets are run from the correct directory - https://github.com/mesonbuild/meson/issues/957 - ''' - testdir = os.path.join(self.common_test_dir, '51 run target') - self.init(testdir) - self.run_target('textprinter') - - def test_install_introspection(self): - ''' - Tests that the Meson introspection API exposes install filenames correctly - https://github.com/mesonbuild/meson/issues/829 - ''' - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'{self.backend.name!r} backend can\'t install files') - testdir = os.path.join(self.common_test_dir, '8 install') - self.init(testdir) - intro = self.introspect('--targets') - if intro[0]['type'] == 'executable': - intro = intro[::-1] - self.assertPathListEqual(intro[0]['install_filename'], ['/usr/lib/libstat.a']) - self.assertPathListEqual(intro[1]['install_filename'], ['/usr/bin/prog' + exe_suffix]) - - def test_install_subdir_introspection(self): - ''' - Test that the Meson introspection API also contains subdir install information - https://github.com/mesonbuild/meson/issues/5556 - ''' - testdir = os.path.join(self.common_test_dir, '59 install subdir') - self.init(testdir) - intro = self.introspect('--installed') - expected = { - 'sub2': 'share/sub2', - 'subdir/sub1': 'share/sub1', - 'subdir/sub_elided': 'share', - 'sub1': 'share/sub1', - 'sub/sub1': 'share/sub1', - 'sub_elided': 'share', - 'nested_elided/sub': 'share', - 'new_directory': 'share/new_directory', - } - - self.assertEqual(len(intro), len(expected)) - - # Convert expected to PurePath - expected_converted = {PurePath(os.path.join(testdir, key)): PurePath(os.path.join(self.prefix, val)) for key, val in expected.items()} - intro_converted = {PurePath(key): PurePath(val) for key, val in intro.items()} - - for src, dst in expected_converted.items(): - self.assertIn(src, intro_converted) - self.assertEqual(dst, intro_converted[src]) - - def test_install_introspection_multiple_outputs(self): - ''' - Tests that the Meson introspection API exposes multiple install filenames correctly without crashing - https://github.com/mesonbuild/meson/pull/4555 - - Reverted to the first file only because of https://github.com/mesonbuild/meson/pull/4547#discussion_r244173438 - TODO Change the format to a list officially in a followup PR - ''' - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'{self.backend.name!r} backend can\'t install files') - testdir = os.path.join(self.common_test_dir, '140 custom target multiple outputs') - self.init(testdir) - intro = self.introspect('--targets') - if intro[0]['type'] == 'executable': - intro = intro[::-1] - self.assertPathListEqual(intro[0]['install_filename'], ['/usr/include/diff.h', '/usr/bin/diff.sh']) - self.assertPathListEqual(intro[1]['install_filename'], ['/opt/same.h', '/opt/same.sh']) - self.assertPathListEqual(intro[2]['install_filename'], ['/usr/include/first.h', None]) - self.assertPathListEqual(intro[3]['install_filename'], [None, '/usr/bin/second.sh']) - - def test_install_log_content(self): - ''' - Tests that the install-log.txt is consistent with the installed files and directories. - Specifically checks that the log file only contains one entry per file/directory. - https://github.com/mesonbuild/meson/issues/4499 - ''' - testdir = os.path.join(self.common_test_dir, '59 install subdir') - self.init(testdir) - self.install() - installpath = Path(self.installdir) - # Find installed files and directories - expected = {installpath: 0} - for name in installpath.rglob('*'): - expected[name] = 0 - def read_logs(): - # Find logged files and directories - with Path(self.builddir, 'meson-logs', 'install-log.txt').open(encoding='utf-8') as f: - return list(map(lambda l: Path(l.strip()), - filter(lambda l: not l.startswith('#'), - f.readlines()))) - logged = read_logs() - for name in logged: - self.assertTrue(name in expected, f'Log contains extra entry {name}') - expected[name] += 1 - - for name, count in expected.items(): - self.assertGreater(count, 0, f'Log is missing entry for {name}') - self.assertLess(count, 2, f'Log has multiple entries for {name}') - - # Verify that with --dry-run we obtain the same logs but with nothing - # actually installed - windows_proof_rmtree(self.installdir) - self._run(self.meson_command + ['install', '--dry-run', '--destdir', self.installdir], workdir=self.builddir) - self.assertEqual(logged, read_logs()) - self.assertFalse(os.path.exists(self.installdir)) - - def test_uninstall(self): - exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix) - dirname = os.path.join(self.installdir, 'usr/share/dir') - testdir = os.path.join(self.common_test_dir, '8 install') - self.init(testdir) - self.assertPathDoesNotExist(exename) - self.install() - self.assertPathExists(exename) - self.uninstall() - self.assertPathDoesNotExist(exename) - self.assertPathDoesNotExist(dirname) - - def test_forcefallback(self): - testdir = os.path.join(self.unit_test_dir, '31 forcefallback') - self.init(testdir, extra_args=['--wrap-mode=forcefallback']) - self.build() - self.run_tests() - - def test_implicit_forcefallback(self): - testdir = os.path.join(self.unit_test_dir, '96 implicit force fallback') - with self.assertRaises(subprocess.CalledProcessError) as cm: - self.init(testdir) - self.init(testdir, extra_args=['--wrap-mode=forcefallback']) - self.new_builddir() - self.init(testdir, extra_args=['--force-fallback-for=something']) - - def test_nopromote(self): - testdir = os.path.join(self.common_test_dir, '98 subproject subdir') - with self.assertRaises(subprocess.CalledProcessError) as cm: - self.init(testdir, extra_args=['--wrap-mode=nopromote']) - self.assertIn('dependency subsub found: NO', cm.exception.stdout) - - def test_force_fallback_for(self): - testdir = os.path.join(self.unit_test_dir, '31 forcefallback') - self.init(testdir, extra_args=['--force-fallback-for=zlib,foo']) - self.build() - self.run_tests() - - def test_testrepeat(self): - testdir = os.path.join(self.common_test_dir, '206 tap tests') - self.init(testdir) - self.build() - self._run(self.mtest_command + ['--repeat=2']) - - def test_testsetups(self): - if not shutil.which('valgrind'): - raise unittest.SkipTest('Valgrind not installed.') - testdir = os.path.join(self.unit_test_dir, '2 testsetups') - self.init(testdir) - self.build() - # Run tests without setup - self.run_tests() - with open(os.path.join(self.logdir, 'testlog.txt'), encoding='utf-8') as f: - basic_log = f.read() - # Run buggy test with setup that has env that will make it fail - self.assertRaises(subprocess.CalledProcessError, - self._run, self.mtest_command + ['--setup=valgrind']) - with open(os.path.join(self.logdir, 'testlog-valgrind.txt'), encoding='utf-8') as f: - vg_log = f.read() - self.assertFalse('TEST_ENV is set' in basic_log) - self.assertFalse('Memcheck' in basic_log) - self.assertTrue('TEST_ENV is set' in vg_log) - self.assertTrue('Memcheck' in vg_log) - # Run buggy test with setup without env that will pass - self._run(self.mtest_command + ['--setup=wrapper']) - # Setup with no properties works - self._run(self.mtest_command + ['--setup=empty']) - # Setup with only env works - self._run(self.mtest_command + ['--setup=onlyenv']) - self._run(self.mtest_command + ['--setup=onlyenv2']) - self._run(self.mtest_command + ['--setup=onlyenv3']) - # Setup with only a timeout works - self._run(self.mtest_command + ['--setup=timeout']) - # Setup that does not define a wrapper works with --wrapper - self._run(self.mtest_command + ['--setup=timeout', '--wrapper', shutil.which('valgrind')]) - # Setup that skips test works - self._run(self.mtest_command + ['--setup=good']) - with open(os.path.join(self.logdir, 'testlog-good.txt'), encoding='utf-8') as f: - exclude_suites_log = f.read() - self.assertFalse('buggy' in exclude_suites_log) - # --suite overrides add_test_setup(xclude_suites) - self._run(self.mtest_command + ['--setup=good', '--suite', 'buggy']) - with open(os.path.join(self.logdir, 'testlog-good.txt'), encoding='utf-8') as f: - include_suites_log = f.read() - self.assertTrue('buggy' in include_suites_log) - - def test_testsetup_selection(self): - testdir = os.path.join(self.unit_test_dir, '14 testsetup selection') - self.init(testdir) - self.build() - - # Run tests without setup - self.run_tests() - - self.assertRaises(subprocess.CalledProcessError, self._run, self.mtest_command + ['--setup=missingfromfoo']) - self._run(self.mtest_command + ['--setup=missingfromfoo', '--no-suite=foo:']) - - self._run(self.mtest_command + ['--setup=worksforall']) - self._run(self.mtest_command + ['--setup=main:worksforall']) - - self.assertRaises(subprocess.CalledProcessError, self._run, - self.mtest_command + ['--setup=onlyinbar']) - self.assertRaises(subprocess.CalledProcessError, self._run, - self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:']) - self._run(self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:', '--no-suite=foo:']) - self._run(self.mtest_command + ['--setup=bar:onlyinbar']) - self.assertRaises(subprocess.CalledProcessError, self._run, - self.mtest_command + ['--setup=foo:onlyinbar']) - self.assertRaises(subprocess.CalledProcessError, self._run, - self.mtest_command + ['--setup=main:onlyinbar']) - - def test_testsetup_default(self): - testdir = os.path.join(self.unit_test_dir, '49 testsetup default') - self.init(testdir) - self.build() - - # Run tests without --setup will cause the default setup to be used - self.run_tests() - with open(os.path.join(self.logdir, 'testlog.txt'), encoding='utf-8') as f: - default_log = f.read() - - # Run tests with explicitly using the same setup that is set as default - self._run(self.mtest_command + ['--setup=mydefault']) - with open(os.path.join(self.logdir, 'testlog-mydefault.txt'), encoding='utf-8') as f: - mydefault_log = f.read() - - # Run tests with another setup - self._run(self.mtest_command + ['--setup=other']) - with open(os.path.join(self.logdir, 'testlog-other.txt'), encoding='utf-8') as f: - other_log = f.read() - - self.assertTrue('ENV_A is 1' in default_log) - self.assertTrue('ENV_B is 2' in default_log) - self.assertTrue('ENV_C is 2' in default_log) - - self.assertTrue('ENV_A is 1' in mydefault_log) - self.assertTrue('ENV_B is 2' in mydefault_log) - self.assertTrue('ENV_C is 2' in mydefault_log) - - self.assertTrue('ENV_A is 1' in other_log) - self.assertTrue('ENV_B is 3' in other_log) - self.assertTrue('ENV_C is 2' in other_log) - - def assertFailedTestCount(self, failure_count, command): - try: - self._run(command) - self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count) - except subprocess.CalledProcessError as e: - self.assertEqual(e.returncode, failure_count) - - def test_suite_selection(self): - testdir = os.path.join(self.unit_test_dir, '4 suite selection') - self.init(testdir) - self.build() - - self.assertFailedTestCount(4, self.mtest_command) - - self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success']) - self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail']) - self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', ':success']) - self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', ':fail']) - - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc']) - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail']) - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj']) - self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix']) - - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:fail']) - self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'mainprj:success']) - - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:fail']) - self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjfail:success']) - - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success']) - self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:fail']) - self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:success']) - - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail']) - self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success']) - self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:fail']) - self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjmix:success']) - - self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail']) - self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj']) - self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail']) - self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test']) - - self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail']) - - def test_build_by_default(self): - testdir = os.path.join(self.common_test_dir, '129 build by default') - self.init(testdir) - self.build() - genfile1 = os.path.join(self.builddir, 'generated1.dat') - genfile2 = os.path.join(self.builddir, 'generated2.dat') - exe1 = os.path.join(self.builddir, 'fooprog' + exe_suffix) - exe2 = os.path.join(self.builddir, 'barprog' + exe_suffix) - self.assertPathExists(genfile1) - self.assertPathExists(genfile2) - self.assertPathDoesNotExist(exe1) - self.assertPathDoesNotExist(exe2) - self.build(target=('fooprog' + exe_suffix)) - self.assertPathExists(exe1) - self.build(target=('barprog' + exe_suffix)) - self.assertPathExists(exe2) - - def test_internal_include_order(self): - if mesonbuild.environment.detect_msys2_arch() and ('MESON_RSP_THRESHOLD' in os.environ): - raise unittest.SkipTest('Test does not yet support gcc rsp files on msys2') - - testdir = os.path.join(self.common_test_dir, '130 include order') - self.init(testdir) - execmd = fxecmd = None - for cmd in self.get_compdb(): - if 'someexe' in cmd['command']: - execmd = cmd['command'] - continue - if 'somefxe' in cmd['command']: - fxecmd = cmd['command'] - continue - if not execmd or not fxecmd: - raise Exception('Could not find someexe and somfxe commands') - # Check include order for 'someexe' - incs = [a for a in split_args(execmd) if a.startswith("-I")] - self.assertEqual(len(incs), 9) - # Need to run the build so the private dir is created. - self.build() - pdirs = glob(os.path.join(self.builddir, 'sub4/someexe*.p')) - self.assertEqual(len(pdirs), 1) - privdir = pdirs[0][len(self.builddir)+1:] - self.assertPathEqual(incs[0], "-I" + privdir) - # target build subdir - self.assertPathEqual(incs[1], "-Isub4") - # target source subdir - self.assertPathBasenameEqual(incs[2], 'sub4') - # include paths added via per-target c_args: ['-I'...] - self.assertPathBasenameEqual(incs[3], 'sub3') - # target include_directories: build dir - self.assertPathEqual(incs[4], "-Isub2") - # target include_directories: source dir - self.assertPathBasenameEqual(incs[5], 'sub2') - # target internal dependency include_directories: build dir - self.assertPathEqual(incs[6], "-Isub1") - # target internal dependency include_directories: source dir - self.assertPathBasenameEqual(incs[7], 'sub1') - # custom target include dir - self.assertPathEqual(incs[8], '-Ictsub') - # Check include order for 'somefxe' - incs = [a for a in split_args(fxecmd) if a.startswith('-I')] - self.assertEqual(len(incs), 9) - # target private dir - pdirs = glob(os.path.join(self.builddir, 'somefxe*.p')) - self.assertEqual(len(pdirs), 1) - privdir = pdirs[0][len(self.builddir)+1:] - self.assertPathEqual(incs[0], '-I' + privdir) - # target build dir - self.assertPathEqual(incs[1], '-I.') - # target source dir - self.assertPathBasenameEqual(incs[2], os.path.basename(testdir)) - # target internal dependency correct include_directories: build dir - self.assertPathEqual(incs[3], "-Isub4") - # target internal dependency correct include_directories: source dir - self.assertPathBasenameEqual(incs[4], 'sub4') - # target internal dependency dep include_directories: build dir - self.assertPathEqual(incs[5], "-Isub1") - # target internal dependency dep include_directories: source dir - self.assertPathBasenameEqual(incs[6], 'sub1') - # target internal dependency wrong include_directories: build dir - self.assertPathEqual(incs[7], "-Isub2") - # target internal dependency wrong include_directories: source dir - self.assertPathBasenameEqual(incs[8], 'sub2') - - def test_compiler_detection(self): - ''' - Test that automatic compiler detection and setting from the environment - both work just fine. This is needed because while running project tests - and other unit tests, we always read CC/CXX/etc from the environment. - ''' - gnu = mesonbuild.compilers.GnuCompiler - clang = mesonbuild.compilers.ClangCompiler - intel = mesonbuild.compilers.IntelGnuLikeCompiler - msvc = (mesonbuild.compilers.VisualStudioCCompiler, mesonbuild.compilers.VisualStudioCPPCompiler) - clangcl = (mesonbuild.compilers.ClangClCCompiler, mesonbuild.compilers.ClangClCPPCompiler) - ar = mesonbuild.linkers.ArLinker - lib = mesonbuild.linkers.VisualStudioLinker - langs = [('c', 'CC'), ('cpp', 'CXX')] - if not is_windows() and platform.machine().lower() != 'e2k': - langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')] - testdir = os.path.join(self.unit_test_dir, '5 compiler detection') - env = get_fake_env(testdir, self.builddir, self.prefix) - for lang, evar in langs: - # Detect with evar and do sanity checks on that - if evar in os.environ: - ecc = compiler_from_language(env, lang, MachineChoice.HOST) - self.assertTrue(ecc.version) - elinker = detect_static_linker(env, ecc) - # Pop it so we don't use it for the next detection - evalue = os.environ.pop(evar) - # Very rough/strict heuristics. Would never work for actual - # compiler detection, but should be ok for the tests. - ebase = os.path.basename(evalue) - if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')): - self.assertIsInstance(ecc, gnu) - self.assertIsInstance(elinker, ar) - elif 'clang-cl' in ebase: - self.assertIsInstance(ecc, clangcl) - self.assertIsInstance(elinker, lib) - elif 'clang' in ebase: - self.assertIsInstance(ecc, clang) - self.assertIsInstance(elinker, ar) - elif ebase.startswith('ic'): - self.assertIsInstance(ecc, intel) - self.assertIsInstance(elinker, ar) - elif ebase.startswith('cl'): - self.assertIsInstance(ecc, msvc) - self.assertIsInstance(elinker, lib) - else: - raise AssertionError(f'Unknown compiler {evalue!r}') - # Check that we actually used the evalue correctly as the compiler - self.assertEqual(ecc.get_exelist(), split_args(evalue)) - # Do auto-detection of compiler based on platform, PATH, etc. - cc = compiler_from_language(env, lang, MachineChoice.HOST) - self.assertTrue(cc.version) - linker = detect_static_linker(env, cc) - # Check compiler type - if isinstance(cc, gnu): - self.assertIsInstance(linker, ar) - if is_osx(): - self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) - elif is_sunos(): - self.assertIsInstance(cc.linker, (mesonbuild.linkers.SolarisDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)) - else: - self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin) - if isinstance(cc, clangcl): - self.assertIsInstance(linker, lib) - self.assertIsInstance(cc.linker, mesonbuild.linkers.ClangClDynamicLinker) - if isinstance(cc, clang): - self.assertIsInstance(linker, ar) - if is_osx(): - self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) - elif is_windows(): - # This is clang, not clang-cl. This can be either an - # ld-like linker of link.exe-like linker (usually the - # former for msys2, the latter otherwise) - self.assertIsInstance(cc.linker, (mesonbuild.linkers.MSVCDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)) - else: - self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin) - if isinstance(cc, intel): - self.assertIsInstance(linker, ar) - if is_osx(): - self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) - elif is_windows(): - self.assertIsInstance(cc.linker, mesonbuild.linkers.XilinkDynamicLinker) - else: - self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuDynamicLinker) - if isinstance(cc, msvc): - self.assertTrue(is_windows()) - self.assertIsInstance(linker, lib) - self.assertEqual(cc.id, 'msvc') - self.assertTrue(hasattr(cc, 'is_64')) - self.assertIsInstance(cc.linker, mesonbuild.linkers.MSVCDynamicLinker) - # If we're on Windows CI, we know what the compiler will be - if 'arch' in os.environ: - if os.environ['arch'] == 'x64': - self.assertTrue(cc.is_64) - else: - self.assertFalse(cc.is_64) - # Set evar ourselves to a wrapper script that just calls the same - # exelist + some argument. This is meant to test that setting - # something like `ccache gcc -pipe` or `distcc ccache gcc` works. - wrapper = os.path.join(testdir, 'compiler wrapper.py') - wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG'] - os.environ[evar] = ' '.join(quote_arg(w) for w in wrappercc) - - # Check static linker too - wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args() - os.environ['AR'] = ' '.join(quote_arg(w) for w in wrapperlinker) - - # Need a new env to re-run environment loading - env = get_fake_env(testdir, self.builddir, self.prefix) - - wcc = compiler_from_language(env, lang, MachineChoice.HOST) - wlinker = detect_static_linker(env, wcc) - # Pop it so we don't use it for the next detection - evalue = os.environ.pop('AR') - # Must be the same type since it's a wrapper around the same exelist - self.assertIs(type(cc), type(wcc)) - self.assertIs(type(linker), type(wlinker)) - # Ensure that the exelist is correct - self.assertEqual(wcc.get_exelist(), wrappercc) - self.assertEqual(wlinker.get_exelist(), wrapperlinker) - # Ensure that the version detection worked correctly - self.assertEqual(cc.version, wcc.version) - if hasattr(cc, 'is_64'): - self.assertEqual(cc.is_64, wcc.is_64) - - def test_always_prefer_c_compiler_for_asm(self): - testdir = os.path.join(self.common_test_dir, '133 c cpp and asm') - # Skip if building with MSVC - env = get_fake_env(testdir, self.builddir, self.prefix) - if detect_c_compiler(env, MachineChoice.HOST).get_id() == 'msvc': - raise unittest.SkipTest('MSVC can\'t compile assembly') - self.init(testdir) - commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}} - for cmd in self.get_compdb(): - # Get compiler - split = split_args(cmd['command']) - if split[0] == 'ccache': - compiler = split[1] - else: - compiler = split[0] - # Classify commands - if 'Ic-asm' in cmd['command']: - if cmd['file'].endswith('.S'): - commands['c-asm']['asm'] = compiler - elif cmd['file'].endswith('.c'): - commands['c-asm']['c'] = compiler - else: - raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) - elif 'Icpp-asm' in cmd['command']: - if cmd['file'].endswith('.S'): - commands['cpp-asm']['asm'] = compiler - elif cmd['file'].endswith('.cpp'): - commands['cpp-asm']['cpp'] = compiler - else: - raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) - elif 'Ic-cpp-asm' in cmd['command']: - if cmd['file'].endswith('.S'): - commands['c-cpp-asm']['asm'] = compiler - elif cmd['file'].endswith('.c'): - commands['c-cpp-asm']['c'] = compiler - elif cmd['file'].endswith('.cpp'): - commands['c-cpp-asm']['cpp'] = compiler - else: - raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command'])) - elif 'Icpp-c-asm' in cmd['command']: - if cmd['file'].endswith('.S'): - commands['cpp-c-asm']['asm'] = compiler - elif cmd['file'].endswith('.c'): - commands['cpp-c-asm']['c'] = compiler - elif cmd['file'].endswith('.cpp'): - commands['cpp-c-asm']['cpp'] = compiler - else: - raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command'])) - else: - raise AssertionError('Unknown command {!r} found'.format(cmd['command'])) - # Check that .S files are always built with the C compiler - self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c']) - self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm']) - self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c']) - self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c']) - self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c']) - self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp']) - self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp']) - self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp']) - # Check that the c-asm target is always linked with the C linker - build_ninja = os.path.join(self.builddir, 'build.ninja') - with open(build_ninja, encoding='utf-8') as f: - contents = f.read() - m = re.search('build c-asm.*: c_LINKER', contents) - self.assertIsNotNone(m, msg=contents) - - def test_preprocessor_checks_CPPFLAGS(self): - ''' - Test that preprocessor compiler checks read CPPFLAGS and also CFLAGS but - not LDFLAGS. - ''' - testdir = os.path.join(self.common_test_dir, '132 get define') - define = 'MESON_TEST_DEFINE_VALUE' - # NOTE: this list can't have \n, ' or " - # \n is never substituted by the GNU pre-processor via a -D define - # ' and " confuse split_args() even when they are escaped - # % and # confuse the MSVC preprocessor - # !, ^, *, and < confuse lcc preprocessor - value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`' - for env_var in ['CPPFLAGS', 'CFLAGS']: - env = {} - env[env_var] = f'-D{define}="{value}"' - env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read' - self.init(testdir, extra_args=[f'-D{define}={value}'], override_envvars=env) - - def test_custom_target_exe_data_deterministic(self): - testdir = os.path.join(self.common_test_dir, '109 custom target capture') - self.init(testdir) - meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) - self.wipe() - self.init(testdir) - meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) - self.assertListEqual(meson_exe_dat1, meson_exe_dat2) - - def test_noop_changes_cause_no_rebuilds(self): - ''' - Test that no-op changes to the build files such as mtime do not cause - a rebuild of anything. - ''' - testdir = os.path.join(self.common_test_dir, '6 linkshared') - self.init(testdir) - self.build() - # Immediately rebuilding should not do anything - self.assertBuildIsNoop() - # Changing mtime of meson.build should not rebuild anything - self.utime(os.path.join(testdir, 'meson.build')) - self.assertReconfiguredBuildIsNoop() - # Changing mtime of libefile.c should rebuild the library, but not relink the executable - self.utime(os.path.join(testdir, 'libfile.c')) - self.assertBuildRelinkedOnlyTarget('mylib') - - def test_source_changes_cause_rebuild(self): - ''' - Test that changes to sources and headers cause rebuilds, but not - changes to unused files (as determined by the dependency file) in the - input files list. - ''' - testdir = os.path.join(self.common_test_dir, '19 header in file list') - self.init(testdir) - self.build() - # Immediately rebuilding should not do anything - self.assertBuildIsNoop() - # Changing mtime of header.h should rebuild everything - self.utime(os.path.join(testdir, 'header.h')) - self.assertBuildRelinkedOnlyTarget('prog') - - def test_custom_target_changes_cause_rebuild(self): - ''' - Test that in a custom target, changes to the input files, the - ExternalProgram, and any File objects on the command-line cause - a rebuild. - ''' - testdir = os.path.join(self.common_test_dir, '57 custom header generator') - self.init(testdir) - self.build() - # Immediately rebuilding should not do anything - self.assertBuildIsNoop() - # Changing mtime of these should rebuild everything - for f in ('input.def', 'makeheader.py', 'somefile.txt'): - self.utime(os.path.join(testdir, f)) - self.assertBuildRelinkedOnlyTarget('prog') - - def test_source_generator_program_cause_rebuild(self): - ''' - Test that changes to generator programs in the source tree cause - a rebuild. - ''' - testdir = os.path.join(self.common_test_dir, '90 gen extra') - self.init(testdir) - self.build() - # Immediately rebuilding should not do anything - self.assertBuildIsNoop() - # Changing mtime of generator should rebuild the executable - self.utime(os.path.join(testdir, 'srcgen.py')) - self.assertRebuiltTarget('basic') - - def test_static_library_lto(self): - ''' - Test that static libraries can be built with LTO and linked to - executables. On Linux, this requires the use of gcc-ar. - https://github.com/mesonbuild/meson/issues/1646 - ''' - testdir = os.path.join(self.common_test_dir, '5 linkstatic') - - env = get_fake_env(testdir, self.builddir, self.prefix) - if detect_c_compiler(env, MachineChoice.HOST).get_id() == 'clang' and is_windows(): - raise unittest.SkipTest('LTO not (yet) supported by windows clang') - - self.init(testdir, extra_args='-Db_lto=true') - self.build() - self.run_tests() - - @skip_if_not_base_option('b_lto_threads') - def test_lto_threads(self): - testdir = os.path.join(self.common_test_dir, '6 linkshared') - - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - extra_args: T.List[str] = [] - if cc.get_id() == 'clang': - if is_windows(): - raise unittest.SkipTest('LTO not (yet) supported by windows clang') - - self.init(testdir, extra_args=['-Db_lto=true', '-Db_lto_threads=8'] + extra_args) - self.build() - self.run_tests() - - expected = set(cc.get_lto_compile_args(threads=8)) - targets = self.introspect('--targets') - # This assumes all of the targets support lto - for t in targets: - for s in t['target_sources']: - for e in expected: - self.assertIn(e, s['parameters']) - - @skip_if_not_base_option('b_lto_mode') - @skip_if_not_base_option('b_lto_threads') - def test_lto_mode(self): - testdir = os.path.join(self.common_test_dir, '6 linkshared') - - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_id() != 'clang': - raise unittest.SkipTest('Only clang currently supports thinLTO') - if cc.linker.id not in {'ld.lld', 'ld.gold', 'ld64', 'lld-link'}: - raise unittest.SkipTest('thinLTO requires ld.lld, ld.gold, ld64, or lld-link') - elif is_windows(): - raise unittest.SkipTest('LTO not (yet) supported by windows clang') - - self.init(testdir, extra_args=['-Db_lto=true', '-Db_lto_mode=thin', '-Db_lto_threads=8', '-Dc_args=-Werror=unused-command-line-argument']) - self.build() - self.run_tests() - - expected = set(cc.get_lto_compile_args(threads=8, mode='thin')) - targets = self.introspect('--targets') - # This assumes all of the targets support lto - for t in targets: - for s in t['target_sources']: - self.assertTrue(expected.issubset(set(s['parameters'])), f'Incorrect values for {t["name"]}') - - def test_dist_git(self): - if not shutil.which('git'): - raise unittest.SkipTest('Git not found') - if self.backend is not Backend.ninja: - raise unittest.SkipTest('Dist is only supported with Ninja') - - try: - self.dist_impl(_git_init, _git_add_all) - except PermissionError: - # When run under Windows CI, something (virus scanner?) - # holds on to the git files so cleaning up the dir - # fails sometimes. - pass - - def has_working_hg(self): - if not shutil.which('hg'): - return False - try: - # This check should not be necessary, but - # CI under macOS passes the above test even - # though Mercurial is not installed. - if subprocess.call(['hg', '--version'], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) != 0: - return False - return True - except FileNotFoundError: - return False - - def test_dist_hg(self): - if not self.has_working_hg(): - raise unittest.SkipTest('Mercurial not found or broken.') - if self.backend is not Backend.ninja: - raise unittest.SkipTest('Dist is only supported with Ninja') - - def hg_init(project_dir): - subprocess.check_call(['hg', 'init'], cwd=project_dir) - with open(os.path.join(project_dir, '.hg', 'hgrc'), 'w', encoding='utf-8') as f: - print('[ui]', file=f) - print('username=Author Person ', file=f) - subprocess.check_call(['hg', 'add', 'meson.build', 'distexe.c'], cwd=project_dir) - subprocess.check_call(['hg', 'commit', '-m', 'I am a project'], cwd=project_dir) - - try: - self.dist_impl(hg_init, include_subprojects=False) - except PermissionError: - # When run under Windows CI, something (virus scanner?) - # holds on to the hg files so cleaning up the dir - # fails sometimes. - pass - - def test_dist_git_script(self): - if not shutil.which('git'): - raise unittest.SkipTest('Git not found') - if self.backend is not Backend.ninja: - raise unittest.SkipTest('Dist is only supported with Ninja') - - try: - with tempfile.TemporaryDirectory() as tmpdir: - project_dir = os.path.join(tmpdir, 'a') - shutil.copytree(os.path.join(self.unit_test_dir, '35 dist script'), - project_dir) - _git_init(project_dir) - self.init(project_dir) - self.build('dist') - - self.new_builddir() - self.init(project_dir, extra_args=['-Dsub:broken_dist_script=false']) - self._run(self.meson_command + ['dist', '--include-subprojects'], workdir=self.builddir) - except PermissionError: - # When run under Windows CI, something (virus scanner?) - # holds on to the git files so cleaning up the dir - # fails sometimes. - pass - - def create_dummy_subproject(self, project_dir, name): - path = os.path.join(project_dir, 'subprojects', name) - os.makedirs(path) - with open(os.path.join(path, 'meson.build'), 'w', encoding='utf-8') as ofile: - ofile.write(f"project('{name}', version: '1.0')") - return path - - def dist_impl(self, vcs_init, vcs_add_all=None, include_subprojects=True): - # Create this on the fly because having rogue .git directories inside - # the source tree leads to all kinds of trouble. - with tempfile.TemporaryDirectory() as project_dir: - with open(os.path.join(project_dir, 'meson.build'), 'w', encoding='utf-8') as ofile: - ofile.write(textwrap.dedent('''\ - project('disttest', 'c', version : '1.4.3') - e = executable('distexe', 'distexe.c') - test('dist test', e) - subproject('vcssub', required : false) - subproject('tarballsub', required : false) - subproject('samerepo', required : false) - ''')) - with open(os.path.join(project_dir, 'distexe.c'), 'w', encoding='utf-8') as ofile: - ofile.write(textwrap.dedent('''\ - #include - - int main(int argc, char **argv) { - printf("I am a distribution test.\\n"); - return 0; - } - ''')) - xz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz') - xz_checksumfile = xz_distfile + '.sha256sum' - gz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.gz') - gz_checksumfile = gz_distfile + '.sha256sum' - zip_distfile = os.path.join(self.distdir, 'disttest-1.4.3.zip') - zip_checksumfile = zip_distfile + '.sha256sum' - vcs_init(project_dir) - if include_subprojects: - vcs_init(self.create_dummy_subproject(project_dir, 'vcssub')) - self.create_dummy_subproject(project_dir, 'tarballsub') - self.create_dummy_subproject(project_dir, 'unusedsub') - if vcs_add_all: - vcs_add_all(self.create_dummy_subproject(project_dir, 'samerepo')) - self.init(project_dir) - self.build('dist') - self.assertPathExists(xz_distfile) - self.assertPathExists(xz_checksumfile) - self.assertPathDoesNotExist(gz_distfile) - self.assertPathDoesNotExist(gz_checksumfile) - self.assertPathDoesNotExist(zip_distfile) - self.assertPathDoesNotExist(zip_checksumfile) - self._run(self.meson_command + ['dist', '--formats', 'gztar'], - workdir=self.builddir) - self.assertPathExists(gz_distfile) - self.assertPathExists(gz_checksumfile) - self._run(self.meson_command + ['dist', '--formats', 'zip'], - workdir=self.builddir) - self.assertPathExists(zip_distfile) - self.assertPathExists(zip_checksumfile) - os.remove(xz_distfile) - os.remove(xz_checksumfile) - os.remove(gz_distfile) - os.remove(gz_checksumfile) - os.remove(zip_distfile) - os.remove(zip_checksumfile) - self._run(self.meson_command + ['dist', '--formats', 'xztar,gztar,zip'], - workdir=self.builddir) - self.assertPathExists(xz_distfile) - self.assertPathExists(xz_checksumfile) - self.assertPathExists(gz_distfile) - self.assertPathExists(gz_checksumfile) - self.assertPathExists(zip_distfile) - self.assertPathExists(zip_checksumfile) - - if include_subprojects: - # Verify that without --include-subprojects we have files from - # the main project and also files from subprojects part of the - # main vcs repository. - z = zipfile.ZipFile(zip_distfile) - expected = ['disttest-1.4.3/', - 'disttest-1.4.3/meson.build', - 'disttest-1.4.3/distexe.c'] - if vcs_add_all: - expected += ['disttest-1.4.3/subprojects/', - 'disttest-1.4.3/subprojects/samerepo/', - 'disttest-1.4.3/subprojects/samerepo/meson.build'] - self.assertEqual(sorted(expected), - sorted(z.namelist())) - # Verify that with --include-subprojects we now also have files - # from tarball and separate vcs subprojects. But not files from - # unused subprojects. - self._run(self.meson_command + ['dist', '--formats', 'zip', '--include-subprojects'], - workdir=self.builddir) - z = zipfile.ZipFile(zip_distfile) - expected += ['disttest-1.4.3/subprojects/tarballsub/', - 'disttest-1.4.3/subprojects/tarballsub/meson.build', - 'disttest-1.4.3/subprojects/vcssub/', - 'disttest-1.4.3/subprojects/vcssub/meson.build'] - self.assertEqual(sorted(expected), - sorted(z.namelist())) - if vcs_add_all: - # Verify we can distribute separately subprojects in the same vcs - # repository as the main project. - subproject_dir = os.path.join(project_dir, 'subprojects', 'samerepo') - self.new_builddir() - self.init(subproject_dir) - self.build('dist') - xz_distfile = os.path.join(self.distdir, 'samerepo-1.0.tar.xz') - xz_checksumfile = xz_distfile + '.sha256sum' - self.assertPathExists(xz_distfile) - self.assertPathExists(xz_checksumfile) - tar = tarfile.open(xz_distfile, "r:xz") # [ignore encoding] - self.assertEqual(sorted(['samerepo-1.0', - 'samerepo-1.0/meson.build']), - sorted([i.name for i in tar])) - - def test_rpath_uses_ORIGIN(self): - ''' - Test that built targets use $ORIGIN in rpath, which ensures that they - are relocatable and ensures that builds are reproducible since the - build directory won't get embedded into the built binaries. - ''' - if is_windows() or is_cygwin(): - raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH') - testdir = os.path.join(self.common_test_dir, '39 library chain') - self.init(testdir) - self.build() - for each in ('prog', 'subdir/liblib1.so', ): - rpath = get_rpath(os.path.join(self.builddir, each)) - self.assertTrue(rpath, f'Rpath could not be determined for {each}.') - if is_dragonflybsd(): - # DragonflyBSD will prepend /usr/lib/gccVERSION to the rpath, - # so ignore that. - self.assertTrue(rpath.startswith('/usr/lib/gcc')) - rpaths = rpath.split(':')[1:] - else: - rpaths = rpath.split(':') - for path in rpaths: - self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path)) - # These two don't link to anything else, so they do not need an rpath entry. - for each in ('subdir/subdir2/liblib2.so', 'subdir/subdir3/liblib3.so'): - rpath = get_rpath(os.path.join(self.builddir, each)) - if is_dragonflybsd(): - # The rpath should be equal to /usr/lib/gccVERSION - self.assertTrue(rpath.startswith('/usr/lib/gcc')) - self.assertEqual(len(rpath.split(':')), 1) - else: - self.assertTrue(rpath is None) - - def test_dash_d_dedup(self): - testdir = os.path.join(self.unit_test_dir, '9 d dedup') - self.init(testdir) - cmd = self.get_compdb()[0]['command'] - self.assertTrue('-D FOO -D BAR' in cmd or - '"-D" "FOO" "-D" "BAR"' in cmd or - '/D FOO /D BAR' in cmd or - '"/D" "FOO" "/D" "BAR"' in cmd) - - def test_all_forbidden_targets_tested(self): - ''' - Test that all forbidden targets are tested in the '150 reserved targets' - test. Needs to be a unit test because it accesses Meson internals. - ''' - testdir = os.path.join(self.common_test_dir, '150 reserved targets') - targets = mesonbuild.coredata.FORBIDDEN_TARGET_NAMES - # We don't actually define a target with this name - targets.pop('build.ninja') - # Remove this to avoid multiple entries with the same name - # but different case. - targets.pop('PHONY') - for i in targets: - self.assertPathExists(os.path.join(testdir, i)) - - def detect_prebuild_env(self): - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - stlinker = detect_static_linker(env, cc) - if mesonbuild.mesonlib.is_windows(): - object_suffix = 'obj' - shared_suffix = 'dll' - elif mesonbuild.mesonlib.is_cygwin(): - object_suffix = 'o' - shared_suffix = 'dll' - elif mesonbuild.mesonlib.is_osx(): - object_suffix = 'o' - shared_suffix = 'dylib' - else: - object_suffix = 'o' - shared_suffix = 'so' - return (cc, stlinker, object_suffix, shared_suffix) - - def pbcompile(self, compiler, source, objectfile, extra_args=None): - cmd = compiler.get_exelist() - extra_args = extra_args or [] - if compiler.get_argument_syntax() == 'msvc': - cmd += ['/nologo', '/Fo' + objectfile, '/c', source] + extra_args - else: - cmd += ['-c', source, '-o', objectfile] + extra_args - subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - - def test_prebuilt_object(self): - (compiler, _, object_suffix, _) = self.detect_prebuild_env() - tdir = os.path.join(self.unit_test_dir, '15 prebuilt object') - source = os.path.join(tdir, 'source.c') - objectfile = os.path.join(tdir, 'prebuilt.' + object_suffix) - self.pbcompile(compiler, source, objectfile) - try: - self.init(tdir) - self.build() - self.run_tests() - finally: - os.unlink(objectfile) - - def build_static_lib(self, compiler, linker, source, objectfile, outfile, extra_args=None): - if extra_args is None: - extra_args = [] - if compiler.get_argument_syntax() == 'msvc': - link_cmd = ['lib', '/NOLOGO', '/OUT:' + outfile, objectfile] - else: - link_cmd = ['ar', 'csr', outfile, objectfile] - link_cmd = linker.get_exelist() - link_cmd += linker.get_always_args() - link_cmd += linker.get_std_link_args() - link_cmd += linker.get_output_args(outfile) - link_cmd += [objectfile] - self.pbcompile(compiler, source, objectfile, extra_args=extra_args) - try: - subprocess.check_call(link_cmd) - finally: - os.unlink(objectfile) - - def test_prebuilt_static_lib(self): - (cc, stlinker, object_suffix, _) = self.detect_prebuild_env() - tdir = os.path.join(self.unit_test_dir, '16 prebuilt static') - source = os.path.join(tdir, 'libdir/best.c') - objectfile = os.path.join(tdir, 'libdir/best.' + object_suffix) - stlibfile = os.path.join(tdir, 'libdir/libbest.a') - self.build_static_lib(cc, stlinker, source, objectfile, stlibfile) - # Run the test - try: - self.init(tdir) - self.build() - self.run_tests() - finally: - os.unlink(stlibfile) - - def build_shared_lib(self, compiler, source, objectfile, outfile, impfile, extra_args=None): - if extra_args is None: - extra_args = [] - if compiler.get_argument_syntax() == 'msvc': - link_cmd = compiler.get_linker_exelist() + [ - '/NOLOGO', '/DLL', '/DEBUG', '/IMPLIB:' + impfile, - '/OUT:' + outfile, objectfile] - else: - if not (compiler.info.is_windows() or compiler.info.is_cygwin() or compiler.info.is_darwin()): - extra_args += ['-fPIC'] - link_cmd = compiler.get_exelist() + ['-shared', '-o', outfile, objectfile] - if not mesonbuild.mesonlib.is_osx(): - link_cmd += ['-Wl,-soname=' + os.path.basename(outfile)] - self.pbcompile(compiler, source, objectfile, extra_args=extra_args) - try: - subprocess.check_call(link_cmd) - finally: - os.unlink(objectfile) - - def test_prebuilt_shared_lib(self): - (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env() - tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared') - source = os.path.join(tdir, 'alexandria.c') - objectfile = os.path.join(tdir, 'alexandria.' + object_suffix) - impfile = os.path.join(tdir, 'alexandria.lib') - if cc.get_argument_syntax() == 'msvc': - shlibfile = os.path.join(tdir, 'alexandria.' + shared_suffix) - elif is_cygwin(): - shlibfile = os.path.join(tdir, 'cygalexandria.' + shared_suffix) - else: - shlibfile = os.path.join(tdir, 'libalexandria.' + shared_suffix) - self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) - # Run the test - try: - self.init(tdir) - self.build() - self.run_tests() - finally: - os.unlink(shlibfile) - if mesonbuild.mesonlib.is_windows(): - # Clean up all the garbage MSVC writes in the - # source tree. - for fname in glob(os.path.join(tdir, 'alexandria.*')): - if os.path.splitext(fname)[1] not in ['.c', '.h']: - os.unlink(fname) - - @skipIfNoPkgconfig - def test_pkgconfig_static(self): - ''' - Test that the we prefer static libraries when `static: true` is - passed to dependency() with pkg-config. Can't be an ordinary test - because we need to build libs and try to find them from meson.build - - Also test that it's not a hard error to have unsatisfiable library deps - since system libraries -lm will never be found statically. - https://github.com/mesonbuild/meson/issues/2785 - ''' - (cc, stlinker, objext, shext) = self.detect_prebuild_env() - testdir = os.path.join(self.unit_test_dir, '18 pkgconfig static') - source = os.path.join(testdir, 'foo.c') - objectfile = os.path.join(testdir, 'foo.' + objext) - stlibfile = os.path.join(testdir, 'libfoo.a') - impfile = os.path.join(testdir, 'foo.lib') - if cc.get_argument_syntax() == 'msvc': - shlibfile = os.path.join(testdir, 'foo.' + shext) - elif is_cygwin(): - shlibfile = os.path.join(testdir, 'cygfoo.' + shext) - else: - shlibfile = os.path.join(testdir, 'libfoo.' + shext) - # Build libs - self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC']) - self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) - # Run test - try: - self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': self.builddir}) - self.build() - self.run_tests() - finally: - os.unlink(stlibfile) - os.unlink(shlibfile) - if mesonbuild.mesonlib.is_windows(): - # Clean up all the garbage MSVC writes in the - # source tree. - for fname in glob(os.path.join(testdir, 'foo.*')): - if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']: - os.unlink(fname) - - @skipIfNoPkgconfig - @mock.patch.dict(os.environ) - def test_pkgconfig_gen_escaping(self): - testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') - prefix = '/usr/with spaces' - libdir = 'lib' - self.init(testdir, extra_args=['--prefix=' + prefix, - '--libdir=' + libdir]) - # Find foo dependency - os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir - env = get_fake_env(testdir, self.builddir, self.prefix) - kwargs = {'required': True, 'silent': True} - foo_dep = PkgConfigDependency('libfoo', env, kwargs) - # Ensure link_args are properly quoted - libdir = PurePath(prefix) / PurePath(libdir) - link_args = ['-L' + libdir.as_posix(), '-lfoo'] - self.assertEqual(foo_dep.get_link_args(), link_args) - # Ensure include args are properly quoted - incdir = PurePath(prefix) / PurePath('include') - cargs = ['-I' + incdir.as_posix(), '-DLIBFOO'] - # pkg-config and pkgconf does not respect the same order - self.assertEqual(sorted(foo_dep.get_compile_args()), sorted(cargs)) - - def test_array_option_change(self): - def get_opt(): - opts = self.introspect('--buildoptions') - for x in opts: - if x.get('name') == 'list': - return x - raise Exception(opts) - - expected = { - 'name': 'list', - 'description': 'list', - 'section': 'user', - 'type': 'array', - 'value': ['foo', 'bar'], - 'machine': 'any', - } - tdir = os.path.join(self.unit_test_dir, '19 array option') - self.init(tdir) - original = get_opt() - self.assertDictEqual(original, expected) - - expected['value'] = ['oink', 'boink'] - self.setconf('-Dlist=oink,boink') - changed = get_opt() - self.assertEqual(changed, expected) - - def test_array_option_bad_change(self): - def get_opt(): - opts = self.introspect('--buildoptions') - for x in opts: - if x.get('name') == 'list': - return x - raise Exception(opts) - - expected = { - 'name': 'list', - 'description': 'list', - 'section': 'user', - 'type': 'array', - 'value': ['foo', 'bar'], - 'machine': 'any', - } - tdir = os.path.join(self.unit_test_dir, '19 array option') - self.init(tdir) - original = get_opt() - self.assertDictEqual(original, expected) - with self.assertRaises(subprocess.CalledProcessError): - self.setconf('-Dlist=bad') - changed = get_opt() - self.assertDictEqual(changed, expected) - - def test_array_option_empty_equivalents(self): - """Array options treat -Dopt=[] and -Dopt= as equivalent.""" - def get_opt(): - opts = self.introspect('--buildoptions') - for x in opts: - if x.get('name') == 'list': - return x - raise Exception(opts) - - expected = { - 'name': 'list', - 'description': 'list', - 'section': 'user', - 'type': 'array', - 'value': [], - 'machine': 'any', - } - tdir = os.path.join(self.unit_test_dir, '19 array option') - self.init(tdir, extra_args='-Dlist=') - original = get_opt() - self.assertDictEqual(original, expected) - - def opt_has(self, name, value): - res = self.introspect('--buildoptions') - found = False - for i in res: - if i['name'] == name: - self.assertEqual(i['value'], value) - found = True - break - self.assertTrue(found, "Array option not found in introspect data.") - - def test_free_stringarray_setting(self): - testdir = os.path.join(self.common_test_dir, '40 options') - self.init(testdir) - self.opt_has('free_array_opt', []) - self.setconf('-Dfree_array_opt=foo,bar', will_build=False) - self.opt_has('free_array_opt', ['foo', 'bar']) - self.setconf("-Dfree_array_opt=['a,b', 'c,d']", will_build=False) - self.opt_has('free_array_opt', ['a,b', 'c,d']) - - # When running under Travis Mac CI, the file updates seem to happen - # too fast so the timestamps do not get properly updated. - # Call this method before file operations in appropriate places - # to make things work. - def mac_ci_delay(self): - if is_osx() and is_ci(): - import time - time.sleep(1) - - def test_options_with_choices_changing(self) -> None: - """Detect when options like arrays or combos have their choices change.""" - testdir = Path(os.path.join(self.unit_test_dir, '84 change option choices')) - options1 = str(testdir / 'meson_options.1.txt') - options2 = str(testdir / 'meson_options.2.txt') - - # Test that old options are changed to the new defaults if they are not valid - real_options = str(testdir / 'meson_options.txt') - self.addCleanup(os.unlink, real_options) - - shutil.copy(options1, real_options) - self.init(str(testdir)) - self.mac_ci_delay() - shutil.copy(options2, real_options) - - self.build() - opts = self.introspect('--buildoptions') - for item in opts: - if item['name'] == 'combo': - self.assertEqual(item['value'], 'b') - self.assertEqual(item['choices'], ['b', 'c', 'd']) - elif item['name'] == 'arr': - self.assertEqual(item['value'], ['b']) - self.assertEqual(item['choices'], ['b', 'c', 'd']) - - self.wipe() - self.mac_ci_delay() - - # When the old options are valid they should remain - shutil.copy(options1, real_options) - self.init(str(testdir), extra_args=['-Dcombo=c', '-Darray=b,c']) - self.mac_ci_delay() - shutil.copy(options2, real_options) - self.build() - opts = self.introspect('--buildoptions') - for item in opts: - if item['name'] == 'combo': - self.assertEqual(item['value'], 'c') - self.assertEqual(item['choices'], ['b', 'c', 'd']) - elif item['name'] == 'arr': - self.assertEqual(item['value'], ['b', 'c']) - self.assertEqual(item['choices'], ['b', 'c', 'd']) - - def test_subproject_promotion(self): - testdir = os.path.join(self.unit_test_dir, '12 promote') - workdir = os.path.join(self.builddir, 'work') - shutil.copytree(testdir, workdir) - spdir = os.path.join(workdir, 'subprojects') - s3dir = os.path.join(spdir, 's3') - scommondir = os.path.join(spdir, 'scommon') - self.assertFalse(os.path.isdir(s3dir)) - subprocess.check_call(self.wrap_command + ['promote', 's3'], - cwd=workdir, - stdout=subprocess.DEVNULL) - self.assertTrue(os.path.isdir(s3dir)) - self.assertFalse(os.path.isdir(scommondir)) - self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'scommon'], - cwd=workdir, - stderr=subprocess.DEVNULL), 0) - self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'invalid/path/to/scommon'], - cwd=workdir, - stderr=subprocess.DEVNULL), 0) - self.assertFalse(os.path.isdir(scommondir)) - subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/scommon'], cwd=workdir) - self.assertTrue(os.path.isdir(scommondir)) - promoted_wrap = os.path.join(spdir, 'athing.wrap') - self.assertFalse(os.path.isfile(promoted_wrap)) - subprocess.check_call(self.wrap_command + ['promote', 'athing'], cwd=workdir) - self.assertTrue(os.path.isfile(promoted_wrap)) - self.init(workdir) - self.build() - - def test_subproject_promotion_wrap(self): - testdir = os.path.join(self.unit_test_dir, '44 promote wrap') - workdir = os.path.join(self.builddir, 'work') - shutil.copytree(testdir, workdir) - spdir = os.path.join(workdir, 'subprojects') - - ambiguous_wrap = os.path.join(spdir, 'ambiguous.wrap') - self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'ambiguous'], - cwd=workdir, - stderr=subprocess.DEVNULL), 0) - self.assertFalse(os.path.isfile(ambiguous_wrap)) - subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/ambiguous.wrap'], cwd=workdir) - self.assertTrue(os.path.isfile(ambiguous_wrap)) - - def test_warning_location(self): - tdir = os.path.join(self.unit_test_dir, '22 warning location') - out = self.init(tdir) - for expected in [ - r'meson.build:4: WARNING: Keyword argument "link_with" defined multiple times.', - r'sub' + os.path.sep + r'meson.build:3: WARNING: Keyword argument "link_with" defined multiple times.', - r'meson.build:6: WARNING: a warning of some sort', - r'sub' + os.path.sep + r'meson.build:4: WARNING: subdir warning', - r'meson.build:7: WARNING: Module unstable-simd has no backwards or forwards compatibility and might not exist in future releases.', - r"meson.build:11: WARNING: The variable(s) 'MISSING' in the input file 'conf.in' are not present in the given configuration data.", - r'meson.build:1: WARNING: Passed invalid keyword argument "invalid".', - ]: - self.assertRegex(out, re.escape(expected)) - - for wd in [ - self.src_root, - self.builddir, - os.getcwd(), - ]: - self.new_builddir() - out = self.init(tdir, workdir=wd) - expected = os.path.join(relpath(tdir, self.src_root), 'meson.build') - relwd = relpath(self.src_root, wd) - if relwd != '.': - expected = os.path.join(relwd, expected) - expected = '\n' + expected + ':' - self.assertIn(expected, out) - - def test_error_location_path(self): - '''Test locations in meson errors contain correct paths''' - # this list contains errors from all the different steps in the - # lexer/parser/interpreter we have tests for. - for (t, f) in [ - ('10 out of bounds', 'meson.build'), - ('18 wrong plusassign', 'meson.build'), - ('60 bad option argument', 'meson_options.txt'), - ('98 subdir parse error', os.path.join('subdir', 'meson.build')), - ('99 invalid option file', 'meson_options.txt'), - ]: - tdir = os.path.join(self.src_root, 'test cases', 'failing', t) - - for wd in [ - self.src_root, - self.builddir, - os.getcwd(), - ]: - try: - self.init(tdir, workdir=wd) - except subprocess.CalledProcessError as e: - expected = os.path.join('test cases', 'failing', t, f) - relwd = relpath(self.src_root, wd) - if relwd != '.': - expected = os.path.join(relwd, expected) - expected = '\n' + expected + ':' - self.assertIn(expected, e.output) - else: - self.fail('configure unexpectedly succeeded') - - def test_permitted_method_kwargs(self): - tdir = os.path.join(self.unit_test_dir, '25 non-permitted kwargs') - out = self.init(tdir) - for expected in [ - r'WARNING: Passed invalid keyword argument "prefixxx".', - r'WARNING: Passed invalid keyword argument "argsxx".', - r'WARNING: Passed invalid keyword argument "invalidxx".', - ]: - self.assertRegex(out, re.escape(expected)) - - def test_templates(self): - ninja = detect_ninja() - if ninja is None: - raise unittest.SkipTest('This test currently requires ninja. Fix this once "meson build" works.') - - langs = ['c'] - env = get_fake_env() - for l in ['cpp', 'cs', 'd', 'java', 'cuda', 'fortran', 'objc', 'objcpp', 'rust']: - try: - comp = mesonbuild.compilers.detect_compiler_for(env, l, MachineChoice.HOST) - with tempfile.TemporaryDirectory() as d: - comp.sanity_check(d, env) - langs.append(l) - except EnvironmentException: - pass - - # The D template fails under mac CI and we don't know why. - # Patches welcome - if is_osx(): - langs = [l for l in langs if l != 'd'] - - for lang in langs: - for target_type in ('executable', 'library'): - # test empty directory - with tempfile.TemporaryDirectory() as tmpdir: - self._run(self.meson_command + ['init', '--language', lang, '--type', target_type], - workdir=tmpdir) - self._run(self.setup_command + ['--backend=ninja', 'builddir'], - workdir=tmpdir) - self._run(ninja, - workdir=os.path.join(tmpdir, 'builddir')) - # test directory with existing code file - if lang in {'c', 'cpp', 'd'}: - with tempfile.TemporaryDirectory() as tmpdir: - with open(os.path.join(tmpdir, 'foo.' + lang), 'w', encoding='utf-8') as f: - f.write('int main(void) {}') - self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) - elif lang in {'java'}: - with tempfile.TemporaryDirectory() as tmpdir: - with open(os.path.join(tmpdir, 'Foo.' + lang), 'w', encoding='utf-8') as f: - f.write('public class Foo { public static void main() {} }') - self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) - - def test_compiler_run_command(self): - ''' - The test checks that the compiler object can be passed to - run_command(). - ''' - testdir = os.path.join(self.unit_test_dir, '24 compiler run_command') - self.init(testdir) - - def test_identical_target_name_in_subproject_flat_layout(self): - ''' - Test that identical targets in different subprojects do not collide - if layout is flat. - ''' - testdir = os.path.join(self.common_test_dir, '172 identical target name in subproject flat layout') - self.init(testdir, extra_args=['--layout=flat']) - self.build() - - def test_identical_target_name_in_subdir_flat_layout(self): - ''' - Test that identical targets in different subdirs do not collide - if layout is flat. - ''' - testdir = os.path.join(self.common_test_dir, '181 same target name flat layout') - self.init(testdir, extra_args=['--layout=flat']) - self.build() - - def test_flock(self): - exception_raised = False - with tempfile.TemporaryDirectory() as tdir: - os.mkdir(os.path.join(tdir, 'meson-private')) - with BuildDirLock(tdir): - try: - with BuildDirLock(tdir): - pass - except MesonException: - exception_raised = True - self.assertTrue(exception_raised, 'Double locking did not raise exception.') - - @unittest.skipIf(is_osx(), 'Test not applicable to OSX') - def test_check_module_linking(self): - """ - Test that link_with: a shared module issues a warning - https://github.com/mesonbuild/meson/issues/2865 - (That an error is raised on OSX is exercised by test failing/78) - """ - tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking') - out = self.init(tdir) - msg = ('WARNING: target links against shared modules. This is not ' - 'recommended as it is not supported on some platforms') - self.assertIn(msg, out) - - def test_ndebug_if_release_disabled(self): - testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release') - self.init(testdir, extra_args=['--buildtype=release', '-Db_ndebug=if-release']) - self.build() - exe = os.path.join(self.builddir, 'main') - self.assertEqual(b'NDEBUG=1', subprocess.check_output(exe).strip()) - - def test_ndebug_if_release_enabled(self): - testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release') - self.init(testdir, extra_args=['--buildtype=debugoptimized', '-Db_ndebug=if-release']) - self.build() - exe = os.path.join(self.builddir, 'main') - self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip()) - - def test_guessed_linker_dependencies(self): - ''' - Test that meson adds dependencies for libraries based on the final - linker command line. - ''' - testdirbase = os.path.join(self.unit_test_dir, '29 guessed linker dependencies') - testdirlib = os.path.join(testdirbase, 'lib') - - extra_args = None - libdir_flags = ['-L'] - env = get_fake_env(testdirlib, self.builddir, self.prefix) - if detect_c_compiler(env, MachineChoice.HOST).get_id() in {'msvc', 'clang-cl', 'intel-cl'}: - # msvc-like compiler, also test it with msvc-specific flags - libdir_flags += ['/LIBPATH:', '-LIBPATH:'] - else: - # static libraries are not linkable with -l with msvc because meson installs them - # as .a files which unix_args_to_native will not know as it expects libraries to use - # .lib as extension. For a DLL the import library is installed as .lib. Thus for msvc - # this tests needs to use shared libraries to test the path resolving logic in the - # dependency generation code path. - extra_args = ['--default-library', 'static'] - - initial_builddir = self.builddir - initial_installdir = self.installdir - - for libdir_flag in libdir_flags: - # build library - self.new_builddir() - self.init(testdirlib, extra_args=extra_args) - self.build() - self.install() - libbuilddir = self.builddir - installdir = self.installdir - libdir = os.path.join(self.installdir, self.prefix.lstrip('/').lstrip('\\'), 'lib') - - # build user of library - self.new_builddir() - # replace is needed because meson mangles platform paths passed via LDFLAGS - self.init(os.path.join(testdirbase, 'exe'), - override_envvars={"LDFLAGS": '{}{}'.format(libdir_flag, libdir.replace('\\', '/'))}) - self.build() - self.assertBuildIsNoop() - - # rebuild library - exebuilddir = self.builddir - self.installdir = installdir - self.builddir = libbuilddir - # Microsoft's compiler is quite smart about touching import libs on changes, - # so ensure that there is actually a change in symbols. - self.setconf('-Dmore_exports=true') - self.build() - self.install() - # no ensure_backend_detects_changes needed because self.setconf did that already - - # assert user of library will be rebuild - self.builddir = exebuilddir - self.assertRebuiltTarget('app') - - # restore dirs for the next test case - self.installdir = initial_builddir - self.builddir = initial_installdir - - def test_conflicting_d_dash_option(self): - testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') - with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as e: - self.init(testdir, extra_args=['-Dbindir=foo', '--bindir=bar']) - # Just to ensure that we caught the correct error - self.assertIn('as both', e.stderr) - - def _test_same_option_twice(self, arg, args): - testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') - self.init(testdir, extra_args=args) - opts = self.introspect('--buildoptions') - for item in opts: - if item['name'] == arg: - self.assertEqual(item['value'], 'bar') - return - raise Exception(f'Missing {arg} value?') - - def test_same_dash_option_twice(self): - self._test_same_option_twice('bindir', ['--bindir=foo', '--bindir=bar']) - - def test_same_d_option_twice(self): - self._test_same_option_twice('bindir', ['-Dbindir=foo', '-Dbindir=bar']) - - def test_same_project_d_option_twice(self): - self._test_same_option_twice('one', ['-Done=foo', '-Done=bar']) - - def _test_same_option_twice_configure(self, arg, args): - testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') - self.init(testdir) - self.setconf(args) - opts = self.introspect('--buildoptions') - for item in opts: - if item['name'] == arg: - self.assertEqual(item['value'], 'bar') - return - raise Exception(f'Missing {arg} value?') - - def test_same_dash_option_twice_configure(self): - self._test_same_option_twice_configure( - 'bindir', ['--bindir=foo', '--bindir=bar']) - - def test_same_d_option_twice_configure(self): - self._test_same_option_twice_configure( - 'bindir', ['-Dbindir=foo', '-Dbindir=bar']) - - def test_same_project_d_option_twice_configure(self): - self._test_same_option_twice_configure( - 'one', ['-Done=foo', '-Done=bar']) - - def test_command_line(self): - testdir = os.path.join(self.unit_test_dir, '34 command line') - - # Verify default values when passing no args that affect the - # configuration, and as a bonus, test that --profile-self works. - out = self.init(testdir, extra_args=['--profile-self', '--fatal-meson-warnings']) - self.assertNotIn('[default: true]', out) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('default_library')].value, 'static') - self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') - self.assertEqual(obj.options[OptionKey('set_sub_opt')].value, True) - self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'default3') - self.wipe() - - # warning_level is special, it's --warnlevel instead of --warning-level - # for historical reasons - self.init(testdir, extra_args=['--warnlevel=2', '--fatal-meson-warnings']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '2') - self.setconf('--warnlevel=3') - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '3') - self.wipe() - - # But when using -D syntax, it should be 'warning_level' - self.init(testdir, extra_args=['-Dwarning_level=2', '--fatal-meson-warnings']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '2') - self.setconf('-Dwarning_level=3') - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '3') - self.wipe() - - # Mixing --option and -Doption is forbidden - with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: - self.init(testdir, extra_args=['--warnlevel=1', '-Dwarning_level=3']) - if isinstance(cm.exception, subprocess.CalledProcessError): - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn('as both', cm.exception.output) - else: - self.assertIn('as both', str(cm.exception)) - self.init(testdir) - with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: - self.setconf(['--warnlevel=1', '-Dwarning_level=3']) - if isinstance(cm.exception, subprocess.CalledProcessError): - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn('as both', cm.exception.output) - else: - self.assertIn('as both', str(cm.exception)) - self.wipe() - - # --default-library should override default value from project() - self.init(testdir, extra_args=['--default-library=both', '--fatal-meson-warnings']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('default_library')].value, 'both') - self.setconf('--default-library=shared') - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared') - if self.backend is Backend.ninja: - # reconfigure target works only with ninja backend - self.build('reconfigure') - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('default_library')].value, 'shared') - self.wipe() - - # Should fail on unknown options - with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: - self.init(testdir, extra_args=['-Dbad=1', '-Dfoo=2', '-Dwrong_link_args=foo']) - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn(msg, cm.exception.output) - self.wipe() - - # Should fail on malformed option - msg = "Option 'foo' must have a value separated by equals sign." - with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: - self.init(testdir, extra_args=['-Dfoo']) - if isinstance(cm.exception, subprocess.CalledProcessError): - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn(msg, cm.exception.output) - else: - self.assertIn(msg, str(cm.exception)) - self.init(testdir) - with self.assertRaises((subprocess.CalledProcessError, RuntimeError)) as cm: - self.setconf('-Dfoo') - if isinstance(cm.exception, subprocess.CalledProcessError): - self.assertNotEqual(0, cm.exception.returncode) - self.assertIn(msg, cm.exception.output) - else: - self.assertIn(msg, str(cm.exception)) - self.wipe() - - # It is not an error to set wrong option for unknown subprojects or - # language because we don't have control on which one will be selected. - self.init(testdir, extra_args=['-Dc_wrong=1', '-Dwrong:bad=1', '-Db_wrong=1']) - self.wipe() - - # Test we can set subproject option - self.init(testdir, extra_args=['-Dsubp:subp_opt=foo', '--fatal-meson-warnings']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('subp_opt', 'subp')].value, 'foo') - self.wipe() - - # c_args value should be parsed with split_args - self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"', '--fatal-meson-warnings']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) - - self.setconf('-Dc_args="foo bar" one two') - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['foo bar', 'one', 'two']) - self.wipe() - - self.init(testdir, extra_args=['-Dset_percent_opt=myoption%', '--fatal-meson-warnings']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('set_percent_opt')].value, 'myoption%') - self.wipe() - - # Setting a 2nd time the same option should override the first value - try: - self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar', - '-Dbuildtype=plain', '-Dbuildtype=release', - '-Db_sanitize=address', '-Db_sanitize=thread', - '-Dc_args=-Dfoo', '-Dc_args=-Dbar', - '-Db_lundef=false', '--fatal-meson-warnings']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('bindir')].value, 'bar') - self.assertEqual(obj.options[OptionKey('buildtype')].value, 'release') - self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'thread') - self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dbar']) - self.setconf(['--bindir=bar', '--bindir=foo', - '-Dbuildtype=release', '-Dbuildtype=plain', - '-Db_sanitize=thread', '-Db_sanitize=address', - '-Dc_args=-Dbar', '-Dc_args=-Dfoo']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('bindir')].value, 'foo') - self.assertEqual(obj.options[OptionKey('buildtype')].value, 'plain') - self.assertEqual(obj.options[OptionKey('b_sanitize')].value, 'address') - self.assertEqual(obj.options[OptionKey('args', lang='c')].value, ['-Dfoo']) - self.wipe() - except KeyError: - # Ignore KeyError, it happens on CI for compilers that does not - # support b_sanitize. We have to test with a base option because - # they used to fail this test with Meson 0.46 an earlier versions. - pass - - def test_warning_level_0(self): - testdir = os.path.join(self.common_test_dir, '207 warning level 0') - - # Verify default values when passing no args - self.init(testdir) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') - self.wipe() - - # verify we can override w/ --warnlevel - self.init(testdir, extra_args=['--warnlevel=1']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') - self.setconf('--warnlevel=0') - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') - self.wipe() - - # verify we can override w/ -Dwarning_level - self.init(testdir, extra_args=['-Dwarning_level=1']) - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '1') - self.setconf('-Dwarning_level=0') - obj = mesonbuild.coredata.load(self.builddir) - self.assertEqual(obj.options[OptionKey('warning_level')].value, '0') - self.wipe() - - def test_feature_check_usage_subprojects(self): - testdir = os.path.join(self.unit_test_dir, '41 featurenew subprojects') - out = self.init(testdir) - # Parent project warns correctly - self.assertRegex(out, "WARNING: Project targeting '>=0.45'.*'0.47.0': dict") - # Subprojects warn correctly - self.assertRegex(out, r"\| WARNING: Project targeting '>=0.40'.*'0.44.0': disabler") - self.assertRegex(out, r"\| WARNING: Project targeting '!=0.40'.*'0.44.0': disabler") - # Subproject has a new-enough meson_version, no warning - self.assertNotRegex(out, "WARNING: Project targeting.*Python") - # Ensure a summary is printed in the subproject and the outer project - self.assertRegex(out, r"\| WARNING: Project specifies a minimum meson_version '>=0.40'") - self.assertRegex(out, r"\| \* 0.44.0: {'disabler'}") - self.assertRegex(out, "WARNING: Project specifies a minimum meson_version '>=0.45'") - self.assertRegex(out, " * 0.47.0: {'dict'}") - - def test_configure_file_warnings(self): - testdir = os.path.join(self.common_test_dir, "14 configure file") - out = self.init(testdir) - self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*") - self.assertRegex(out, "WARNING:.*'FOO_BAR'.*nosubst-nocopy2.txt.in.*not present.*") - self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*") - self.assertRegex(out, "WARNING:.*empty configuration_data.*test.py.in") - # Warnings for configuration files that are overwritten. - self.assertRegex(out, "WARNING:.*\"double_output.txt\".*overwrites") - self.assertRegex(out, "WARNING:.*\"subdir.double_output2.txt\".*overwrites") - self.assertNotRegex(out, "WARNING:.*no_write_conflict.txt.*overwrites") - self.assertNotRegex(out, "WARNING:.*@BASENAME@.*overwrites") - self.assertRegex(out, "WARNING:.*\"sameafterbasename\".*overwrites") - # No warnings about empty configuration data objects passed to files with substitutions - self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy1.txt.in") - self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy2.txt.in") - with open(os.path.join(self.builddir, 'nosubst-nocopy1.txt'), 'rb') as f: - self.assertEqual(f.read().strip(), b'/* #undef FOO_BAR */') - with open(os.path.join(self.builddir, 'nosubst-nocopy2.txt'), 'rb') as f: - self.assertEqual(f.read().strip(), b'') - self.assertRegex(out, r"DEPRECATION:.*\['array'\] is invalid.*dict") - - def test_dirs(self): - with tempfile.TemporaryDirectory() as containing: - with tempfile.TemporaryDirectory(dir=containing) as srcdir: - mfile = os.path.join(srcdir, 'meson.build') - of = open(mfile, 'w', encoding='utf-8') - of.write("project('foobar', 'c')\n") - of.close() - pc = subprocess.run(self.setup_command, - cwd=srcdir, - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL) - self.assertIn(b'Must specify at least one directory name', pc.stdout) - with tempfile.TemporaryDirectory(dir=srcdir) as builddir: - subprocess.run(self.setup_command, - check=True, - cwd=builddir, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - - def get_opts_as_dict(self): - result = {} - for i in self.introspect('--buildoptions'): - result[i['name']] = i['value'] - return result - - def test_buildtype_setting(self): - testdir = os.path.join(self.common_test_dir, '1 trivial') - self.init(testdir) - opts = self.get_opts_as_dict() - self.assertEqual(opts['buildtype'], 'debug') - self.assertEqual(opts['debug'], True) - self.setconf('-Ddebug=false') - opts = self.get_opts_as_dict() - self.assertEqual(opts['debug'], False) - self.assertEqual(opts['buildtype'], 'debug') - self.assertEqual(opts['optimization'], '0') - self.setconf('-Doptimization=g') - opts = self.get_opts_as_dict() - self.assertEqual(opts['debug'], False) - self.assertEqual(opts['buildtype'], 'debug') - self.assertEqual(opts['optimization'], 'g') - - @skipIfNoPkgconfig - @unittest.skipIf(is_windows(), 'Help needed with fixing this test on windows') - def test_native_dep_pkgconfig(self): - testdir = os.path.join(self.unit_test_dir, - '46 native dep pkgconfig var') - with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile: - crossfile.write(textwrap.dedent( - '''[binaries] - pkgconfig = '{}' - - [properties] - - [host_machine] - system = 'linux' - cpu_family = 'arm' - cpu = 'armv7' - endian = 'little' - '''.format(os.path.join(testdir, 'cross_pkgconfig.py')))) - crossfile.flush() - self.meson_cross_file = crossfile.name - - env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir, - 'native_pkgconfig')} - self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env) - self.wipe() - self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) - - @skipIfNoPkgconfig - @unittest.skipIf(is_windows(), 'Help needed with fixing this test on windows') - def test_pkg_config_libdir(self): - testdir = os.path.join(self.unit_test_dir, - '46 native dep pkgconfig var') - with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile: - crossfile.write(textwrap.dedent( - '''[binaries] - pkgconfig = 'pkg-config' - - [properties] - pkg_config_libdir = ['{}'] - - [host_machine] - system = 'linux' - cpu_family = 'arm' - cpu = 'armv7' - endian = 'little' - '''.format(os.path.join(testdir, 'cross_pkgconfig')))) - crossfile.flush() - self.meson_cross_file = crossfile.name - - env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir, - 'native_pkgconfig')} - self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env) - self.wipe() - self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) - - def __reconfigure(self, change_minor=False): - # Set an older version to force a reconfigure from scratch - filename = os.path.join(self.privatedir, 'coredata.dat') - with open(filename, 'rb') as f: - obj = pickle.load(f) - if change_minor: - v = mesonbuild.coredata.version.split('.') - obj.version = '.'.join(v[0:2] + [str(int(v[2]) + 1)]) - else: - obj.version = '0.47.0' - with open(filename, 'wb') as f: - pickle.dump(obj, f) - - def test_reconfigure(self): - testdir = os.path.join(self.unit_test_dir, '48 reconfigure') - self.init(testdir, extra_args=['-Dopt1=val1', '-Dsub1:werror=true']) - self.setconf('-Dopt2=val2') - - self.__reconfigure() - - out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3']) - self.assertRegex(out, 'Regenerating configuration from scratch') - self.assertRegex(out, 'opt1 val1') - self.assertRegex(out, 'opt2 val2') - self.assertRegex(out, 'opt3 val3') - self.assertRegex(out, 'opt4 default4') - self.assertRegex(out, 'sub1:werror True') - self.build() - self.run_tests() - - # Create a file in builddir and verify wipe command removes it - filename = os.path.join(self.builddir, 'something') - open(filename, 'w', encoding='utf-8').close() - self.assertTrue(os.path.exists(filename)) - out = self.init(testdir, extra_args=['--wipe', '-Dopt4=val4']) - self.assertFalse(os.path.exists(filename)) - self.assertRegex(out, 'opt1 val1') - self.assertRegex(out, 'opt2 val2') - self.assertRegex(out, 'opt3 val3') - self.assertRegex(out, 'opt4 val4') - self.assertRegex(out, 'sub1:werror True') - self.assertTrue(Path(self.builddir, '.gitignore').exists()) - self.build() - self.run_tests() - - def test_wipe_from_builddir(self): - testdir = os.path.join(self.common_test_dir, '157 custom target subdir depend files') - self.init(testdir) - self.__reconfigure() - - with Path(self.builddir): - self.init(testdir, extra_args=['--wipe']) - - def test_minor_version_does_not_reconfigure_wipe(self): - testdir = os.path.join(self.unit_test_dir, '48 reconfigure') - self.init(testdir, extra_args=['-Dopt1=val1']) - self.setconf('-Dopt2=val2') - - self.__reconfigure(change_minor=True) - - out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3']) - self.assertNotRegex(out, 'Regenerating configuration from scratch') - self.assertRegex(out, 'opt1 val1') - self.assertRegex(out, 'opt2 val2') - self.assertRegex(out, 'opt3 val3') - self.assertRegex(out, 'opt4 default4') - self.build() - self.run_tests() - - def test_target_construct_id_from_path(self): - # This id is stable but not guessable. - # The test is supposed to prevent unintentional - # changes of target ID generation. - target_id = Target.construct_id_from_path('some/obscure/subdir', - 'target-id', '@suffix') - self.assertEqual('5e002d3@@target-id@suffix', target_id) - target_id = Target.construct_id_from_path('subproject/foo/subdir/bar', - 'target2-id', '@other') - self.assertEqual('81d46d1@@target2-id@other', target_id) - - def test_introspect_projectinfo_without_configured_build(self): - testfile = os.path.join(self.common_test_dir, '33 run program', 'meson.build') - res = self.introspect_directory(testfile, '--projectinfo') - self.assertEqual(set(res['buildsystem_files']), {'meson.build'}) - self.assertEqual(res['version'], 'undefined') - self.assertEqual(res['descriptive_name'], 'run command') - self.assertEqual(res['subprojects'], []) - - testfile = os.path.join(self.common_test_dir, '40 options', 'meson.build') - res = self.introspect_directory(testfile, '--projectinfo') - self.assertEqual(set(res['buildsystem_files']), {'meson_options.txt', 'meson.build'}) - self.assertEqual(res['version'], 'undefined') - self.assertEqual(res['descriptive_name'], 'options') - self.assertEqual(res['subprojects'], []) - - testfile = os.path.join(self.common_test_dir, '43 subproject options', 'meson.build') - res = self.introspect_directory(testfile, '--projectinfo') - self.assertEqual(set(res['buildsystem_files']), {'meson_options.txt', 'meson.build'}) - self.assertEqual(res['version'], 'undefined') - self.assertEqual(res['descriptive_name'], 'suboptions') - self.assertEqual(len(res['subprojects']), 1) - subproject_files = {f.replace('\\', '/') for f in res['subprojects'][0]['buildsystem_files']} - self.assertEqual(subproject_files, {'subprojects/subproject/meson_options.txt', 'subprojects/subproject/meson.build'}) - self.assertEqual(res['subprojects'][0]['name'], 'subproject') - self.assertEqual(res['subprojects'][0]['version'], 'undefined') - self.assertEqual(res['subprojects'][0]['descriptive_name'], 'subproject') - - def test_introspect_projectinfo_subprojects(self): - testdir = os.path.join(self.common_test_dir, '98 subproject subdir') - self.init(testdir) - res = self.introspect('--projectinfo') - expected = { - 'descriptive_name': 'proj', - 'version': 'undefined', - 'subproject_dir': 'subprojects', - 'subprojects': [ - { - 'descriptive_name': 'sub', - 'name': 'sub', - 'version': '1.0' - }, - { - 'descriptive_name': 'sub_implicit', - 'name': 'sub_implicit', - 'version': '1.0', - }, - { - 'descriptive_name': 'sub-novar', - 'name': 'sub_novar', - 'version': '1.0', - }, - { - 'descriptive_name': 'subsub', - 'name': 'subsub', - 'version': 'undefined' - }, - { - 'descriptive_name': 'subsubsub', - 'name': 'subsubsub', - 'version': 'undefined' - }, - ] - } - res['subprojects'] = sorted(res['subprojects'], key=lambda i: i['name']) - self.assertDictEqual(expected, res) - - def test_introspection_target_subproject(self): - testdir = os.path.join(self.common_test_dir, '42 subproject') - self.init(testdir) - res = self.introspect('--targets') - - expected = { - 'sublib': 'sublib', - 'simpletest': 'sublib', - 'user': None - } - - for entry in res: - name = entry['name'] - self.assertEqual(entry['subproject'], expected[name]) - - def test_introspect_projectinfo_subproject_dir(self): - testdir = os.path.join(self.common_test_dir, '75 custom subproject dir') - self.init(testdir) - res = self.introspect('--projectinfo') - - self.assertEqual(res['subproject_dir'], 'custom_subproject_dir') - - def test_introspect_projectinfo_subproject_dir_from_source(self): - testfile = os.path.join(self.common_test_dir, '75 custom subproject dir', 'meson.build') - res = self.introspect_directory(testfile, '--projectinfo') - - self.assertEqual(res['subproject_dir'], 'custom_subproject_dir') - - @skipIfNoExecutable('clang-format') - def test_clang_format(self): - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'Clang-format is for now only supported on Ninja, not {self.backend.name}') - testdir = os.path.join(self.unit_test_dir, '54 clang-format') - testfile = os.path.join(testdir, 'prog.c') - badfile = os.path.join(testdir, 'prog_orig_c') - goodfile = os.path.join(testdir, 'prog_expected_c') - testheader = os.path.join(testdir, 'header.h') - badheader = os.path.join(testdir, 'header_orig_h') - goodheader = os.path.join(testdir, 'header_expected_h') - try: - shutil.copyfile(badfile, testfile) - shutil.copyfile(badheader, testheader) - self.init(testdir) - self.assertNotEqual(Path(testfile).read_text(encoding='utf-8'), - Path(goodfile).read_text(encoding='utf-8')) - self.assertNotEqual(Path(testheader).read_text(encoding='utf-8'), - Path(goodheader).read_text(encoding='utf-8')) - self.run_target('clang-format') - self.assertEqual(Path(testheader).read_text(encoding='utf-8'), - Path(goodheader).read_text(encoding='utf-8')) - finally: - if os.path.exists(testfile): - os.unlink(testfile) - if os.path.exists(testheader): - os.unlink(testheader) - - @skipIfNoExecutable('clang-tidy') - def test_clang_tidy(self): - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'Clang-tidy is for now only supported on Ninja, not {self.backend.name}') - if shutil.which('c++') is None: - raise unittest.SkipTest('Clang-tidy breaks when ccache is used and "c++" not in path.') - if is_osx(): - raise unittest.SkipTest('Apple ships a broken clang-tidy that chokes on -pipe.') - testdir = os.path.join(self.unit_test_dir, '69 clang-tidy') - dummydir = os.path.join(testdir, 'dummydir.h') - self.init(testdir, override_envvars={'CXX': 'c++'}) - out = self.run_target('clang-tidy') - self.assertIn('cttest.cpp:4:20', out) - self.assertNotIn(dummydir, out) - - def test_identity_cross(self): - testdir = os.path.join(self.unit_test_dir, '70 cross') - # Do a build to generate a cross file where the host is this target - self.init(testdir, extra_args=['-Dgenerate=true']) - self.meson_cross_file = os.path.join(self.builddir, "crossfile") - self.assertTrue(os.path.exists(self.meson_cross_file)) - # Now verify that this is detected as cross - self.new_builddir() - self.init(testdir) - - def test_introspect_buildoptions_without_configured_build(self): - testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions') - testfile = os.path.join(testdir, 'meson.build') - res_nb = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args) - self.init(testdir, default_args=False) - res_wb = self.introspect('--buildoptions') - self.maxDiff = None - # XXX: These now generate in a different order, is that okay? - self.assertListEqual(sorted(res_nb, key=lambda x: x['name']), sorted(res_wb, key=lambda x: x['name'])) - - def test_meson_configure_from_source_does_not_crash(self): - testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions') - self._run(self.mconf_command + [testdir]) - - def test_introspect_buildoptions_cross_only(self): - testdir = os.path.join(self.unit_test_dir, '83 cross only introspect') - testfile = os.path.join(testdir, 'meson.build') - res = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args) - optnames = [o['name'] for o in res] - self.assertIn('c_args', optnames) - self.assertNotIn('build.c_args', optnames) - - def test_introspect_json_flat(self): - testdir = os.path.join(self.unit_test_dir, '57 introspection') - out = self.init(testdir, extra_args=['-Dlayout=flat']) - infodir = os.path.join(self.builddir, 'meson-info') - self.assertPathExists(infodir) - - with open(os.path.join(infodir, 'intro-targets.json'), encoding='utf-8') as fp: - targets = json.load(fp) - - for i in targets: - for out in i['filename']: - assert(os.path.relpath(out, self.builddir).startswith('meson-out')) - - def test_introspect_json_dump(self): - testdir = os.path.join(self.unit_test_dir, '57 introspection') - self.init(testdir) - infodir = os.path.join(self.builddir, 'meson-info') - self.assertPathExists(infodir) - - def assertKeyTypes(key_type_list, obj, strict: bool = True): - for i in key_type_list: - if isinstance(i[1], (list, tuple)) and None in i[1]: - i = (i[0], tuple([x for x in i[1] if x is not None])) - if i[0] not in obj or obj[i[0]] is None: - continue - self.assertIn(i[0], obj) - self.assertIsInstance(obj[i[0]], i[1]) - if strict: - for k in obj.keys(): - found = False - for i in key_type_list: - if k == i[0]: - found = True - break - self.assertTrue(found, f'Key "{k}" not in expected list') - - root_keylist = [ - ('benchmarks', list), - ('buildoptions', list), - ('buildsystem_files', list), - ('dependencies', list), - ('installed', dict), - ('projectinfo', dict), - ('targets', list), - ('tests', list), - ] - - test_keylist = [ - ('cmd', list), - ('env', dict), - ('name', str), - ('timeout', int), - ('suite', list), - ('is_parallel', bool), - ('protocol', str), - ('depends', list), - ('workdir', (str, None)), - ('priority', int), - ] - - buildoptions_keylist = [ - ('name', str), - ('section', str), - ('type', str), - ('description', str), - ('machine', str), - ('choices', (list, None)), - ('value', (str, int, bool, list)), - ] - - buildoptions_typelist = [ - ('combo', str, [('choices', list)]), - ('string', str, []), - ('boolean', bool, []), - ('integer', int, []), - ('array', list, []), - ] - - buildoptions_sections = ['core', 'backend', 'base', 'compiler', 'directory', 'user', 'test'] - buildoptions_machines = ['any', 'build', 'host'] - - dependencies_typelist = [ - ('name', str), - ('version', str), - ('compile_args', list), - ('link_args', list), - ] - - targets_typelist = [ - ('name', str), - ('id', str), - ('type', str), - ('defined_in', str), - ('filename', list), - ('build_by_default', bool), - ('target_sources', list), - ('extra_files', list), - ('subproject', (str, None)), - ('install_filename', (list, None)), - ('installed', bool), - ] - - targets_sources_typelist = [ - ('language', str), - ('compiler', list), - ('parameters', list), - ('sources', list), - ('generated_sources', list), - ] - - # First load all files - res = {} - for i in root_keylist: - curr = os.path.join(infodir, 'intro-{}.json'.format(i[0])) - self.assertPathExists(curr) - with open(curr, encoding='utf-8') as fp: - res[i[0]] = json.load(fp) - - assertKeyTypes(root_keylist, res) - - # Match target ids to input and output files for ease of reference - src_to_id = {} - out_to_id = {} - name_to_out = {} - for i in res['targets']: - print(json.dump(i, sys.stdout)) - out_to_id.update({os.path.relpath(out, self.builddir): i['id'] - for out in i['filename']}) - name_to_out.update({i['name']: i['filename']}) - for group in i['target_sources']: - src_to_id.update({os.path.relpath(src, testdir): i['id'] - for src in group['sources']}) - - # Check Tests and benchmarks - tests_to_find = ['test case 1', 'test case 2', 'benchmark 1'] - deps_to_find = {'test case 1': [src_to_id['t1.cpp']], - 'test case 2': [src_to_id['t2.cpp'], src_to_id['t3.cpp']], - 'benchmark 1': [out_to_id['file2'], out_to_id['file3'], out_to_id['file4'], src_to_id['t3.cpp']]} - for i in res['benchmarks'] + res['tests']: - assertKeyTypes(test_keylist, i) - if i['name'] in tests_to_find: - tests_to_find.remove(i['name']) - self.assertEqual(sorted(i['depends']), - sorted(deps_to_find[i['name']])) - self.assertListEqual(tests_to_find, []) - - # Check buildoptions - buildopts_to_find = {'cpp_std': 'c++11'} - for i in res['buildoptions']: - assertKeyTypes(buildoptions_keylist, i) - valid_type = False - for j in buildoptions_typelist: - if i['type'] == j[0]: - self.assertIsInstance(i['value'], j[1]) - assertKeyTypes(j[2], i, strict=False) - valid_type = True - break - - self.assertIn(i['section'], buildoptions_sections) - self.assertIn(i['machine'], buildoptions_machines) - self.assertTrue(valid_type) - if i['name'] in buildopts_to_find: - self.assertEqual(i['value'], buildopts_to_find[i['name']]) - buildopts_to_find.pop(i['name'], None) - self.assertDictEqual(buildopts_to_find, {}) - - # Check buildsystem_files - bs_files = ['meson.build', 'meson_options.txt', 'sharedlib/meson.build', 'staticlib/meson.build'] - bs_files = [os.path.join(testdir, x) for x in bs_files] - self.assertPathListEqual(list(sorted(res['buildsystem_files'])), list(sorted(bs_files))) - - # Check dependencies - dependencies_to_find = ['threads'] - for i in res['dependencies']: - assertKeyTypes(dependencies_typelist, i) - if i['name'] in dependencies_to_find: - dependencies_to_find.remove(i['name']) - self.assertListEqual(dependencies_to_find, []) - - # Check projectinfo - self.assertDictEqual(res['projectinfo'], {'version': '1.2.3', 'descriptive_name': 'introspection', 'subproject_dir': 'subprojects', 'subprojects': []}) - - # Check targets - targets_to_find = { - 'sharedTestLib': ('shared library', True, False, 'sharedlib/meson.build', - [os.path.join(testdir, 'sharedlib', 'shared.cpp')]), - 'staticTestLib': ('static library', True, False, 'staticlib/meson.build', - [os.path.join(testdir, 'staticlib', 'static.c')]), - 'custom target test 1': ('custom', False, False, 'meson.build', - [os.path.join(testdir, 'cp.py')]), - 'custom target test 2': ('custom', False, False, 'meson.build', - name_to_out['custom target test 1']), - 'test1': ('executable', True, True, 'meson.build', - [os.path.join(testdir, 't1.cpp')]), - 'test2': ('executable', True, False, 'meson.build', - [os.path.join(testdir, 't2.cpp')]), - 'test3': ('executable', True, False, 'meson.build', - [os.path.join(testdir, 't3.cpp')]), - 'custom target test 3': ('custom', False, False, 'meson.build', - name_to_out['test3']), - } - for i in res['targets']: - assertKeyTypes(targets_typelist, i) - if i['name'] in targets_to_find: - tgt = targets_to_find[i['name']] - self.assertEqual(i['type'], tgt[0]) - self.assertEqual(i['build_by_default'], tgt[1]) - self.assertEqual(i['installed'], tgt[2]) - self.assertPathEqual(i['defined_in'], os.path.join(testdir, tgt[3])) - targets_to_find.pop(i['name'], None) - for j in i['target_sources']: - assertKeyTypes(targets_sources_typelist, j) - self.assertEqual(j['sources'], [os.path.normpath(f) for f in tgt[4]]) - self.assertDictEqual(targets_to_find, {}) - - def test_introspect_file_dump_equals_all(self): - testdir = os.path.join(self.unit_test_dir, '57 introspection') - self.init(testdir) - res_all = self.introspect('--all') - res_file = {} - - root_keylist = [ - 'benchmarks', - 'buildoptions', - 'buildsystem_files', - 'dependencies', - 'installed', - 'projectinfo', - 'targets', - 'tests', - ] - - infodir = os.path.join(self.builddir, 'meson-info') - self.assertPathExists(infodir) - for i in root_keylist: - curr = os.path.join(infodir, f'intro-{i}.json') - self.assertPathExists(curr) - with open(curr, encoding='utf-8') as fp: - res_file[i] = json.load(fp) - - self.assertEqual(res_all, res_file) - - def test_introspect_meson_info(self): - testdir = os.path.join(self.unit_test_dir, '57 introspection') - introfile = os.path.join(self.builddir, 'meson-info', 'meson-info.json') - self.init(testdir) - self.assertPathExists(introfile) - with open(introfile, encoding='utf-8') as fp: - res1 = json.load(fp) - - for i in ['meson_version', 'directories', 'introspection', 'build_files_updated', 'error']: - self.assertIn(i, res1) - - self.assertEqual(res1['error'], False) - self.assertEqual(res1['build_files_updated'], True) - - def test_introspect_config_update(self): - testdir = os.path.join(self.unit_test_dir, '57 introspection') - introfile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json') - self.init(testdir) - self.assertPathExists(introfile) - with open(introfile, encoding='utf-8') as fp: - res1 = json.load(fp) - - for i in res1: - if i['name'] == 'cpp_std': - i['value'] = 'c++14' - if i['name'] == 'build.cpp_std': - i['value'] = 'c++14' - if i['name'] == 'buildtype': - i['value'] = 'release' - if i['name'] == 'optimization': - i['value'] = '3' - if i['name'] == 'debug': - i['value'] = False - - self.setconf('-Dcpp_std=c++14') - self.setconf('-Dbuildtype=release') - - with open(introfile, encoding='utf-8') as fp: - res2 = json.load(fp) - - self.assertListEqual(res1, res2) - - def test_introspect_targets_from_source(self): - testdir = os.path.join(self.unit_test_dir, '57 introspection') - testfile = os.path.join(testdir, 'meson.build') - introfile = os.path.join(self.builddir, 'meson-info', 'intro-targets.json') - self.init(testdir) - self.assertPathExists(introfile) - with open(introfile, encoding='utf-8') as fp: - res_wb = json.load(fp) - - res_nb = self.introspect_directory(testfile, ['--targets'] + self.meson_args) - - # Account for differences in output - res_wb = [i for i in res_wb if i['type'] != 'custom'] - for i in res_wb: - i['filename'] = [os.path.relpath(x, self.builddir) for x in i['filename']] - if 'install_filename' in i: - del i['install_filename'] - - sources = [] - for j in i['target_sources']: - sources += j['sources'] - i['target_sources'] = [{ - 'language': 'unknown', - 'compiler': [], - 'parameters': [], - 'sources': sources, - 'generated_sources': [] - }] - - self.maxDiff = None - self.assertListEqual(res_nb, res_wb) - - def test_introspect_ast_source(self): - testdir = os.path.join(self.unit_test_dir, '57 introspection') - testfile = os.path.join(testdir, 'meson.build') - res_nb = self.introspect_directory(testfile, ['--ast'] + self.meson_args) - - node_counter = {} - - def accept_node(json_node): - self.assertIsInstance(json_node, dict) - for i in ['lineno', 'colno', 'end_lineno', 'end_colno']: - self.assertIn(i, json_node) - self.assertIsInstance(json_node[i], int) - self.assertIn('node', json_node) - n = json_node['node'] - self.assertIsInstance(n, str) - self.assertIn(n, nodes) - if n not in node_counter: - node_counter[n] = 0 - node_counter[n] = node_counter[n] + 1 - for nodeDesc in nodes[n]: - key = nodeDesc[0] - func = nodeDesc[1] - self.assertIn(key, json_node) - if func is None: - tp = nodeDesc[2] - self.assertIsInstance(json_node[key], tp) - continue - func(json_node[key]) - - def accept_node_list(node_list): - self.assertIsInstance(node_list, list) - for i in node_list: - accept_node(i) - - def accept_kwargs(kwargs): - self.assertIsInstance(kwargs, list) - for i in kwargs: - self.assertIn('key', i) - self.assertIn('val', i) - accept_node(i['key']) - accept_node(i['val']) - - nodes = { - 'BooleanNode': [('value', None, bool)], - 'IdNode': [('value', None, str)], - 'NumberNode': [('value', None, int)], - 'StringNode': [('value', None, str)], - 'FormatStringNode': [('value', None, str)], - 'ContinueNode': [], - 'BreakNode': [], - 'ArgumentNode': [('positional', accept_node_list), ('kwargs', accept_kwargs)], - 'ArrayNode': [('args', accept_node)], - 'DictNode': [('args', accept_node)], - 'EmptyNode': [], - 'OrNode': [('left', accept_node), ('right', accept_node)], - 'AndNode': [('left', accept_node), ('right', accept_node)], - 'ComparisonNode': [('left', accept_node), ('right', accept_node), ('ctype', None, str)], - 'ArithmeticNode': [('left', accept_node), ('right', accept_node), ('op', None, str)], - 'NotNode': [('right', accept_node)], - 'CodeBlockNode': [('lines', accept_node_list)], - 'IndexNode': [('object', accept_node), ('index', accept_node)], - 'MethodNode': [('object', accept_node), ('args', accept_node), ('name', None, str)], - 'FunctionNode': [('args', accept_node), ('name', None, str)], - 'AssignmentNode': [('value', accept_node), ('var_name', None, str)], - 'PlusAssignmentNode': [('value', accept_node), ('var_name', None, str)], - 'ForeachClauseNode': [('items', accept_node), ('block', accept_node), ('varnames', None, list)], - 'IfClauseNode': [('ifs', accept_node_list), ('else', accept_node)], - 'IfNode': [('condition', accept_node), ('block', accept_node)], - 'UMinusNode': [('right', accept_node)], - 'TernaryNode': [('condition', accept_node), ('true', accept_node), ('false', accept_node)], - } - - accept_node(res_nb) - - for n, c in [('ContinueNode', 2), ('BreakNode', 1), ('NotNode', 3)]: - self.assertIn(n, node_counter) - self.assertEqual(node_counter[n], c) - - def test_introspect_dependencies_from_source(self): - testdir = os.path.join(self.unit_test_dir, '57 introspection') - testfile = os.path.join(testdir, 'meson.build') - res_nb = self.introspect_directory(testfile, ['--scan-dependencies'] + self.meson_args) - expected = [ - { - 'name': 'threads', - 'required': True, - 'version': [], - 'has_fallback': False, - 'conditional': False - }, - { - 'name': 'zlib', - 'required': False, - 'version': [], - 'has_fallback': False, - 'conditional': False - }, - { - 'name': 'bugDep1', - 'required': True, - 'version': [], - 'has_fallback': False, - 'conditional': False - }, - { - 'name': 'somethingthatdoesnotexist', - 'required': True, - 'version': ['>=1.2.3'], - 'has_fallback': False, - 'conditional': True - }, - { - 'name': 'look_i_have_a_fallback', - 'required': True, - 'version': ['>=1.0.0', '<=99.9.9'], - 'has_fallback': True, - 'conditional': True - } - ] - self.maxDiff = None - self.assertListEqual(res_nb, expected) - - def test_unstable_coredata(self): - testdir = os.path.join(self.common_test_dir, '1 trivial') - self.init(testdir) - # just test that the command does not fail (e.g. because it throws an exception) - self._run([*self.meson_command, 'unstable-coredata', self.builddir]) - - @skip_if_no_cmake - def test_cmake_prefix_path(self): - testdir = os.path.join(self.unit_test_dir, '63 cmake_prefix_path') - self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')]) - - @skip_if_no_cmake - def test_cmake_parser(self): - testdir = os.path.join(self.unit_test_dir, '64 cmake parser') - self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')]) - - def test_alias_target(self): - if self.backend is Backend.vs: - # FIXME: This unit test is broken with vs backend, needs investigation - raise unittest.SkipTest(f'Skipping alias_target test with {self.backend.name} backend') - testdir = os.path.join(self.unit_test_dir, '65 alias target') - self.init(testdir) - self.build() - self.assertPathDoesNotExist(os.path.join(self.builddir, 'prog' + exe_suffix)) - self.assertPathDoesNotExist(os.path.join(self.builddir, 'hello.txt')) - self.run_target('build-all') - self.assertPathExists(os.path.join(self.builddir, 'prog' + exe_suffix)) - self.assertPathExists(os.path.join(self.builddir, 'hello.txt')) - - def test_configure(self): - testdir = os.path.join(self.common_test_dir, '2 cpp') - self.init(testdir) - self._run(self.mconf_command + [self.builddir]) - - def test_summary(self): - testdir = os.path.join(self.unit_test_dir, '72 summary') - out = self.init(testdir) - expected = textwrap.dedent(r''' - Some Subproject 2.0 - - string : bar - integer: 1 - boolean: True - - My Project 1.0 - - Configuration - Some boolean : False - Another boolean: True - Some string : Hello World - A list : string - 1 - True - empty list : - enabled_opt : enabled - A number : 1 - yes : YES - no : NO - coma list : a, b, c - - Stuff - missing prog : NO - existing prog : ''' + sys.executable + ''' - missing dep : NO - internal dep : YES - - Plugins - long coma list : alpha, alphacolor, apetag, audiofx, audioparsers, auparse, - autodetect, avi - - Subprojects - sub : YES - sub2 : NO Problem encountered: This subproject failed - ''') - expected_lines = expected.split('\n')[1:] - out_start = out.find(expected_lines[0]) - out_lines = out[out_start:].split('\n')[:len(expected_lines)] - if sys.version_info < (3, 7, 0): - # Dictionary order is not stable in Python <3.7, so sort the lines - # while comparing - self.assertEqual(sorted(expected_lines), sorted(out_lines)) - else: - self.assertEqual(expected_lines, out_lines) - - def test_meson_compile(self): - """Test the meson compile command.""" - - def get_exe_name(basename: str) -> str: - if is_windows(): - return f'{basename}.exe' - else: - return basename - - def get_shared_lib_name(basename: str) -> str: - if mesonbuild.environment.detect_msys2_arch(): - return f'lib{basename}.dll' - elif is_windows(): - return f'{basename}.dll' - elif is_cygwin(): - return f'cyg{basename}.dll' - elif is_osx(): - return f'lib{basename}.dylib' - else: - return f'lib{basename}.so' - - def get_static_lib_name(basename: str) -> str: - return f'lib{basename}.a' - - # Base case (no targets or additional arguments) - - testdir = os.path.join(self.common_test_dir, '1 trivial') - self.init(testdir) - - self._run([*self.meson_command, 'compile', '-C', self.builddir]) - self.assertPathExists(os.path.join(self.builddir, get_exe_name('trivialprog'))) - - # `--clean` - - self._run([*self.meson_command, 'compile', '-C', self.builddir, '--clean']) - self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) - - # Target specified in a project with unique names - - testdir = os.path.join(self.common_test_dir, '6 linkshared') - self.init(testdir, extra_args=['--wipe']) - # Multiple targets and target type specified - self._run([*self.meson_command, 'compile', '-C', self.builddir, 'mylib', 'mycpplib:shared_library']) - # Check that we have a shared lib, but not an executable, i.e. check that target actually worked - self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mylib'))) - self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('prog'))) - self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mycpplib'))) - self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('cppprog'))) - - # Target specified in a project with non unique names - - testdir = os.path.join(self.common_test_dir, '185 same target name') - self.init(testdir, extra_args=['--wipe']) - self._run([*self.meson_command, 'compile', '-C', self.builddir, './foo']) - self.assertPathExists(os.path.join(self.builddir, get_static_lib_name('foo'))) - self._run([*self.meson_command, 'compile', '-C', self.builddir, 'sub/foo']) - self.assertPathExists(os.path.join(self.builddir, 'sub', get_static_lib_name('foo'))) - - # run_target - - testdir = os.path.join(self.common_test_dir, '51 run target') - self.init(testdir, extra_args=['--wipe']) - out = self._run([*self.meson_command, 'compile', '-C', self.builddir, 'py3hi']) - self.assertIn('I am Python3.', out) - - # `--$BACKEND-args` - - testdir = os.path.join(self.common_test_dir, '1 trivial') - if self.backend is Backend.ninja: - self.init(testdir, extra_args=['--wipe']) - # Dry run - should not create a program - self._run([*self.meson_command, 'compile', '-C', self.builddir, '--ninja-args=-n']) - self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) - elif self.backend is Backend.vs: - self.init(testdir, extra_args=['--wipe']) - self._run([*self.meson_command, 'compile', '-C', self.builddir]) - # Explicitly clean the target through msbuild interface - self._run([*self.meson_command, 'compile', '-C', self.builddir, '--vs-args=-t:{}:Clean'.format(re.sub(r'[\%\$\@\;\.\(\)\']', '_', get_exe_name('trivialprog')))]) - self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) - - def test_spurious_reconfigure_built_dep_file(self): - testdir = os.path.join(self.unit_test_dir, '74 dep files') - - # Regression test: Spurious reconfigure was happening when build - # directory is inside source directory. - # See https://gitlab.freedesktop.org/gstreamer/gst-build/-/issues/85. - srcdir = os.path.join(self.builddir, 'srctree') - shutil.copytree(testdir, srcdir) - builddir = os.path.join(srcdir, '_build') - self.change_builddir(builddir) - - self.init(srcdir) - self.build() - - # During first configure the file did not exist so no dependency should - # have been set. A rebuild should not trigger a reconfigure. - self.clean() - out = self.build() - self.assertNotIn('Project configured', out) - - self.init(srcdir, extra_args=['--reconfigure']) - - # During the reconfigure the file did exist, but is inside build - # directory, so no dependency should have been set. A rebuild should not - # trigger a reconfigure. - self.clean() - out = self.build() - self.assertNotIn('Project configured', out) - - def _test_junit(self, case: str) -> None: - try: - import lxml.etree as et - except ImportError: - raise unittest.SkipTest('lxml required, but not found.') - - schema = et.XMLSchema(et.parse(str(Path(__file__).parent / 'data' / 'schema.xsd'))) - - self.init(case) - self.run_tests() - - junit = et.parse(str(Path(self.builddir) / 'meson-logs' / 'testlog.junit.xml')) - try: - schema.assertValid(junit) - except et.DocumentInvalid as e: - self.fail(e.error_log) - - def test_junit_valid_tap(self): - self._test_junit(os.path.join(self.common_test_dir, '206 tap tests')) - - def test_junit_valid_exitcode(self): - self._test_junit(os.path.join(self.common_test_dir, '41 test args')) - - def test_junit_valid_gtest(self): - self._test_junit(os.path.join(self.framework_test_dir, '2 gtest')) - - def test_link_language_linker(self): - # TODO: there should be some way to query how we're linking things - # without resorting to reading the ninja.build file - if self.backend is not Backend.ninja: - raise unittest.SkipTest('This test reads the ninja file') - - testdir = os.path.join(self.common_test_dir, '225 link language') - self.init(testdir) - - build_ninja = os.path.join(self.builddir, 'build.ninja') - with open(build_ninja, encoding='utf-8') as f: - contents = f.read() - - self.assertRegex(contents, r'build main(\.exe)?.*: c_LINKER') - self.assertRegex(contents, r'build (lib|cyg)?mylib.*: c_LINKER') - - def test_commands_documented(self): - ''' - Test that all listed meson commands are documented in Commands.md. - ''' - - # The docs directory is not in release tarballs. - if not os.path.isdir('docs'): - raise unittest.SkipTest('Doc directory does not exist.') - doc_path = 'docs/markdown/Commands.md' - - md = None - with open(doc_path, encoding='utf-8') as f: - md = f.read() - self.assertIsNotNone(md) - - ## Get command sections - - section_pattern = re.compile(r'^### (.+)$', re.MULTILINE) - md_command_section_matches = [i for i in section_pattern.finditer(md)] - md_command_sections = dict() - for i, s in enumerate(md_command_section_matches): - section_end = len(md) if i == len(md_command_section_matches) - 1 else md_command_section_matches[i + 1].start() - md_command_sections[s.group(1)] = (s.start(), section_end) - - ## Validate commands - - md_commands = {k for k,v in md_command_sections.items()} - - help_output = self._run(self.meson_command + ['--help']) - help_commands = {c.strip() for c in re.findall(r'usage:(?:.+)?{((?:[a-z]+,*)+?)}', help_output, re.MULTILINE|re.DOTALL)[0].split(',')} - - self.assertEqual(md_commands | {'help'}, help_commands, f'Doc file: `{doc_path}`') - - ## Validate that each section has proper placeholders - - def get_data_pattern(command): - return re.compile( - r'{{ ' + command + r'_usage.inc }}[\r\n]' - r'.*?' - r'{{ ' + command + r'_arguments.inc }}[\r\n]', - flags = re.MULTILINE|re.DOTALL) - - for command in md_commands: - m = get_data_pattern(command).search(md, pos=md_command_sections[command][0], endpos=md_command_sections[command][1]) - self.assertIsNotNone(m, f'Command `{command}` is missing placeholders for dynamic data. Doc file: `{doc_path}`') - - def _check_coverage_files(self, types=('text', 'xml', 'html')): - covdir = Path(self.builddir) / 'meson-logs' - files = [] - if 'text' in types: - files.append('coverage.txt') - if 'xml' in types: - files.append('coverage.xml') - if 'html' in types: - files.append('coveragereport/index.html') - for f in files: - self.assertTrue((covdir / f).is_file(), msg=f'{f} is not a file') - - def test_coverage(self): - if mesonbuild.environment.detect_msys2_arch(): - raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() - if not gcovr_exe: - raise unittest.SkipTest('gcovr not found, or too old') - testdir = os.path.join(self.common_test_dir, '1 trivial') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): - raise unittest.SkipTest('llvm-cov not found') - if cc.get_id() == 'msvc': - raise unittest.SkipTest('Test only applies to non-MSVC compilers') - self.init(testdir, extra_args=['-Db_coverage=true']) - self.build() - self.run_tests() - self.run_target('coverage') - self._check_coverage_files() - - def test_coverage_complex(self): - if mesonbuild.environment.detect_msys2_arch(): - raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() - if not gcovr_exe: - raise unittest.SkipTest('gcovr not found, or too old') - testdir = os.path.join(self.common_test_dir, '105 generatorcustom') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): - raise unittest.SkipTest('llvm-cov not found') - if cc.get_id() == 'msvc': - raise unittest.SkipTest('Test only applies to non-MSVC compilers') - self.init(testdir, extra_args=['-Db_coverage=true']) - self.build() - self.run_tests() - self.run_target('coverage') - self._check_coverage_files() - - def test_coverage_html(self): - if mesonbuild.environment.detect_msys2_arch(): - raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() - if not gcovr_exe: - raise unittest.SkipTest('gcovr not found, or too old') - testdir = os.path.join(self.common_test_dir, '1 trivial') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): - raise unittest.SkipTest('llvm-cov not found') - if cc.get_id() == 'msvc': - raise unittest.SkipTest('Test only applies to non-MSVC compilers') - self.init(testdir, extra_args=['-Db_coverage=true']) - self.build() - self.run_tests() - self.run_target('coverage-html') - self._check_coverage_files(['html']) - - def test_coverage_text(self): - if mesonbuild.environment.detect_msys2_arch(): - raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() - if not gcovr_exe: - raise unittest.SkipTest('gcovr not found, or too old') - testdir = os.path.join(self.common_test_dir, '1 trivial') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): - raise unittest.SkipTest('llvm-cov not found') - if cc.get_id() == 'msvc': - raise unittest.SkipTest('Test only applies to non-MSVC compilers') - self.init(testdir, extra_args=['-Db_coverage=true']) - self.build() - self.run_tests() - self.run_target('coverage-text') - self._check_coverage_files(['text']) - - def test_coverage_xml(self): - if mesonbuild.environment.detect_msys2_arch(): - raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') - gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() - if not gcovr_exe: - raise unittest.SkipTest('gcovr not found, or too old') - testdir = os.path.join(self.common_test_dir, '1 trivial') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_id() == 'clang': - if not mesonbuild.environment.detect_llvm_cov(): - raise unittest.SkipTest('llvm-cov not found') - if cc.get_id() == 'msvc': - raise unittest.SkipTest('Test only applies to non-MSVC compilers') - self.init(testdir, extra_args=['-Db_coverage=true']) - self.build() - self.run_tests() - self.run_target('coverage-xml') - self._check_coverage_files(['xml']) - - def test_cross_file_constants(self): - with temp_filename() as crossfile1, temp_filename() as crossfile2: - with open(crossfile1, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent( - ''' - [constants] - compiler = 'gcc' - ''')) - with open(crossfile2, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent( - ''' - [constants] - toolchain = '/toolchain/' - common_flags = ['--sysroot=' + toolchain / 'sysroot'] - - [properties] - c_args = common_flags + ['-DSOMETHING'] - cpp_args = c_args + ['-DSOMETHING_ELSE'] - - [binaries] - c = toolchain / compiler - ''')) - - values = mesonbuild.coredata.parse_machine_files([crossfile1, crossfile2]) - self.assertEqual(values['binaries']['c'], '/toolchain/gcc') - self.assertEqual(values['properties']['c_args'], - ['--sysroot=/toolchain/sysroot', '-DSOMETHING']) - self.assertEqual(values['properties']['cpp_args'], - ['--sysroot=/toolchain/sysroot', '-DSOMETHING', '-DSOMETHING_ELSE']) - - @unittest.skipIf(is_windows(), 'Directory cleanup fails for some reason') - def test_wrap_git(self): - with tempfile.TemporaryDirectory() as tmpdir: - srcdir = os.path.join(tmpdir, 'src') - shutil.copytree(os.path.join(self.unit_test_dir, '81 wrap-git'), srcdir) - upstream = os.path.join(srcdir, 'subprojects', 'wrap_git_upstream') - upstream_uri = Path(upstream).as_uri() - _git_init(upstream) - with open(os.path.join(srcdir, 'subprojects', 'wrap_git.wrap'), 'w', encoding='utf-8') as f: - f.write(textwrap.dedent(''' - [wrap-git] - url = {} - patch_directory = wrap_git_builddef - revision = master - '''.format(upstream_uri))) - self.init(srcdir) - self.build() - self.run_tests() - - def test_multi_output_custom_target_no_warning(self): - testdir = os.path.join(self.common_test_dir, '228 custom_target source') - - out = self.init(testdir) - self.assertNotRegex(out, 'WARNING:.*Using the first one.') - self.build() - self.run_tests() - - @unittest.skipUnless(is_linux() and (re.search('^i.86$|^x86$|^x64$|^x86_64$|^amd64$', platform.processor()) is not None), - 'Requires ASM compiler for x86 or x86_64 platform currently only available on Linux CI runners') - def test_nostdlib(self): - testdir = os.path.join(self.unit_test_dir, '78 nostdlib') - machinefile = os.path.join(self.builddir, 'machine.txt') - with open(machinefile, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent(''' - [properties] - c_stdlib = 'mylibc' - ''')) - - # Test native C stdlib - self.meson_native_file = machinefile - self.init(testdir) - self.build() - - # Test cross C stdlib - self.new_builddir() - self.meson_native_file = None - self.meson_cross_file = machinefile - self.init(testdir) - self.build() - - def test_meson_version_compare(self): - testdir = os.path.join(self.unit_test_dir, '82 meson version compare') - out = self.init(testdir) - self.assertNotRegex(out, r'WARNING') - - def test_wrap_redirect(self): - redirect_wrap = os.path.join(self.builddir, 'redirect.wrap') - real_wrap = os.path.join(self.builddir, 'foo/subprojects/real.wrap') - os.makedirs(os.path.dirname(real_wrap)) - - # Invalid redirect, filename must have .wrap extension - with open(redirect_wrap, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent(''' - [wrap-redirect] - filename = foo/subprojects/real.wrapper - ''')) - with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be a .wrap file'): - PackageDefinition(redirect_wrap) - - # Invalid redirect, filename cannot be in parent directory - with open(redirect_wrap, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent(''' - [wrap-redirect] - filename = ../real.wrap - ''')) - with self.assertRaisesRegex(WrapException, 'wrap-redirect filename cannot contain ".."'): - PackageDefinition(redirect_wrap) - - # Invalid redirect, filename must be in foo/subprojects/real.wrap - with open(redirect_wrap, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent(''' - [wrap-redirect] - filename = foo/real.wrap - ''')) - with self.assertRaisesRegex(WrapException, 'wrap-redirect filename must be in the form foo/subprojects/bar.wrap'): - wrap = PackageDefinition(redirect_wrap) - - # Correct redirect - with open(redirect_wrap, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent(''' - [wrap-redirect] - filename = foo/subprojects/real.wrap - ''')) - with open(real_wrap, 'w', encoding='utf-8') as f: - f.write(textwrap.dedent(''' - [wrap-git] - url = http://invalid - ''')) - wrap = PackageDefinition(redirect_wrap) - self.assertEqual(wrap.get('url'), 'http://invalid') - - @skip_if_no_cmake - def test_nested_cmake_rebuild(self) -> None: - # This checks a bug where if a non-meson project is used as a third - # level (or deeper) subproject it doesn't cause a rebuild if the build - # files for that project are changed - testdir = os.path.join(self.unit_test_dir, '85 nested subproject regenerate depends') - cmakefile = Path(testdir) / 'subprojects' / 'sub2' / 'CMakeLists.txt' - self.init(testdir) - self.build() - with cmakefile.open('a', encoding='utf-8') as f: - os.utime(str(cmakefile)) - self.assertReconfiguredBuildIsNoop() - - def test_version_file(self): - srcdir = os.path.join(self.common_test_dir, '2 cpp') - self.init(srcdir) - projinfo = self.introspect('--projectinfo') - self.assertEqual(projinfo['version'], '1.0.0') - - def test_cflags_cppflags(self): - envs = {'CPPFLAGS': '-DCPPFLAG', - 'CFLAGS': '-DCFLAG', - 'CXXFLAGS': '-DCXXFLAG'} - srcdir = os.path.join(self.unit_test_dir, '89 multiple envvars') - self.init(srcdir, override_envvars=envs) - self.build() - - def test_build_b_options(self) -> None: - # Currently (0.57) these do nothing, but they've always been allowed - srcdir = os.path.join(self.common_test_dir, '2 cpp') - self.init(srcdir, extra_args=['-Dbuild.b_lto=true']) - - def test_install_skip_subprojects(self): - testdir = os.path.join(self.unit_test_dir, '92 install skip subprojects') - self.init(testdir) - self.build() - - main_expected = [ - '', - 'share', - 'include', - 'foo', - 'bin', - 'share/foo', - 'share/foo/foo.dat', - 'include/foo.h', - 'foo/foofile', - 'bin/foo' + exe_suffix, - ] - bar_expected = [ - 'bar', - 'share/foo/bar.dat', - 'include/bar.h', - 'bin/bar' + exe_suffix, - 'bar/barfile' - ] - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_argument_syntax() == 'msvc': - main_expected.append('bin/foo.pdb') - bar_expected.append('bin/bar.pdb') - prefix = destdir_join(self.installdir, self.prefix) - main_expected = [Path(prefix, p) for p in main_expected] - bar_expected = [Path(prefix, p) for p in bar_expected] - all_expected = main_expected + bar_expected - - def check_installed_files(extra_args, expected): - args = ['install', '--destdir', self.installdir] + extra_args - self._run(self.meson_command + args, workdir=self.builddir) - all_files = [p for p in Path(self.installdir).rglob('*')] - self.assertEqual(sorted(expected), sorted(all_files)) - windows_proof_rmtree(self.installdir) - - check_installed_files([], all_expected) - check_installed_files(['--skip-subprojects'], main_expected) - check_installed_files(['--skip-subprojects', 'bar'], main_expected) - check_installed_files(['--skip-subprojects', 'another'], all_expected) - - def test_adding_subproject_to_configure_project(self) -> None: - srcdir = os.path.join(self.unit_test_dir, '93 new subproject in configured project') - self.init(srcdir) - self.build() - self.setconf('-Duse-sub=true') - self.build() - - def test_devenv(self): - testdir = os.path.join(self.unit_test_dir, '91 devenv') - self.init(testdir) - self.build() - - cmd = self.meson_command + ['devenv', '-C', self.builddir] - script = os.path.join(testdir, 'test-devenv.py') - app = os.path.join(self.builddir, 'app') - self._run(cmd + python_command + [script]) - self.assertEqual('This is text.', self._run(cmd + [app]).strip()) - - def test_clang_format_check(self): - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'Skipping clang-format tests with {self.backend.name} backend') - if not shutil.which('clang-format'): - raise unittest.SkipTest('clang-format not found') - - testdir = os.path.join(self.unit_test_dir, '94 clangformat') - newdir = os.path.join(self.builddir, 'testdir') - shutil.copytree(testdir, newdir) - self.new_builddir() - self.init(newdir) - - # Should reformat 1 file but not return error - output = self.build('clang-format') - self.assertEqual(1, output.count('File reformatted:')) - - # Reset source tree then try again with clang-format-check, it should - # return an error code this time. - windows_proof_rmtree(newdir) - shutil.copytree(testdir, newdir) - with self.assertRaises(subprocess.CalledProcessError): - output = self.build('clang-format-check') - self.assertEqual(1, output.count('File reformatted:')) - - # The check format should not touch any files. Thus - # running format again has some work to do. - output = self.build('clang-format') - self.assertEqual(1, output.count('File reformatted:')) - self.build('clang-format-check') - - def test_custom_target_implicit_include(self): - testdir = os.path.join(self.unit_test_dir, '95 custominc') - self.init(testdir) - self.build() - compdb = self.get_compdb() - matches = 0 - for c in compdb: - if 'prog.c' in c['file']: - self.assertNotIn('easytogrepfor', c['command']) - matches += 1 - self.assertEqual(matches, 1) - matches = 0 - for c in compdb: - if 'prog2.c' in c['file']: - self.assertIn('easytogrepfor', c['command']) - matches += 1 - self.assertEqual(matches, 1) - - def test_env_flags_to_linker(self) -> None: - # Compilers that act as drivers should add their compiler flags to the - # linker, those that do not shouldn't - with mock.patch.dict(os.environ, {'CFLAGS': '-DCFLAG', 'LDFLAGS': '-flto'}): - env = get_fake_env() - - # Get the compiler so we know which compiler class to mock. - cc = mesonbuild.compilers.detect_compiler_for(env, 'c', MachineChoice.HOST) - cc_type = type(cc) - - # Test a compiler that acts as a linker - with mock.patch.object(cc_type, 'INVOKES_LINKER', True): - cc = mesonbuild.compilers.detect_compiler_for(env, 'c', MachineChoice.HOST) - link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language) - self.assertEqual(sorted(link_args), sorted(['-DCFLAG', '-flto'])) - - # And one that doesn't - with mock.patch.object(cc_type, 'INVOKES_LINKER', False): - cc = mesonbuild.compilers.detect_compiler_for(env, 'c', MachineChoice.HOST) - link_args = env.coredata.get_external_link_args(cc.for_machine, cc.language) - self.assertEqual(sorted(link_args), sorted(['-flto'])) - -class FailureTests(BasePlatformTests): - ''' - Tests that test failure conditions. Build files here should be dynamically - generated and static tests should go into `test cases/failing*`. - This is useful because there can be many ways in which a particular - function can fail, and creating failing tests for all of them is tedious - and slows down testing. - ''' - dnf = "[Dd]ependency.*not found(:.*)?" - nopkg = '[Pp]kg-config.*not found' - - def setUp(self): - super().setUp() - self.srcdir = os.path.realpath(tempfile.mkdtemp()) - self.mbuild = os.path.join(self.srcdir, 'meson.build') - self.moptions = os.path.join(self.srcdir, 'meson_options.txt') - - def tearDown(self): - super().tearDown() - windows_proof_rmtree(self.srcdir) - - def assertMesonRaises(self, contents, match, *, - extra_args=None, - langs=None, - meson_version=None, - options=None, - override_envvars=None): - ''' - Assert that running meson configure on the specified @contents raises - a error message matching regex @match. - ''' - if langs is None: - langs = [] - with open(self.mbuild, 'w', encoding='utf-8') as f: - f.write("project('failure test', 'c', 'cpp'") - if meson_version: - f.write(f", meson_version: '{meson_version}'") - f.write(")\n") - for lang in langs: - f.write(f"add_languages('{lang}', required : false)\n") - f.write(contents) - if options is not None: - with open(self.moptions, 'w', encoding='utf-8') as f: - f.write(options) - o = {'MESON_FORCE_BACKTRACE': '1'} - if override_envvars is None: - override_envvars = o - else: - override_envvars.update(o) - # Force tracebacks so we can detect them properly - with self.assertRaisesRegex(MesonException, match, msg=contents): - # Must run in-process or we'll get a generic CalledProcessError - self.init(self.srcdir, extra_args=extra_args, - inprocess=True, - override_envvars = override_envvars) - - def obtainMesonOutput(self, contents, match, extra_args, langs, meson_version=None): - if langs is None: - langs = [] - with open(self.mbuild, 'w', encoding='utf-8') as f: - f.write("project('output test', 'c', 'cpp'") - if meson_version: - f.write(f", meson_version: '{meson_version}'") - f.write(")\n") - for lang in langs: - f.write(f"add_languages('{lang}', required : false)\n") - f.write(contents) - # Run in-process for speed and consistency with assertMesonRaises - return self.init(self.srcdir, extra_args=extra_args, inprocess=True) - - def assertMesonOutputs(self, contents, match, extra_args=None, langs=None, meson_version=None): - ''' - Assert that running meson configure on the specified @contents outputs - something that matches regex @match. - ''' - out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version) - self.assertRegex(out, match) - - def assertMesonDoesNotOutput(self, contents, match, extra_args=None, langs=None, meson_version=None): - ''' - Assert that running meson configure on the specified @contents does not output - something that matches regex @match. - ''' - out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version) - self.assertNotRegex(out, match) - - @skipIfNoPkgconfig - def test_dependency(self): - if subprocess.call(['pkg-config', '--exists', 'zlib']) != 0: - raise unittest.SkipTest('zlib not found with pkg-config') - a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"), - ("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"), - ("dependency('zlib', version : 1)", "Item must be a list or one of "), - ("dependency('zlib', required : 1)", "[Rr]equired.*boolean"), - ("dependency('zlib', method : 1)", "[Mm]ethod.*string"), - ("dependency('zlibfail')", self.dnf),) - for contents, match in a: - self.assertMesonRaises(contents, match) - - def test_apple_frameworks_dependency(self): - if not is_osx(): - raise unittest.SkipTest('only run on macOS') - self.assertMesonRaises("dependency('appleframeworks')", - "requires at least one module") - - def test_extraframework_dependency_method(self): - code = "dependency('python', method : 'extraframework')" - if not is_osx(): - self.assertMesonRaises(code, self.dnf) - else: - # Python2 framework is always available on macOS - self.assertMesonOutputs(code, '[Dd]ependency.*python.*found.*YES') - - def test_sdl2_notfound_dependency(self): - # Want to test failure, so skip if available - if shutil.which('sdl2-config'): - raise unittest.SkipTest('sdl2-config found') - self.assertMesonRaises("dependency('sdl2', method : 'sdlconfig')", self.dnf) - if shutil.which('pkg-config'): - self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", self.dnf) - with no_pkgconfig(): - # Look for pkg-config, cache it, then - # Use cached pkg-config without erroring out, then - # Use cached pkg-config to error out - code = "dependency('foobarrr', method : 'pkg-config', required : false)\n" \ - "dependency('foobarrr2', method : 'pkg-config', required : false)\n" \ - "dependency('sdl2', method : 'pkg-config')" - self.assertMesonRaises(code, self.nopkg) - - def test_gnustep_notfound_dependency(self): - # Want to test failure, so skip if available - if shutil.which('gnustep-config'): - raise unittest.SkipTest('gnustep-config found') - self.assertMesonRaises("dependency('gnustep')", - f"(requires a Objc compiler|{self.dnf})", - langs = ['objc']) - - def test_wx_notfound_dependency(self): - # Want to test failure, so skip if available - if shutil.which('wx-config-3.0') or shutil.which('wx-config') or shutil.which('wx-config-gtk3'): - raise unittest.SkipTest('wx-config, wx-config-3.0 or wx-config-gtk3 found') - self.assertMesonRaises("dependency('wxwidgets')", self.dnf) - self.assertMesonOutputs("dependency('wxwidgets', required : false)", - "Run-time dependency .*WxWidgets.* found: .*NO.*") - - def test_wx_dependency(self): - if not shutil.which('wx-config-3.0') and not shutil.which('wx-config') and not shutil.which('wx-config-gtk3'): - raise unittest.SkipTest('Neither wx-config, wx-config-3.0 nor wx-config-gtk3 found') - self.assertMesonRaises("dependency('wxwidgets', modules : 1)", - "module argument is not a string") - - def test_llvm_dependency(self): - self.assertMesonRaises("dependency('llvm', modules : 'fail')", - f"(required.*fail|{self.dnf})") - - def test_boost_notfound_dependency(self): - # Can be run even if Boost is found or not - self.assertMesonRaises("dependency('boost', modules : 1)", - "module.*not a string") - self.assertMesonRaises("dependency('boost', modules : 'fail')", - f"(fail.*not found|{self.dnf})") - - def test_boost_BOOST_ROOT_dependency(self): - # Test BOOST_ROOT; can be run even if Boost is found or not - self.assertMesonRaises("dependency('boost')", - f"(boost_root.*absolute|{self.dnf})", - override_envvars = {'BOOST_ROOT': 'relative/path'}) - - def test_dependency_invalid_method(self): - code = '''zlib_dep = dependency('zlib', required : false) - zlib_dep.get_configtool_variable('foo') - ''' - self.assertMesonRaises(code, ".* is not a config-tool dependency") - code = '''zlib_dep = dependency('zlib', required : false) - dep = declare_dependency(dependencies : zlib_dep) - dep.get_pkgconfig_variable('foo') - ''' - self.assertMesonRaises(code, "Method.*pkgconfig.*is invalid.*internal") - code = '''zlib_dep = dependency('zlib', required : false) - dep = declare_dependency(dependencies : zlib_dep) - dep.get_configtool_variable('foo') - ''' - self.assertMesonRaises(code, "Method.*configtool.*is invalid.*internal") - - def test_objc_cpp_detection(self): - ''' - Test that when we can't detect objc or objcpp, we fail gracefully. - ''' - env = get_fake_env() - try: - detect_objc_compiler(env, MachineChoice.HOST) - detect_objcpp_compiler(env, MachineChoice.HOST) - except EnvironmentException: - code = "add_languages('objc')\nadd_languages('objcpp')" - self.assertMesonRaises(code, "Unknown compiler") - return - raise unittest.SkipTest("objc and objcpp found, can't test detection failure") - - def test_subproject_variables(self): - ''' - Test that: - 1. The correct message is outputted when a not-required dep is not - found and the fallback subproject is also not found. - 2. A not-required fallback dependency is not found because the - subproject failed to parse. - 3. A not-found not-required dep with a fallback subproject outputs the - correct message when the fallback subproject is found but the - variable inside it is not. - 4. A fallback dependency is found from the subproject parsed in (3) - 5. A wrap file from a subproject is used but fails because it does not - contain required keys. - ''' - tdir = os.path.join(self.unit_test_dir, '20 subproj dep variables') - stray_file = os.path.join(tdir, 'subprojects/subsubproject.wrap') - if os.path.exists(stray_file): - windows_proof_rm(stray_file) - out = self.init(tdir, inprocess=True) - self.assertRegex(out, r"Neither a subproject directory nor a .*nosubproj.wrap.* file was found") - self.assertRegex(out, r'Function does not take positional arguments.') - self.assertRegex(out, r'Dependency .*somenotfounddep.* from subproject .*subprojects/somesubproj.* found: .*NO.*') - self.assertRegex(out, r'Dependency .*zlibproxy.* from subproject .*subprojects.*somesubproj.* found: .*YES.*') - self.assertRegex(out, r'Missing key .*source_filename.* in subsubproject.wrap') - windows_proof_rm(stray_file) - - def test_exception_exit_status(self): - ''' - Test exit status on python exception - ''' - tdir = os.path.join(self.unit_test_dir, '21 exit status') - with self.assertRaises(subprocess.CalledProcessError) as cm: - self.init(tdir, inprocess=False, override_envvars = {'MESON_UNIT_TEST': '1', 'MESON_FORCE_BACKTRACE': ''}) - self.assertEqual(cm.exception.returncode, 2) - self.wipe() - - def test_dict_requires_key_value_pairs(self): - self.assertMesonRaises("dict = {3, 'foo': 'bar'}", - 'Only key:value pairs are valid in dict construction.') - self.assertMesonRaises("{'foo': 'bar', 3}", - 'Only key:value pairs are valid in dict construction.') - - def test_dict_forbids_duplicate_keys(self): - self.assertMesonRaises("dict = {'a': 41, 'a': 42}", - 'Duplicate dictionary key: a.*') - - def test_dict_forbids_integer_key(self): - self.assertMesonRaises("dict = {3: 'foo'}", - 'Key must be a string.*') - - def test_using_too_recent_feature(self): - # Here we use a dict, which was introduced in 0.47.0 - self.assertMesonOutputs("dict = {}", - ".*WARNING.*Project targeting.*but.*", - meson_version='>= 0.46.0') - - def test_using_recent_feature(self): - # Same as above, except the meson version is now appropriate - self.assertMesonDoesNotOutput("dict = {}", - ".*WARNING.*Project targeting.*but.*", - meson_version='>= 0.47') - - def test_using_too_recent_feature_dependency(self): - self.assertMesonOutputs("dependency('pcap', required: false)", - ".*WARNING.*Project targeting.*but.*", - meson_version='>= 0.41.0') - - def test_vcs_tag_featurenew_build_always_stale(self): - 'https://github.com/mesonbuild/meson/issues/3904' - vcs_tag = '''version_data = configuration_data() - version_data.set('PROJVER', '@VCS_TAG@') - vf = configure_file(output : 'version.h.in', configuration: version_data) - f = vcs_tag(input : vf, output : 'version.h') - ''' - msg = '.*WARNING:.*feature.*build_always_stale.*custom_target.*' - self.assertMesonDoesNotOutput(vcs_tag, msg, meson_version='>=0.43') - - def test_missing_subproject_not_required_and_required(self): - self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" + - "sub2 = subproject('not-found-subproject', required: true)", - """.*Subproject "subprojects/not-found-subproject" required but not found.*""") - - def test_get_variable_on_not_found_project(self): - self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" + - "sub1.get_variable('naaa')", - """Subproject "subprojects/not-found-subproject" disabled can't get_variable on it.""") - - def test_version_checked_before_parsing_options(self): - ''' - https://github.com/mesonbuild/meson/issues/5281 - ''' - options = "option('some-option', type: 'foo', value: '')" - match = 'Meson version is.*but project requires >=2000' - self.assertMesonRaises("", match, meson_version='>=2000', options=options) - - def test_assert_default_message(self): - self.assertMesonRaises("k1 = 'a'\n" + - "assert({\n" + - " k1: 1,\n" + - "}['a'] == 2)\n", - r"Assert failed: {k1 : 1}\['a'\] == 2") - - def test_wrap_nofallback(self): - self.assertMesonRaises("dependency('notfound', fallback : ['foo', 'foo_dep'])", - r"Dependency 'notfound' is required but not found.", - extra_args=['--wrap-mode=nofallback']) - - def test_message(self): - self.assertMesonOutputs("message('Array:', ['a', 'b'])", - r"Message:.* Array: \['a', 'b'\]") - - def test_warning(self): - self.assertMesonOutputs("warning('Array:', ['a', 'b'])", - r"WARNING:.* Array: \['a', 'b'\]") - - def test_override_dependency_twice(self): - self.assertMesonRaises("meson.override_dependency('foo', declare_dependency())\n" + - "meson.override_dependency('foo', declare_dependency())", - """Tried to override dependency 'foo' which has already been resolved or overridden""") - - @unittest.skipIf(is_windows(), 'zlib is not available on Windows') - def test_override_resolved_dependency(self): - self.assertMesonRaises("dependency('zlib')\n" + - "meson.override_dependency('zlib', declare_dependency())", - """Tried to override dependency 'zlib' which has already been resolved or overridden""") - - def test_error_func(self): - self.assertMesonRaises("error('a', 'b', ['c', ['d', {'e': 'f'}]], 'g')", - r"Problem encountered: a b \['c', \['d', {'e' : 'f'}\]\] g") - - -@unittest.skipUnless(is_windows() or is_cygwin(), "requires Windows (or Windows via Cygwin)") -class WindowsTests(BasePlatformTests): - ''' - Tests that should run on Cygwin, MinGW, and MSVC - ''' - - def setUp(self): - super().setUp() - self.platform_test_dir = os.path.join(self.src_root, 'test cases/windows') - - @unittest.skipIf(is_cygwin(), 'Test only applicable to Windows') - @mock.patch.dict(os.environ) - def test_find_program(self): - ''' - Test that Windows-specific edge-cases in find_program are functioning - correctly. Cannot be an ordinary test because it involves manipulating - PATH to point to a directory with Python scripts. - ''' - testdir = os.path.join(self.platform_test_dir, '8 find program') - # Find `cmd` and `cmd.exe` - prog1 = ExternalProgram('cmd') - self.assertTrue(prog1.found(), msg='cmd not found') - prog2 = ExternalProgram('cmd.exe') - self.assertTrue(prog2.found(), msg='cmd.exe not found') - self.assertPathEqual(prog1.get_path(), prog2.get_path()) - # Find cmd.exe with args without searching - prog = ExternalProgram('cmd', command=['cmd', '/C']) - self.assertTrue(prog.found(), msg='cmd not found with args') - self.assertPathEqual(prog.get_command()[0], 'cmd') - # Find cmd with an absolute path that's missing the extension - cmd_path = prog2.get_path()[:-4] - prog = ExternalProgram(cmd_path) - self.assertTrue(prog.found(), msg=f'{cmd_path!r} not found') - # Finding a script with no extension inside a directory works - prog = ExternalProgram(os.path.join(testdir, 'test-script')) - self.assertTrue(prog.found(), msg='test-script not found') - # Finding a script with an extension inside a directory works - prog = ExternalProgram(os.path.join(testdir, 'test-script-ext.py')) - self.assertTrue(prog.found(), msg='test-script-ext.py not found') - # Finding a script in PATH - os.environ['PATH'] += os.pathsep + testdir - # If `.PY` is in PATHEXT, scripts can be found as programs - if '.PY' in [ext.upper() for ext in os.environ['PATHEXT'].split(';')]: - # Finding a script in PATH w/o extension works and adds the interpreter - prog = ExternalProgram('test-script-ext') - self.assertTrue(prog.found(), msg='test-script-ext not found in PATH') - self.assertPathEqual(prog.get_command()[0], python_command[0]) - self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') - # Finding a script in PATH with extension works and adds the interpreter - prog = ExternalProgram('test-script-ext.py') - self.assertTrue(prog.found(), msg='test-script-ext.py not found in PATH') - self.assertPathEqual(prog.get_command()[0], python_command[0]) - self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') - # Using a script with an extension directly via command= works and adds the interpreter - prog = ExternalProgram('test-script-ext.py', command=[os.path.join(testdir, 'test-script-ext.py'), '--help']) - self.assertTrue(prog.found(), msg='test-script-ext.py with full path not picked up via command=') - self.assertPathEqual(prog.get_command()[0], python_command[0]) - self.assertPathEqual(prog.get_command()[2], '--help') - self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') - # Using a script without an extension directly via command= works and adds the interpreter - prog = ExternalProgram('test-script', command=[os.path.join(testdir, 'test-script'), '--help']) - self.assertTrue(prog.found(), msg='test-script with full path not picked up via command=') - self.assertPathEqual(prog.get_command()[0], python_command[0]) - self.assertPathEqual(prog.get_command()[2], '--help') - self.assertPathBasenameEqual(prog.get_path(), 'test-script') - # Ensure that WindowsApps gets removed from PATH - path = os.environ['PATH'] - if 'WindowsApps' not in path: - username = os.environ['USERNAME'] - appstore_dir = fr'C:\Users\{username}\AppData\Local\Microsoft\WindowsApps' - path = os.pathsep + appstore_dir - path = ExternalProgram._windows_sanitize_path(path) - self.assertNotIn('WindowsApps', path) - - def test_ignore_libs(self): - ''' - Test that find_library on libs that are to be ignored returns an empty - array of arguments. Must be a unit test because we cannot inspect - ExternalLibraryHolder from build files. - ''' - testdir = os.path.join(self.platform_test_dir, '1 basic') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_argument_syntax() != 'msvc': - raise unittest.SkipTest('Not using MSVC') - # To force people to update this test, and also test - self.assertEqual(set(cc.ignore_libs), {'c', 'm', 'pthread', 'dl', 'rt', 'execinfo'}) - for l in cc.ignore_libs: - self.assertEqual(cc.find_library(l, env, []), []) - - def test_rc_depends_files(self): - testdir = os.path.join(self.platform_test_dir, '5 resources') - - # resource compiler depfile generation is not yet implemented for msvc - env = get_fake_env(testdir, self.builddir, self.prefix) - depfile_works = detect_c_compiler(env, MachineChoice.HOST).get_id() not in {'msvc', 'clang-cl', 'intel-cl'} - - self.init(testdir) - self.build() - # Immediately rebuilding should not do anything - self.assertBuildIsNoop() - # Test compile_resources(depend_file:) - # Changing mtime of sample.ico should rebuild prog - self.utime(os.path.join(testdir, 'res', 'sample.ico')) - self.assertRebuiltTarget('prog') - # Test depfile generation by compile_resources - # Changing mtime of resource.h should rebuild myres.rc and then prog - if depfile_works: - self.utime(os.path.join(testdir, 'inc', 'resource', 'resource.h')) - self.assertRebuiltTarget('prog') - self.wipe() - - if depfile_works: - testdir = os.path.join(self.platform_test_dir, '12 resources with custom targets') - self.init(testdir) - self.build() - # Immediately rebuilding should not do anything - self.assertBuildIsNoop() - # Changing mtime of resource.h should rebuild myres_1.rc and then prog_1 - self.utime(os.path.join(testdir, 'res', 'resource.h')) - self.assertRebuiltTarget('prog_1') - - def test_msvc_cpp17(self): - testdir = os.path.join(self.unit_test_dir, '45 vscpp17') - - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_argument_syntax() != 'msvc': - raise unittest.SkipTest('Test only applies to MSVC-like compilers') - - try: - self.init(testdir) - except subprocess.CalledProcessError: - # According to Python docs, output is only stored when - # using check_output. We don't use it, so we can't check - # that the output is correct (i.e. that it failed due - # to the right reason). - return - self.build() - - def test_install_pdb_introspection(self): - testdir = os.path.join(self.platform_test_dir, '1 basic') - - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_argument_syntax() != 'msvc': - raise unittest.SkipTest('Test only applies to MSVC-like compilers') - - self.init(testdir) - installed = self.introspect('--installed') - files = [os.path.basename(path) for path in installed.values()] - - self.assertTrue('prog.pdb' in files) - - def _check_ld(self, name: str, lang: str, expected: str) -> None: - if not shutil.which(name): - raise unittest.SkipTest(f'Could not find {name}.') - envvars = [mesonbuild.envconfig.ENV_VAR_PROG_MAP[f'{lang}_ld']] - - # Also test a deprecated variable if there is one. - if f'{lang}_ld' in mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP: - envvars.append( - mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP[f'{lang}_ld']) - - for envvar in envvars: - with mock.patch.dict(os.environ, {envvar: name}): - env = get_fake_env() - try: - comp = compiler_from_language(env, lang, MachineChoice.HOST) - except EnvironmentException: - raise unittest.SkipTest(f'Could not find a compiler for {lang}') - self.assertEqual(comp.linker.id, expected) - - def test_link_environment_variable_lld_link(self): - env = get_fake_env() - comp = detect_c_compiler(env, MachineChoice.HOST) - if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): - raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') - self._check_ld('lld-link', 'c', 'lld-link') - - def test_link_environment_variable_link(self): - env = get_fake_env() - comp = detect_c_compiler(env, MachineChoice.HOST) - if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): - raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') - self._check_ld('link', 'c', 'link') - - def test_link_environment_variable_optlink(self): - env = get_fake_env() - comp = detect_c_compiler(env, MachineChoice.HOST) - if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): - raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') - self._check_ld('optlink', 'c', 'optlink') - - @skip_if_not_language('rust') - def test_link_environment_variable_rust(self): - self._check_ld('link', 'rust', 'link') - - @skip_if_not_language('d') - def test_link_environment_variable_d(self): - env = get_fake_env() - comp = detect_d_compiler(env, MachineChoice.HOST) - if comp.id == 'dmd': - raise unittest.SkipTest('meson cannot reliably make DMD use a different linker.') - self._check_ld('lld-link', 'd', 'lld-link') - - def test_pefile_checksum(self): - try: - import pefile - except ImportError: - if is_ci(): - raise - raise unittest.SkipTest('pefile module not found') - testdir = os.path.join(self.common_test_dir, '6 linkshared') - self.init(testdir, extra_args=['--buildtype=release']) - self.build() - # Test that binaries have a non-zero checksum - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - cc_id = cc.get_id() - ld_id = cc.get_linker_id() - dll = glob(os.path.join(self.builddir, '*mycpplib.dll'))[0] - exe = os.path.join(self.builddir, 'cppprog.exe') - for f in (dll, exe): - pe = pefile.PE(f) - msg = f'PE file: {f!r}, compiler: {cc_id!r}, linker: {ld_id!r}' - if cc_id == 'clang-cl': - # Latest clang-cl tested (7.0) does not write checksums out - self.assertFalse(pe.verify_checksum(), msg=msg) - else: - # Verify that a valid checksum was written by all other compilers - self.assertTrue(pe.verify_checksum(), msg=msg) - - def test_qt5dependency_vscrt(self): - ''' - Test that qt5 dependencies use the debug module suffix when b_vscrt is - set to 'mdd' - ''' - # Verify that the `b_vscrt` option is available - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - if OptionKey('b_vscrt') not in cc.base_options: - raise unittest.SkipTest('Compiler does not support setting the VS CRT') - # Verify that qmake is for Qt5 - if not shutil.which('qmake-qt5'): - if not shutil.which('qmake') and not is_ci(): - raise unittest.SkipTest('QMake not found') - output = subprocess.getoutput('qmake --version') - if 'Qt version 5' not in output and not is_ci(): - raise unittest.SkipTest('Qmake found, but it is not for Qt 5.') - # Setup with /MDd - testdir = os.path.join(self.framework_test_dir, '4 qt') - self.init(testdir, extra_args=['-Db_vscrt=mdd']) - # Verify that we're linking to the debug versions of Qt DLLs - build_ninja = os.path.join(self.builddir, 'build.ninja') - with open(build_ninja, encoding='utf-8') as f: - contents = f.read() - m = re.search('build qt5core.exe: cpp_LINKER.*Qt5Cored.lib', contents) - self.assertIsNotNone(m, msg=contents) - - def test_compiler_checks_vscrt(self): - ''' - Test that the correct VS CRT is used when running compiler checks - ''' - # Verify that the `b_vscrt` option is available - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - if OptionKey('b_vscrt') not in cc.base_options: - raise unittest.SkipTest('Compiler does not support setting the VS CRT') - - def sanitycheck_vscrt(vscrt): - checks = self.get_meson_log_sanitychecks() - self.assertTrue(len(checks) > 0) - for check in checks: - self.assertIn(vscrt, check) - - testdir = os.path.join(self.common_test_dir, '1 trivial') - self.init(testdir) - sanitycheck_vscrt('/MDd') - - self.new_builddir() - self.init(testdir, extra_args=['-Dbuildtype=debugoptimized']) - sanitycheck_vscrt('/MD') - - self.new_builddir() - self.init(testdir, extra_args=['-Dbuildtype=release']) - sanitycheck_vscrt('/MD') - - self.new_builddir() - self.init(testdir, extra_args=['-Db_vscrt=md']) - sanitycheck_vscrt('/MD') - - self.new_builddir() - self.init(testdir, extra_args=['-Db_vscrt=mdd']) - sanitycheck_vscrt('/MDd') - - self.new_builddir() - self.init(testdir, extra_args=['-Db_vscrt=mt']) - sanitycheck_vscrt('/MT') - - self.new_builddir() - self.init(testdir, extra_args=['-Db_vscrt=mtd']) - sanitycheck_vscrt('/MTd') - - def test_modules(self): - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'C++ modules only work with the Ninja backend (not {self.backend.name}).') - if 'VSCMD_VER' not in os.environ: - raise unittest.SkipTest('C++ modules is only supported with Visual Studio.') - if version_compare(os.environ['VSCMD_VER'], '<16.10.0'): - raise unittest.SkipTest('C++ modules are only supported with VS 2019 Preview or newer.') - self.init(os.path.join(self.unit_test_dir, '86 cpp modules')) - self.build() - - -@unittest.skipUnless(is_osx(), "requires Darwin") -class DarwinTests(BasePlatformTests): - ''' - Tests that should run on macOS - ''' - - def setUp(self): - super().setUp() - self.platform_test_dir = os.path.join(self.src_root, 'test cases/osx') - - def test_apple_bitcode(self): - ''' - Test that -fembed-bitcode is correctly added while compiling and - -bitcode_bundle is added while linking when b_bitcode is true and not - when it is false. This can't be an ordinary test case because we need - to inspect the compiler database. - ''' - testdir = os.path.join(self.platform_test_dir, '7 bitcode') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.id != 'clang': - raise unittest.SkipTest('Not using Clang on OSX') - # Try with bitcode enabled - out = self.init(testdir, extra_args='-Db_bitcode=true') - # Warning was printed - self.assertRegex(out, 'WARNING:.*b_bitcode') - # Compiler options were added - for compdb in self.get_compdb(): - if 'module' in compdb['file']: - self.assertNotIn('-fembed-bitcode', compdb['command']) - else: - self.assertIn('-fembed-bitcode', compdb['command']) - build_ninja = os.path.join(self.builddir, 'build.ninja') - # Linker options were added - with open(build_ninja, encoding='utf-8') as f: - contents = f.read() - m = re.search('LINK_ARGS =.*-bitcode_bundle', contents) - self.assertIsNotNone(m, msg=contents) - # Try with bitcode disabled - self.setconf('-Db_bitcode=false') - # Regenerate build - self.build() - for compdb in self.get_compdb(): - self.assertNotIn('-fembed-bitcode', compdb['command']) - build_ninja = os.path.join(self.builddir, 'build.ninja') - with open(build_ninja, encoding='utf-8') as f: - contents = f.read() - m = re.search('LINK_ARGS =.*-bitcode_bundle', contents) - self.assertIsNone(m, msg=contents) - - def test_apple_bitcode_modules(self): - ''' - Same as above, just for shared_module() - ''' - testdir = os.path.join(self.common_test_dir, '148 shared module resolving symbol in executable') - # Ensure that it builds even with bitcode enabled - self.init(testdir, extra_args='-Db_bitcode=true') - self.build() - self.run_tests() - - def _get_darwin_versions(self, fname): - fname = os.path.join(self.builddir, fname) - out = subprocess.check_output(['otool', '-L', fname], universal_newlines=True) - m = re.match(r'.*version (.*), current version (.*)\)', out.split('\n')[1]) - self.assertIsNotNone(m, msg=out) - return m.groups() - - @skipIfNoPkgconfig - def test_library_versioning(self): - ''' - Ensure that compatibility_version and current_version are set correctly - ''' - testdir = os.path.join(self.platform_test_dir, '2 library versions') - self.init(testdir) - self.build() - targets = {} - for t in self.introspect('--targets'): - targets[t['name']] = t['filename'][0] if isinstance(t['filename'], list) else t['filename'] - self.assertEqual(self._get_darwin_versions(targets['some']), ('7.0.0', '7.0.0')) - self.assertEqual(self._get_darwin_versions(targets['noversion']), ('0.0.0', '0.0.0')) - self.assertEqual(self._get_darwin_versions(targets['onlyversion']), ('1.0.0', '1.0.0')) - self.assertEqual(self._get_darwin_versions(targets['onlysoversion']), ('5.0.0', '5.0.0')) - self.assertEqual(self._get_darwin_versions(targets['intver']), ('2.0.0', '2.0.0')) - self.assertEqual(self._get_darwin_versions(targets['stringver']), ('2.3.0', '2.3.0')) - self.assertEqual(self._get_darwin_versions(targets['stringlistver']), ('2.4.0', '2.4.0')) - self.assertEqual(self._get_darwin_versions(targets['intstringver']), ('1111.0.0', '2.5.0')) - self.assertEqual(self._get_darwin_versions(targets['stringlistvers']), ('2.6.0', '2.6.1')) - - def test_duplicate_rpath(self): - testdir = os.path.join(self.unit_test_dir, '10 build_rpath') - # We purposely pass a duplicate rpath to Meson, in order - # to ascertain that Meson does not call install_name_tool - # with duplicate -delete_rpath arguments, which would - # lead to erroring out on installation - env = {"LDFLAGS": "-Wl,-rpath,/foo/bar"} - self.init(testdir, override_envvars=env) - self.build() - self.install() - - def test_removing_unused_linker_args(self): - testdir = os.path.join(self.common_test_dir, '104 has arg') - env = {'CFLAGS': '-L/tmp -L /var/tmp -headerpad_max_install_names -Wl,-export_dynamic -framework Foundation'} - self.init(testdir, override_envvars=env) - - def test_objc_versions(self): - # Objective-C always uses the C standard version. - # Objecttive-C++ always uses the C++ standard version. - # This is what most people seem to want and in addition - # it is the only setup supported by Xcode. - testdir = os.path.join(self.objc_test_dir, '1 simple') - self.init(testdir) - self.assertIn('-std=c99', self.get_compdb()[0]['command']) - self.wipe() - testdir = os.path.join(self.objcpp_test_dir, '1 simple') - self.init(testdir) - self.assertIn('-std=c++14', self.get_compdb()[0]['command']) - -@unittest.skipUnless(not is_windows(), "requires something Unix-like") -class LinuxlikeTests(BasePlatformTests): - ''' - Tests that should run on Linux, macOS, and *BSD - ''' - - def test_basic_soname(self): - ''' - Test that the soname is set correctly for shared libraries. This can't - be an ordinary test case because we need to run `readelf` and actually - check the soname. - https://github.com/mesonbuild/meson/issues/785 - ''' - testdir = os.path.join(self.common_test_dir, '4 shared') - self.init(testdir) - self.build() - lib1 = os.path.join(self.builddir, 'libmylib.so') - soname = get_soname(lib1) - self.assertEqual(soname, 'libmylib.so') - - def test_custom_soname(self): - ''' - Test that the soname is set correctly for shared libraries when - a custom prefix and/or suffix is used. This can't be an ordinary test - case because we need to run `readelf` and actually check the soname. - https://github.com/mesonbuild/meson/issues/785 - ''' - testdir = os.path.join(self.common_test_dir, '24 library versions') - self.init(testdir) - self.build() - lib1 = os.path.join(self.builddir, 'prefixsomelib.suffix') - soname = get_soname(lib1) - self.assertEqual(soname, 'prefixsomelib.suffix') - - def test_pic(self): - ''' - Test that -fPIC is correctly added to static libraries when b_staticpic - is true and not when it is false. This can't be an ordinary test case - because we need to inspect the compiler database. - ''' - if is_windows() or is_cygwin() or is_osx(): - raise unittest.SkipTest('PIC not relevant') - - testdir = os.path.join(self.common_test_dir, '3 static') - self.init(testdir) - compdb = self.get_compdb() - self.assertIn('-fPIC', compdb[0]['command']) - self.setconf('-Db_staticpic=false') - # Regenerate build - self.build() - compdb = self.get_compdb() - self.assertNotIn('-fPIC', compdb[0]['command']) - - @mock.patch.dict(os.environ) - def test_pkgconfig_gen(self): - ''' - Test that generated pkg-config files can be found and have the correct - version and link args. This can't be an ordinary test case because we - need to run pkg-config outside of a Meson build file. - https://github.com/mesonbuild/meson/issues/889 - ''' - testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') - self.init(testdir) - env = get_fake_env(testdir, self.builddir, self.prefix) - kwargs = {'required': True, 'silent': True} - os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir - foo_dep = PkgConfigDependency('libfoo', env, kwargs) - self.assertTrue(foo_dep.found()) - self.assertEqual(foo_dep.get_version(), '1.0') - self.assertIn('-lfoo', foo_dep.get_link_args()) - self.assertEqual(foo_dep.get_pkgconfig_variable('foo', {}), 'bar') - self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data') - - libhello_nolib = PkgConfigDependency('libhello_nolib', env, kwargs) - self.assertTrue(libhello_nolib.found()) - self.assertEqual(libhello_nolib.get_link_args(), []) - self.assertEqual(libhello_nolib.get_compile_args(), []) - self.assertEqual(libhello_nolib.get_pkgconfig_variable('foo', {}), 'bar') - self.assertEqual(libhello_nolib.get_pkgconfig_variable('prefix', {}), self.prefix) - self.assertEqual(libhello_nolib.get_pkgconfig_variable('escaped_var', {}), r'hello\ world') - self.assertEqual(libhello_nolib.get_pkgconfig_variable('unescaped_var', {}), 'hello world') - - cc = detect_c_compiler(env, MachineChoice.HOST) - if cc.get_id() in {'gcc', 'clang'}: - for name in {'ct', 'ct0'}: - ct_dep = PkgConfigDependency(name, env, kwargs) - self.assertTrue(ct_dep.found()) - self.assertIn('-lct', ct_dep.get_link_args()) - - def test_pkgconfig_gen_deps(self): - ''' - Test that generated pkg-config files correctly handle dependencies - ''' - testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') - self.init(testdir) - privatedir1 = self.privatedir - - self.new_builddir() - testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen', 'dependencies') - self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': privatedir1}) - privatedir2 = self.privatedir - - env = { - 'PKG_CONFIG_LIBDIR': os.pathsep.join([privatedir1, privatedir2]), - 'PKG_CONFIG_SYSTEM_LIBRARY_PATH': '/usr/lib', - } - self._run(['pkg-config', 'dependency-test', '--validate'], override_envvars=env) - - # pkg-config strips some duplicated flags so we have to parse the - # generated file ourself. - expected = { - 'Requires': 'libexposed', - 'Requires.private': 'libfoo >= 1.0', - 'Libs': '-L${libdir} -llibmain -pthread -lcustom', - 'Libs.private': '-lcustom2 -L${libdir} -llibinternal', - 'Cflags': '-I${includedir} -pthread -DCUSTOM', - } - if is_osx() or is_haiku(): - expected['Cflags'] = expected['Cflags'].replace('-pthread ', '') - with open(os.path.join(privatedir2, 'dependency-test.pc'), encoding='utf-8') as f: - matched_lines = 0 - for line in f: - parts = line.split(':', 1) - if parts[0] in expected: - key = parts[0] - val = parts[1].strip() - expected_val = expected[key] - self.assertEqual(expected_val, val) - matched_lines += 1 - self.assertEqual(len(expected), matched_lines) - - cmd = ['pkg-config', 'requires-test'] - out = self._run(cmd + ['--print-requires'], override_envvars=env).strip().split('\n') - if not is_openbsd(): - self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) - else: - self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello'])) - - cmd = ['pkg-config', 'requires-private-test'] - out = self._run(cmd + ['--print-requires-private'], override_envvars=env).strip().split('\n') - if not is_openbsd(): - self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) - else: - self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello'])) - - cmd = ['pkg-config', 'pub-lib-order'] - out = self._run(cmd + ['--libs'], override_envvars=env).strip().split() - self.assertEqual(out, ['-llibmain2', '-llibinternal']) - - # See common/44 pkgconfig-gen/meson.build for description of the case this test - with open(os.path.join(privatedir1, 'simple2.pc'), encoding='utf-8') as f: - content = f.read() - self.assertIn('Libs: -L${libdir} -lsimple2 -lsimple1', content) - self.assertIn('Libs.private: -lz', content) - - with open(os.path.join(privatedir1, 'simple3.pc'), encoding='utf-8') as f: - content = f.read() - self.assertEqual(1, content.count('-lsimple3')) - - with open(os.path.join(privatedir1, 'simple5.pc'), encoding='utf-8') as f: - content = f.read() - self.assertNotIn('-lstat2', content) - - @mock.patch.dict(os.environ) - def test_pkgconfig_uninstalled(self): - testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') - self.init(testdir) - self.build() - - os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(self.builddir, 'meson-uninstalled') - if is_cygwin(): - os.environ['PATH'] += os.pathsep + self.builddir - - self.new_builddir() - testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen', 'dependencies') - self.init(testdir) - self.build() - self.run_tests() - - def test_pkg_unfound(self): - testdir = os.path.join(self.unit_test_dir, '23 unfound pkgconfig') - self.init(testdir) - with open(os.path.join(self.privatedir, 'somename.pc'), encoding='utf-8') as f: - pcfile = f.read() - self.assertFalse('blub_blob_blib' in pcfile) - - def test_symlink_builddir(self): - ''' - Test using a symlink as either the builddir for "setup" or - the argument for "-C". - ''' - testdir = os.path.join(self.common_test_dir, '1 trivial') - os.symlink(self.builddir, self.builddir + '-symlink') - self.change_builddir(self.builddir + '-symlink') - self.init(testdir) - self.build() - self._run(self.mtest_command) - - def test_vala_c_warnings(self): - ''' - Test that no warnings are emitted for C code generated by Vala. This - can't be an ordinary test case because we need to inspect the compiler - database. - https://github.com/mesonbuild/meson/issues/864 - ''' - if not shutil.which('valac'): - raise unittest.SkipTest('valac not installed.') - testdir = os.path.join(self.vala_test_dir, '5 target glib') - self.init(testdir) - compdb = self.get_compdb() - vala_command = None - c_command = None - for each in compdb: - if each['file'].endswith('GLib.Thread.c'): - vala_command = each['command'] - elif each['file'].endswith('GLib.Thread.vala'): - continue - elif each['file'].endswith('retcode.c'): - c_command = each['command'] - else: - m = 'Unknown file {!r} in vala_c_warnings test'.format(each['file']) - raise AssertionError(m) - self.assertIsNotNone(vala_command) - self.assertIsNotNone(c_command) - # -w suppresses all warnings, should be there in Vala but not in C - self.assertIn(" -w ", vala_command) - self.assertNotIn(" -w ", c_command) - # -Wall enables all warnings, should be there in C but not in Vala - self.assertNotIn(" -Wall ", vala_command) - self.assertIn(" -Wall ", c_command) - # -Werror converts warnings to errors, should always be there since it's - # injected by an unrelated piece of code and the project has werror=true - self.assertIn(" -Werror ", vala_command) - self.assertIn(" -Werror ", c_command) - - @skipIfNoPkgconfig - def test_qtdependency_pkgconfig_detection(self): - ''' - Test that qt4 and qt5 detection with pkgconfig works. - ''' - # Verify Qt4 or Qt5 can be found with pkg-config - qt4 = subprocess.call(['pkg-config', '--exists', 'QtCore']) - qt5 = subprocess.call(['pkg-config', '--exists', 'Qt5Core']) - testdir = os.path.join(self.framework_test_dir, '4 qt') - self.init(testdir, extra_args=['-Dmethod=pkg-config']) - # Confirm that the dependency was found with pkg-config - mesonlog = self.get_meson_log() - if qt4 == 0: - self.assertRegex('\n'.join(mesonlog), - r'Run-time dependency qt4 \(modules: Core\) found: YES 4.* \(pkg-config\)') - if qt5 == 0: - self.assertRegex('\n'.join(mesonlog), - r'Run-time dependency qt5 \(modules: Core\) found: YES 5.* \(pkg-config\)') - - @skip_if_not_base_option('b_sanitize') - def test_generate_gir_with_address_sanitizer(self): - if is_cygwin(): - raise unittest.SkipTest('asan not available on Cygwin') - if is_openbsd(): - raise unittest.SkipTest('-fsanitize=address is not supported on OpenBSD') - - testdir = os.path.join(self.framework_test_dir, '7 gnome') - self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false']) - self.build() - - def test_qt5dependency_qmake_detection(self): - ''' - Test that qt5 detection with qmake works. This can't be an ordinary - test case because it involves setting the environment. - ''' - # Verify that qmake is for Qt5 - if not shutil.which('qmake-qt5'): - if not shutil.which('qmake'): - raise unittest.SkipTest('QMake not found') - output = subprocess.getoutput('qmake --version') - if 'Qt version 5' not in output: - raise unittest.SkipTest('Qmake found, but it is not for Qt 5.') - # Disable pkg-config codepath and force searching with qmake/qmake-qt5 - testdir = os.path.join(self.framework_test_dir, '4 qt') - self.init(testdir, extra_args=['-Dmethod=qmake']) - # Confirm that the dependency was found with qmake - mesonlog = self.get_meson_log() - self.assertRegex('\n'.join(mesonlog), - r'Run-time dependency qt5 \(modules: Core\) found: YES .* \(qmake\)\n') - - def test_qt6dependency_qmake_detection(self): - ''' - Test that qt6 detection with qmake works. This can't be an ordinary - test case because it involves setting the environment. - ''' - # Verify that qmake is for Qt5 - if not shutil.which('qmake-qt6'): - if not shutil.which('qmake'): - raise unittest.SkipTest('QMake not found') - output = subprocess.getoutput('qmake --version') - if 'Qt version 6' not in output: - raise unittest.SkipTest('Qmake found, but it is not for Qt 6.') - # Disable pkg-config codepath and force searching with qmake/qmake-qt6 - testdir = os.path.join(self.framework_test_dir, '4 qt') - self.init(testdir, extra_args=['-Dmethod=qmake']) - # Confirm that the dependency was found with qmake - mesonlog = self.get_meson_log() - self.assertRegex('\n'.join(mesonlog), - r'Run-time dependency qt6 \(modules: Core\) found: YES .* \(qmake\)\n') - - def glob_sofiles_without_privdir(self, g): - files = glob(g) - return [f for f in files if not f.endswith('.p')] - - def _test_soname_impl(self, libpath, install): - if is_cygwin() or is_osx(): - raise unittest.SkipTest('Test only applicable to ELF and linuxlike sonames') - - testdir = os.path.join(self.unit_test_dir, '1 soname') - self.init(testdir) - self.build() - if install: - self.install() - - # File without aliases set. - nover = os.path.join(libpath, 'libnover.so') - self.assertPathExists(nover) - self.assertFalse(os.path.islink(nover)) - self.assertEqual(get_soname(nover), 'libnover.so') - self.assertEqual(len(self.glob_sofiles_without_privdir(nover[:-3] + '*')), 1) - - # File with version set - verset = os.path.join(libpath, 'libverset.so') - self.assertPathExists(verset + '.4.5.6') - self.assertEqual(os.readlink(verset), 'libverset.so.4') - self.assertEqual(get_soname(verset), 'libverset.so.4') - self.assertEqual(len(self.glob_sofiles_without_privdir(verset[:-3] + '*')), 3) - - # File with soversion set - soverset = os.path.join(libpath, 'libsoverset.so') - self.assertPathExists(soverset + '.1.2.3') - self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3') - self.assertEqual(get_soname(soverset), 'libsoverset.so.1.2.3') - self.assertEqual(len(self.glob_sofiles_without_privdir(soverset[:-3] + '*')), 2) - - # File with version and soversion set to same values - settosame = os.path.join(libpath, 'libsettosame.so') - self.assertPathExists(settosame + '.7.8.9') - self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9') - self.assertEqual(get_soname(settosame), 'libsettosame.so.7.8.9') - self.assertEqual(len(self.glob_sofiles_without_privdir(settosame[:-3] + '*')), 2) - - # File with version and soversion set to different values - bothset = os.path.join(libpath, 'libbothset.so') - self.assertPathExists(bothset + '.1.2.3') - self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3') - self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6') - self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3') - self.assertEqual(len(self.glob_sofiles_without_privdir(bothset[:-3] + '*')), 3) - - def test_soname(self): - self._test_soname_impl(self.builddir, False) - - def test_installed_soname(self): - libdir = self.installdir + os.path.join(self.prefix, self.libdir) - self._test_soname_impl(libdir, True) - - def test_compiler_check_flags_order(self): - ''' - Test that compiler check flags override all other flags. This can't be - an ordinary test case because it needs the environment to be set. - ''' - testdir = os.path.join(self.common_test_dir, '36 has function') - env = get_fake_env(testdir, self.builddir, self.prefix) - cpp = detect_cpp_compiler(env, MachineChoice.HOST) - Oflag = '-O3' - OflagCPP = Oflag - if cpp.get_id() in ('clang', 'gcc'): - # prevent developers from adding "int main(int argc, char **argv)" - # to small Meson checks unless these parameters are actually used - OflagCPP += ' -Werror=unused-parameter' - env = {'CFLAGS': Oflag, - 'CXXFLAGS': OflagCPP} - self.init(testdir, override_envvars=env) - cmds = self.get_meson_log_compiler_checks() - for cmd in cmds: - if cmd[0] == 'ccache': - cmd = cmd[1:] - # Verify that -I flags from the `args` kwarg are first - # This is set in the '36 has function' test case - self.assertEqual(cmd[1], '-I/tmp') - # Verify that -O3 set via the environment is overridden by -O0 - Oargs = [arg for arg in cmd if arg.startswith('-O')] - self.assertEqual(Oargs, [Oflag, '-O0']) - - def _test_stds_impl(self, testdir: str, compiler: 'Compiler') -> None: - has_cpp17 = (compiler.get_id() not in {'clang', 'gcc'} or - compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=5.0.0', '>=9.1') or - compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=5.0.0')) - has_cpp2a_c17 = (compiler.get_id() not in {'clang', 'gcc'} or - compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=6.0.0', '>=10.0') or - compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0')) - has_cpp20 = (compiler.get_id() not in {'clang', 'gcc'} or - compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=10.0.0', None) or - compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=10.0.0')) - has_c18 = (compiler.get_id() not in {'clang', 'gcc'} or - compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=8.0.0', '>=11.0') or - compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0')) - # Check that all the listed -std=xxx options for this compiler work just fine when used - # https://en.wikipedia.org/wiki/Xcode#Latest_versions - # https://www.gnu.org/software/gcc/projects/cxx-status.html - key = OptionKey('std', lang=compiler.language) - for v in compiler.get_options()[key].choices: - # we do it like this to handle gnu++17,c++17 and gnu17,c17 cleanly - # thus, C++ first - if '++17' in v and not has_cpp17: - continue - elif '++2a' in v and not has_cpp2a_c17: # https://en.cppreference.com/w/cpp/compiler_support - continue - elif '++20' in v and not has_cpp20: - continue - # now C - elif '17' in v and not has_cpp2a_c17: - continue - elif '18' in v and not has_c18: - continue - self.init(testdir, extra_args=[f'-D{key!s}={v}']) - cmd = self.get_compdb()[0]['command'] - # c++03 and gnu++03 are not understood by ICC, don't try to look for them - skiplist = frozenset([ - ('intel', 'c++03'), - ('intel', 'gnu++03')]) - if v != 'none' and not (compiler.get_id(), v) in skiplist: - cmd_std = f" -std={v} " - self.assertIn(cmd_std, cmd) - try: - self.build() - except Exception: - print(f'{key!s} was {v!r}') - raise - self.wipe() - # Check that an invalid std option in CFLAGS/CPPFLAGS fails - # Needed because by default ICC ignores invalid options - cmd_std = '-std=FAIL' - if compiler.language == 'c': - env_flag_name = 'CFLAGS' - elif compiler.language == 'cpp': - env_flag_name = 'CXXFLAGS' - else: - raise NotImplementedError(f'Language {compiler.language} not defined.') - env = {} - env[env_flag_name] = cmd_std - with self.assertRaises((subprocess.CalledProcessError, mesonbuild.mesonlib.EnvironmentException), - msg='C compiler should have failed with -std=FAIL'): - self.init(testdir, override_envvars = env) - # ICC won't fail in the above because additional flags are needed to - # make unknown -std=... options errors. - self.build() - - def test_compiler_c_stds(self): - ''' - Test that C stds specified for this compiler can all be used. Can't be - an ordinary test because it requires passing options to meson. - ''' - testdir = os.path.join(self.common_test_dir, '1 trivial') - env = get_fake_env(testdir, self.builddir, self.prefix) - cc = detect_c_compiler(env, MachineChoice.HOST) - self._test_stds_impl(testdir, cc) - - def test_compiler_cpp_stds(self): - ''' - Test that C++ stds specified for this compiler can all be used. Can't - be an ordinary test because it requires passing options to meson. - ''' - testdir = os.path.join(self.common_test_dir, '2 cpp') - env = get_fake_env(testdir, self.builddir, self.prefix) - cpp = detect_cpp_compiler(env, MachineChoice.HOST) - self._test_stds_impl(testdir, cpp) - - def test_unity_subproj(self): - testdir = os.path.join(self.common_test_dir, '42 subproject') - self.init(testdir, extra_args='--unity=subprojects') - pdirs = glob(os.path.join(self.builddir, 'subprojects/sublib/simpletest*.p')) - self.assertEqual(len(pdirs), 1) - self.assertPathExists(os.path.join(pdirs[0], 'simpletest-unity0.c')) - sdirs = glob(os.path.join(self.builddir, 'subprojects/sublib/*sublib*.p')) - self.assertEqual(len(sdirs), 1) - self.assertPathExists(os.path.join(sdirs[0], 'sublib-unity0.c')) - self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c')) - self.build() - - def test_installed_modes(self): - ''' - Test that files installed by these tests have the correct permissions. - Can't be an ordinary test because our installed_files.txt is very basic. - ''' - # Test file modes - testdir = os.path.join(self.common_test_dir, '12 data') - self.init(testdir) - self.install() - - f = os.path.join(self.installdir, 'etc', 'etcfile.dat') - found_mode = stat.filemode(os.stat(f).st_mode) - want_mode = 'rw------T' - self.assertEqual(want_mode, found_mode[1:]) - - f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh') - statf = os.stat(f) - found_mode = stat.filemode(statf.st_mode) - want_mode = 'rwxr-sr-x' - self.assertEqual(want_mode, found_mode[1:]) - if os.getuid() == 0: - # The chown failed nonfatally if we're not root - self.assertEqual(0, statf.st_uid) - self.assertEqual(0, statf.st_gid) - - f = os.path.join(self.installdir, 'usr', 'share', 'progname', - 'fileobject_datafile.dat') - orig = os.path.join(testdir, 'fileobject_datafile.dat') - statf = os.stat(f) - statorig = os.stat(orig) - found_mode = stat.filemode(statf.st_mode) - orig_mode = stat.filemode(statorig.st_mode) - self.assertEqual(orig_mode[1:], found_mode[1:]) - self.assertEqual(os.getuid(), statf.st_uid) - if os.getuid() == 0: - # The chown failed nonfatally if we're not root - self.assertEqual(0, statf.st_gid) - - self.wipe() - # Test directory modes - testdir = os.path.join(self.common_test_dir, '59 install subdir') - self.init(testdir) - self.install() - - f = os.path.join(self.installdir, 'usr', 'share', 'sub1', 'second.dat') - statf = os.stat(f) - found_mode = stat.filemode(statf.st_mode) - want_mode = 'rwxr-x--t' - self.assertEqual(want_mode, found_mode[1:]) - if os.getuid() == 0: - # The chown failed nonfatally if we're not root - self.assertEqual(0, statf.st_uid) - - def test_installed_modes_extended(self): - ''' - Test that files are installed with correct permissions using install_mode. - ''' - testdir = os.path.join(self.common_test_dir, '190 install_mode') - self.init(testdir) - self.build() - self.install() - - for fsobj, want_mode in [ - ('bin', 'drwxr-x---'), - ('bin/runscript.sh', '-rwxr-sr-x'), - ('bin/trivialprog', '-rwxr-sr-x'), - ('include', 'drwxr-x---'), - ('include/config.h', '-rw-rwSr--'), - ('include/rootdir.h', '-r--r--r-T'), - ('lib', 'drwxr-x---'), - ('lib/libstat.a', '-rw---Sr--'), - ('share', 'drwxr-x---'), - ('share/man', 'drwxr-x---'), - ('share/man/man1', 'drwxr-x---'), - ('share/man/man1/foo.1', '-r--r--r-T'), - ('share/sub1', 'drwxr-x---'), - ('share/sub1/second.dat', '-rwxr-x--t'), - ('subdir', 'drwxr-x---'), - ('subdir/data.dat', '-rw-rwSr--'), - ]: - f = os.path.join(self.installdir, 'usr', *fsobj.split('/')) - found_mode = stat.filemode(os.stat(f).st_mode) - self.assertEqual(want_mode, found_mode, - msg=('Expected file %s to have mode %s but found %s instead.' % - (fsobj, want_mode, found_mode))) - # Ensure that introspect --installed works on all types of files - # FIXME: also verify the files list - self.introspect('--installed') - - def test_install_umask(self): - ''' - Test that files are installed with correct permissions using default - install umask of 022, regardless of the umask at time the worktree - was checked out or the build was executed. - ''' - # Copy source tree to a temporary directory and change permissions - # there to simulate a checkout with umask 002. - orig_testdir = os.path.join(self.unit_test_dir, '26 install umask') - # Create a new testdir under tmpdir. - tmpdir = os.path.realpath(tempfile.mkdtemp()) - self.addCleanup(windows_proof_rmtree, tmpdir) - testdir = os.path.join(tmpdir, '26 install umask') - # Copy the tree using shutil.copyfile, which will use the current umask - # instead of preserving permissions of the old tree. - save_umask = os.umask(0o002) - self.addCleanup(os.umask, save_umask) - shutil.copytree(orig_testdir, testdir, copy_function=shutil.copyfile) - # Preserve the executable status of subdir/sayhello though. - os.chmod(os.path.join(testdir, 'subdir', 'sayhello'), 0o775) - self.init(testdir) - # Run the build under a 027 umask now. - os.umask(0o027) - self.build() - # And keep umask 027 for the install step too. - self.install() - - for executable in [ - 'bin/prog', - 'share/subdir/sayhello', - ]: - f = os.path.join(self.installdir, 'usr', *executable.split('/')) - found_mode = stat.filemode(os.stat(f).st_mode) - want_mode = '-rwxr-xr-x' - self.assertEqual(want_mode, found_mode, - msg=('Expected file %s to have mode %s but found %s instead.' % - (executable, want_mode, found_mode))) - - for directory in [ - 'usr', - 'usr/bin', - 'usr/include', - 'usr/share', - 'usr/share/man', - 'usr/share/man/man1', - 'usr/share/subdir', - ]: - f = os.path.join(self.installdir, *directory.split('/')) - found_mode = stat.filemode(os.stat(f).st_mode) - want_mode = 'drwxr-xr-x' - self.assertEqual(want_mode, found_mode, - msg=('Expected directory %s to have mode %s but found %s instead.' % - (directory, want_mode, found_mode))) - - for datafile in [ - 'include/sample.h', - 'share/datafile.cat', - 'share/file.dat', - 'share/man/man1/prog.1', - 'share/subdir/datafile.dog', - ]: - f = os.path.join(self.installdir, 'usr', *datafile.split('/')) - found_mode = stat.filemode(os.stat(f).st_mode) - want_mode = '-rw-r--r--' - self.assertEqual(want_mode, found_mode, - msg=('Expected file %s to have mode %s but found %s instead.' % - (datafile, want_mode, found_mode))) - - def test_cpp_std_override(self): - testdir = os.path.join(self.unit_test_dir, '6 std override') - self.init(testdir) - compdb = self.get_compdb() - # Don't try to use -std=c++03 as a check for the - # presence of a compiler flag, as ICC does not - # support it. - for i in compdb: - if 'prog98' in i['file']: - c98_comp = i['command'] - if 'prog11' in i['file']: - c11_comp = i['command'] - if 'progp' in i['file']: - plain_comp = i['command'] - self.assertNotEqual(len(plain_comp), 0) - self.assertIn('-std=c++98', c98_comp) - self.assertNotIn('-std=c++11', c98_comp) - self.assertIn('-std=c++11', c11_comp) - self.assertNotIn('-std=c++98', c11_comp) - self.assertNotIn('-std=c++98', plain_comp) - self.assertNotIn('-std=c++11', plain_comp) - # Now werror - self.assertIn('-Werror', plain_comp) - self.assertNotIn('-Werror', c98_comp) - - def test_run_installed(self): - if is_cygwin() or is_osx(): - raise unittest.SkipTest('LD_LIBRARY_PATH and RPATH not applicable') - - testdir = os.path.join(self.unit_test_dir, '7 run installed') - self.init(testdir) - self.build() - self.install() - installed_exe = os.path.join(self.installdir, 'usr/bin/prog') - installed_libdir = os.path.join(self.installdir, 'usr/foo') - installed_lib = os.path.join(installed_libdir, 'libfoo.so') - self.assertTrue(os.path.isfile(installed_exe)) - self.assertTrue(os.path.isdir(installed_libdir)) - self.assertTrue(os.path.isfile(installed_lib)) - # Must fail when run without LD_LIBRARY_PATH to ensure that - # rpath has been properly stripped rather than pointing to the builddir. - self.assertNotEqual(subprocess.call(installed_exe, stderr=subprocess.DEVNULL), 0) - # When LD_LIBRARY_PATH is set it should start working. - # For some reason setting LD_LIBRARY_PATH in os.environ fails - # when all tests are run (but works when only this test is run), - # but doing this explicitly works. - env = os.environ.copy() - env['LD_LIBRARY_PATH'] = ':'.join([installed_libdir, env.get('LD_LIBRARY_PATH', '')]) - self.assertEqual(subprocess.call(installed_exe, env=env), 0) - # Ensure that introspect --installed works - installed = self.introspect('--installed') - for v in installed.values(): - self.assertTrue('prog' in v or 'foo' in v) - - @skipIfNoPkgconfig - def test_order_of_l_arguments(self): - testdir = os.path.join(self.unit_test_dir, '8 -L -l order') - self.init(testdir, override_envvars={'PKG_CONFIG_PATH': testdir}) - # NOTE: .pc file has -Lfoo -lfoo -Lbar -lbar but pkg-config reorders - # the flags before returning them to -Lfoo -Lbar -lfoo -lbar - # but pkgconf seems to not do that. Sigh. Support both. - expected_order = [('-L/me/first', '-lfoo1'), - ('-L/me/second', '-lfoo2'), - ('-L/me/first', '-L/me/second'), - ('-lfoo1', '-lfoo2'), - ('-L/me/second', '-L/me/third'), - ('-L/me/third', '-L/me/fourth',), - ('-L/me/third', '-lfoo3'), - ('-L/me/fourth', '-lfoo4'), - ('-lfoo3', '-lfoo4'), - ] - with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as ifile: - for line in ifile: - if expected_order[0][0] in line: - for first, second in expected_order: - self.assertLess(line.index(first), line.index(second)) - return - raise RuntimeError('Linker entries not found in the Ninja file.') - - def test_introspect_dependencies(self): - ''' - Tests that mesonintrospect --dependencies returns expected output. - ''' - testdir = os.path.join(self.framework_test_dir, '7 gnome') - self.init(testdir) - glib_found = False - gobject_found = False - deps = self.introspect('--dependencies') - self.assertIsInstance(deps, list) - for dep in deps: - self.assertIsInstance(dep, dict) - self.assertIn('name', dep) - self.assertIn('compile_args', dep) - self.assertIn('link_args', dep) - if dep['name'] == 'glib-2.0': - glib_found = True - elif dep['name'] == 'gobject-2.0': - gobject_found = True - self.assertTrue(glib_found) - self.assertTrue(gobject_found) - if subprocess.call(['pkg-config', '--exists', 'glib-2.0 >= 2.56.2']) != 0: - raise unittest.SkipTest('glib >= 2.56.2 needed for the rest') - targets = self.introspect('--targets') - docbook_target = None - for t in targets: - if t['name'] == 'generated-gdbus-docbook': - docbook_target = t - break - self.assertIsInstance(docbook_target, dict) - self.assertEqual(os.path.basename(t['filename'][0]), 'generated-gdbus-doc-' + os.path.basename(t['target_sources'][0]['sources'][0])) - - def test_introspect_installed(self): - testdir = os.path.join(self.linuxlike_test_dir, '7 library versions') - self.init(testdir) - - install = self.introspect('--installed') - install = {os.path.basename(k): v for k, v in install.items()} - print(install) - if is_osx(): - the_truth = { - 'libmodule.dylib': '/usr/lib/libmodule.dylib', - 'libnoversion.dylib': '/usr/lib/libnoversion.dylib', - 'libonlysoversion.5.dylib': '/usr/lib/libonlysoversion.5.dylib', - 'libonlysoversion.dylib': '/usr/lib/libonlysoversion.dylib', - 'libonlyversion.1.dylib': '/usr/lib/libonlyversion.1.dylib', - 'libonlyversion.dylib': '/usr/lib/libonlyversion.dylib', - 'libsome.0.dylib': '/usr/lib/libsome.0.dylib', - 'libsome.dylib': '/usr/lib/libsome.dylib', - } - the_truth_2 = {'/usr/lib/libsome.dylib', - '/usr/lib/libsome.0.dylib', - } - else: - the_truth = { - 'libmodule.so': '/usr/lib/libmodule.so', - 'libnoversion.so': '/usr/lib/libnoversion.so', - 'libonlysoversion.so': '/usr/lib/libonlysoversion.so', - 'libonlysoversion.so.5': '/usr/lib/libonlysoversion.so.5', - 'libonlyversion.so': '/usr/lib/libonlyversion.so', - 'libonlyversion.so.1': '/usr/lib/libonlyversion.so.1', - 'libonlyversion.so.1.4.5': '/usr/lib/libonlyversion.so.1.4.5', - 'libsome.so': '/usr/lib/libsome.so', - 'libsome.so.0': '/usr/lib/libsome.so.0', - 'libsome.so.1.2.3': '/usr/lib/libsome.so.1.2.3', - } - the_truth_2 = {'/usr/lib/libsome.so', - '/usr/lib/libsome.so.0', - '/usr/lib/libsome.so.1.2.3'} - self.assertDictEqual(install, the_truth) - - targets = self.introspect('--targets') - for t in targets: - if t['name'] != 'some': - continue - self.assertSetEqual(the_truth_2, set(t['install_filename'])) - - def test_build_rpath(self): - if is_cygwin(): - raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH') - testdir = os.path.join(self.unit_test_dir, '10 build_rpath') - self.init(testdir) - self.build() - build_rpath = get_rpath(os.path.join(self.builddir, 'prog')) - self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar') - build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx')) - self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar') - self.install() - install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog')) - self.assertEqual(install_rpath, '/baz') - install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx')) - self.assertEqual(install_rpath, 'baz') - - @skipIfNoPkgconfig - def test_build_rpath_pkgconfig(self): - ''' - Test that current build artefacts (libs) are found first on the rpath, - manually specified rpath comes second and additional rpath elements (from - pkg-config files) come last - ''' - if is_cygwin(): - raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH') - testdir = os.path.join(self.unit_test_dir, '90 pkgconfig build rpath order') - self.init(testdir, override_envvars={'PKG_CONFIG_PATH': testdir}) - self.build() - build_rpath = get_rpath(os.path.join(self.builddir, 'prog')) - self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar:/foo/dummy') - build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx')) - self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar:/foo/dummy') - self.install() - install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog')) - self.assertEqual(install_rpath, '/baz:/foo/dummy') - install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx')) - self.assertEqual(install_rpath, 'baz:/foo/dummy') - - def test_global_rpath(self): - if is_cygwin(): - raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH') - if is_osx(): - raise unittest.SkipTest('Global RPATHs via LDFLAGS not yet supported on MacOS (does anybody need it?)') - - testdir = os.path.join(self.unit_test_dir, '80 global-rpath') - oldinstalldir = self.installdir - - # Build and install an external library without DESTDIR. - # The external library generates a .pc file without an rpath. - yonder_dir = os.path.join(testdir, 'yonder') - yonder_prefix = os.path.join(oldinstalldir, 'yonder') - yonder_libdir = os.path.join(yonder_prefix, self.libdir) - self.prefix = yonder_prefix - self.installdir = yonder_prefix - self.init(yonder_dir) - self.build() - self.install(use_destdir=False) - - # Since rpath has multiple valid formats we need to - # test that they are all properly used. - rpath_formats = [ - ('-Wl,-rpath=', False), - ('-Wl,-rpath,', False), - ('-Wl,--just-symbols=', True), - ('-Wl,--just-symbols,', True), - ('-Wl,-R', False), - ('-Wl,-R,', False) - ] - for rpath_format, exception in rpath_formats: - # Build an app that uses that installed library. - # Supply the rpath to the installed library via LDFLAGS - # (as systems like buildroot and guix are wont to do) - # and verify install preserves that rpath. - self.new_builddir() - env = {'LDFLAGS': rpath_format + yonder_libdir, - 'PKG_CONFIG_PATH': os.path.join(yonder_libdir, 'pkgconfig')} - if exception: - with self.assertRaises(subprocess.CalledProcessError): - self.init(testdir, override_envvars=env) - continue - self.init(testdir, override_envvars=env) - self.build() - self.install(use_destdir=False) - got_rpath = get_rpath(os.path.join(yonder_prefix, 'bin/rpathified')) - self.assertEqual(got_rpath, yonder_libdir, rpath_format) - - @skip_if_not_base_option('b_sanitize') - def test_pch_with_address_sanitizer(self): - if is_cygwin(): - raise unittest.SkipTest('asan not available on Cygwin') - if is_openbsd(): - raise unittest.SkipTest('-fsanitize=address is not supported on OpenBSD') - - testdir = os.path.join(self.common_test_dir, '13 pch') - self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false']) - self.build() - compdb = self.get_compdb() - for i in compdb: - self.assertIn("-fsanitize=address", i["command"]) - - def test_cross_find_program(self): - testdir = os.path.join(self.unit_test_dir, '11 cross prog') - crossfile = tempfile.NamedTemporaryFile(mode='w') - print(os.path.join(testdir, 'some_cross_tool.py')) - - tool_path = os.path.join(testdir, 'some_cross_tool.py') - - crossfile.write(textwrap.dedent(f'''\ - [binaries] - c = '{shutil.which('gcc' if is_sunos() else 'cc')}' - ar = '{shutil.which('ar')}' - strip = '{shutil.which('strip')}' - sometool.py = ['{tool_path}'] - someothertool.py = '{tool_path}' - - [properties] - - [host_machine] - system = 'linux' - cpu_family = 'arm' - cpu = 'armv7' # Not sure if correct. - endian = 'little' - ''')) - crossfile.flush() - self.meson_cross_file = crossfile.name - self.init(testdir) - - def test_reconfigure(self): - testdir = os.path.join(self.unit_test_dir, '13 reconfigure') - self.init(testdir, extra_args=['-Db_coverage=true'], default_args=False) - self.build('reconfigure') - - def test_vala_generated_source_buildir_inside_source_tree(self): - ''' - Test that valac outputs generated C files in the expected location when - the builddir is a subdir of the source tree. - ''' - if not shutil.which('valac'): - raise unittest.SkipTest('valac not installed.') - - testdir = os.path.join(self.vala_test_dir, '8 generated sources') - newdir = os.path.join(self.builddir, 'srctree') - shutil.copytree(testdir, newdir) - testdir = newdir - # New builddir - builddir = os.path.join(testdir, 'subdir/_build') - os.makedirs(builddir, exist_ok=True) - self.change_builddir(builddir) - self.init(testdir) - self.build() - - def test_old_gnome_module_codepaths(self): - ''' - A lot of code in the GNOME module is conditional on the version of the - glib tools that are installed, and breakages in the old code can slip - by once the CI has a newer glib version. So we force the GNOME module - to pretend that it's running on an ancient glib so the fallback code is - also tested. - ''' - testdir = os.path.join(self.framework_test_dir, '7 gnome') - mesonbuild.modules.gnome.native_glib_version = '2.20' - env = {'MESON_UNIT_TEST_PRETEND_GLIB_OLD': "1"} - try: - self.init(testdir, - inprocess=True, - override_envvars=env) - self.build(override_envvars=env) - finally: - mesonbuild.modules.gnome.native_glib_version = None - - @skipIfNoPkgconfig - def test_pkgconfig_usage(self): - testdir1 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependency') - testdir2 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependee') - if subprocess.call(['pkg-config', '--cflags', 'glib-2.0'], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) != 0: - raise unittest.SkipTest('Glib 2.0 dependency not available.') - with tempfile.TemporaryDirectory() as tempdirname: - self.init(testdir1, extra_args=['--prefix=' + tempdirname, '--libdir=lib'], default_args=False) - self.install(use_destdir=False) - shutil.rmtree(self.builddir) - os.mkdir(self.builddir) - pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') - self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'libpkgdep.pc'))) - lib_dir = os.path.join(tempdirname, 'lib') - myenv = os.environ.copy() - myenv['PKG_CONFIG_PATH'] = pkg_dir - # Private internal libraries must not leak out. - pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep'], env=myenv) - self.assertFalse(b'libpkgdep-int' in pkg_out, 'Internal library leaked out.') - # Dependencies must not leak to cflags when building only a shared library. - pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep'], env=myenv) - self.assertFalse(b'glib' in pkg_out, 'Internal dependency leaked to headers.') - # Test that the result is usable. - self.init(testdir2, override_envvars=myenv) - self.build(override_envvars=myenv) - myenv = os.environ.copy() - myenv['LD_LIBRARY_PATH'] = ':'.join([lib_dir, myenv.get('LD_LIBRARY_PATH', '')]) - if is_cygwin(): - bin_dir = os.path.join(tempdirname, 'bin') - myenv['PATH'] = bin_dir + os.pathsep + myenv['PATH'] - self.assertTrue(os.path.isdir(lib_dir)) - test_exe = os.path.join(self.builddir, 'pkguser') - self.assertTrue(os.path.isfile(test_exe)) - subprocess.check_call(test_exe, env=myenv) - - @skipIfNoPkgconfig - def test_pkgconfig_relative_paths(self): - testdir = os.path.join(self.unit_test_dir, '62 pkgconfig relative paths') - pkg_dir = os.path.join(testdir, 'pkgconfig') - self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'librelativepath.pc'))) - - env = get_fake_env(testdir, self.builddir, self.prefix) - env.coredata.set_options({OptionKey('pkg_config_path'): pkg_dir}, subproject='') - kwargs = {'required': True, 'silent': True} - relative_path_dep = PkgConfigDependency('librelativepath', env, kwargs) - self.assertTrue(relative_path_dep.found()) - - # Ensure link_args are properly quoted - libpath = Path(self.builddir) / '../relativepath/lib' - link_args = ['-L' + libpath.as_posix(), '-lrelativepath'] - self.assertEqual(relative_path_dep.get_link_args(), link_args) - - @skipIfNoPkgconfig - def test_pkgconfig_duplicate_path_entries(self): - testdir = os.path.join(self.unit_test_dir, '111 pkgconfig duplicate path entries') - pkg_dir = os.path.join(testdir, 'pkgconfig') - - env = get_fake_env(testdir, self.builddir, self.prefix) - env.coredata.set_options({OptionKey('pkg_config_path'): pkg_dir}, subproject='') - - PkgConfigDependency.setup_env({}, env, MachineChoice.HOST, pkg_dir) - pkg_config_path = env.coredata.options[OptionKey('pkg_config_path')].value - self.assertTrue(len(pkg_config_path) == 1) - - @skipIfNoPkgconfig - def test_pkgconfig_internal_libraries(self): - ''' - ''' - with tempfile.TemporaryDirectory() as tempdirname: - # build library - testdirbase = os.path.join(self.unit_test_dir, '32 pkgconfig use libraries') - testdirlib = os.path.join(testdirbase, 'lib') - self.init(testdirlib, extra_args=['--prefix=' + tempdirname, - '--libdir=lib', - '--default-library=static'], default_args=False) - self.build() - self.install(use_destdir=False) - - # build user of library - pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') - self.new_builddir() - self.init(os.path.join(testdirbase, 'app'), - override_envvars={'PKG_CONFIG_PATH': pkg_dir}) - self.build() - - @skipIfNoPkgconfig - def test_static_archive_stripping(self): - ''' - Check that Meson produces valid static archives with --strip enabled - ''' - with tempfile.TemporaryDirectory() as tempdirname: - testdirbase = os.path.join(self.unit_test_dir, '66 static archive stripping') - - # build lib - self.new_builddir() - testdirlib = os.path.join(testdirbase, 'lib') - testlibprefix = os.path.join(tempdirname, 'libprefix') - self.init(testdirlib, extra_args=['--prefix=' + testlibprefix, - '--libdir=lib', - '--default-library=static', - '--buildtype=debug', - '--strip'], default_args=False) - self.build() - self.install(use_destdir=False) - - # build executable (uses lib, fails if static archive has been stripped incorrectly) - pkg_dir = os.path.join(testlibprefix, 'lib/pkgconfig') - self.new_builddir() - self.init(os.path.join(testdirbase, 'app'), - override_envvars={'PKG_CONFIG_PATH': pkg_dir}) - self.build() - - @skipIfNoPkgconfig - def test_pkgconfig_formatting(self): - testdir = os.path.join(self.unit_test_dir, '38 pkgconfig format') - self.init(testdir) - myenv = os.environ.copy() - myenv['PKG_CONFIG_PATH'] = self.privatedir - stdo = subprocess.check_output(['pkg-config', '--libs-only-l', 'libsomething'], env=myenv) - deps = [b'-lgobject-2.0', b'-lgio-2.0', b'-lglib-2.0', b'-lsomething'] - if is_windows() or is_cygwin() or is_osx() or is_openbsd(): - # On Windows, libintl is a separate library - deps.append(b'-lintl') - self.assertEqual(set(deps), set(stdo.split())) - - @skipIfNoPkgconfig - @skip_if_not_language('cs') - def test_pkgconfig_csharp_library(self): - testdir = os.path.join(self.unit_test_dir, '50 pkgconfig csharp library') - self.init(testdir) - myenv = os.environ.copy() - myenv['PKG_CONFIG_PATH'] = self.privatedir - stdo = subprocess.check_output(['pkg-config', '--libs', 'libsomething'], env=myenv) - - self.assertEqual("-r/usr/lib/libsomething.dll", str(stdo.decode('ascii')).strip()) - - @skipIfNoPkgconfig - def test_pkgconfig_link_order(self): - ''' - Test that libraries are listed before their dependencies. - ''' - testdir = os.path.join(self.unit_test_dir, '53 pkgconfig static link order') - self.init(testdir) - myenv = os.environ.copy() - myenv['PKG_CONFIG_PATH'] = self.privatedir - stdo = subprocess.check_output(['pkg-config', '--libs', 'libsomething'], env=myenv) - deps = stdo.split() - self.assertTrue(deps.index(b'-lsomething') < deps.index(b'-ldependency')) - - def test_deterministic_dep_order(self): - ''' - Test that the dependencies are always listed in a deterministic order. - ''' - testdir = os.path.join(self.unit_test_dir, '43 dep order') - self.init(testdir) - with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as bfile: - for line in bfile: - if 'build myexe:' in line or 'build myexe.exe:' in line: - self.assertIn('liblib1.a liblib2.a', line) - return - raise RuntimeError('Could not find the build rule') - - def test_deterministic_rpath_order(self): - ''' - Test that the rpaths are always listed in a deterministic order. - ''' - if is_cygwin(): - raise unittest.SkipTest('rpath are not used on Cygwin') - testdir = os.path.join(self.unit_test_dir, '42 rpath order') - self.init(testdir) - if is_osx(): - rpathre = re.compile(r'-rpath,.*/subprojects/sub1.*-rpath,.*/subprojects/sub2') - else: - rpathre = re.compile(r'-rpath,\$\$ORIGIN/subprojects/sub1:\$\$ORIGIN/subprojects/sub2') - with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as bfile: - for line in bfile: - if '-rpath' in line: - self.assertRegex(line, rpathre) - return - raise RuntimeError('Could not find the rpath') - - def test_override_with_exe_dep(self): - ''' - Test that we produce the correct dependencies when a program is overridden with an executable. - ''' - testdir = os.path.join(self.src_root, 'test cases', 'native', '9 override with exe') - self.init(testdir) - with open(os.path.join(self.builddir, 'build.ninja'), encoding='utf-8') as bfile: - for line in bfile: - if 'main1.c:' in line or 'main2.c:' in line: - self.assertIn('| subprojects/sub/foobar', line) - - @skipIfNoPkgconfig - def test_usage_external_library(self): - ''' - Test that uninstalled usage of an external library (from the system or - PkgConfigDependency) works. On macOS, this workflow works out of the - box. On Linux, BSDs, Windows, etc, you need to set extra arguments such - as LD_LIBRARY_PATH, etc, so this test is skipped. - - The system library is found with cc.find_library() and pkg-config deps. - ''' - oldprefix = self.prefix - # Install external library so we can find it - testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'external library') - # install into installdir without using DESTDIR - installdir = self.installdir - self.prefix = installdir - self.init(testdir) - self.prefix = oldprefix - self.build() - self.install(use_destdir=False) - ## New builddir for the consumer - self.new_builddir() - env = {'LIBRARY_PATH': os.path.join(installdir, self.libdir), - 'PKG_CONFIG_PATH': os.path.join(installdir, self.libdir, 'pkgconfig')} - testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'built library') - # install into installdir without using DESTDIR - self.prefix = self.installdir - self.init(testdir, override_envvars=env) - self.prefix = oldprefix - self.build(override_envvars=env) - # test uninstalled - self.run_tests(override_envvars=env) - if not (is_osx() or is_linux()): - return - # test running after installation - self.install(use_destdir=False) - prog = os.path.join(self.installdir, 'bin', 'prog') - self._run([prog]) - if not is_osx(): - # Rest of the workflow only works on macOS - return - out = self._run(['otool', '-L', prog]) - self.assertNotIn('@rpath', out) - ## New builddir for testing that DESTDIR is not added to install_name - self.new_builddir() - # install into installdir with DESTDIR - self.init(testdir, override_envvars=env) - self.build(override_envvars=env) - # test running after installation - self.install(override_envvars=env) - prog = self.installdir + os.path.join(self.prefix, 'bin', 'prog') - lib = self.installdir + os.path.join(self.prefix, 'lib', 'libbar_built.dylib') - for f in prog, lib: - out = self._run(['otool', '-L', f]) - # Ensure that the otool output does not contain self.installdir - self.assertNotRegex(out, self.installdir + '.*dylib ') - - @skipIfNoPkgconfig - def test_usage_pkgconfig_prefixes(self): - ''' - Build and install two external libraries, to different prefixes, - then build and install a client program that finds them via pkgconfig, - and verify the installed client program runs. - ''' - oldinstalldir = self.installdir - - # Build and install both external libraries without DESTDIR - val1dir = os.path.join(self.unit_test_dir, '75 pkgconfig prefixes', 'val1') - val1prefix = os.path.join(oldinstalldir, 'val1') - self.prefix = val1prefix - self.installdir = val1prefix - self.init(val1dir) - self.build() - self.install(use_destdir=False) - self.new_builddir() - - env1 = {} - env1['PKG_CONFIG_PATH'] = os.path.join(val1prefix, self.libdir, 'pkgconfig') - val2dir = os.path.join(self.unit_test_dir, '75 pkgconfig prefixes', 'val2') - val2prefix = os.path.join(oldinstalldir, 'val2') - self.prefix = val2prefix - self.installdir = val2prefix - self.init(val2dir, override_envvars=env1) - self.build() - self.install(use_destdir=False) - self.new_builddir() - - # Build, install, and run the client program - env2 = {} - env2['PKG_CONFIG_PATH'] = os.path.join(val2prefix, self.libdir, 'pkgconfig') - testdir = os.path.join(self.unit_test_dir, '75 pkgconfig prefixes', 'client') - testprefix = os.path.join(oldinstalldir, 'client') - self.prefix = testprefix - self.installdir = testprefix - self.init(testdir, override_envvars=env2) - self.build() - self.install(use_destdir=False) - prog = os.path.join(self.installdir, 'bin', 'client') - env3 = {} - if is_cygwin(): - env3['PATH'] = os.path.join(val1prefix, 'bin') + \ - os.pathsep + \ - os.path.join(val2prefix, 'bin') + \ - os.pathsep + os.environ['PATH'] - out = self._run([prog], override_envvars=env3).strip() - # Expected output is val1 + val2 = 3 - self.assertEqual(out, '3') - - def install_subdir_invalid_symlinks(self, testdir, subdir_path): - ''' - Test that installation of broken symlinks works fine. - https://github.com/mesonbuild/meson/issues/3914 - ''' - testdir = os.path.join(self.common_test_dir, testdir) - subdir = os.path.join(testdir, subdir_path) - with chdir(subdir): - # Can't distribute broken symlinks in the source tree because it breaks - # the creation of zipapps. Create it dynamically and run the test by - # hand. - src = '../../nonexistent.txt' - os.symlink(src, 'invalid-symlink.txt') - try: - self.init(testdir) - self.build() - self.install() - install_path = subdir_path.split(os.path.sep)[-1] - link = os.path.join(self.installdir, 'usr', 'share', install_path, 'invalid-symlink.txt') - self.assertTrue(os.path.islink(link), msg=link) - self.assertEqual(src, os.readlink(link)) - self.assertFalse(os.path.isfile(link), msg=link) - finally: - os.remove(os.path.join(subdir, 'invalid-symlink.txt')) - - def test_install_subdir_symlinks(self): - self.install_subdir_invalid_symlinks('59 install subdir', os.path.join('sub', 'sub1')) - - def test_install_subdir_symlinks_with_default_umask(self): - self.install_subdir_invalid_symlinks('190 install_mode', 'sub2') - - def test_install_subdir_symlinks_with_default_umask_and_mode(self): - self.install_subdir_invalid_symlinks('190 install_mode', 'sub1') - - @skipIfNoPkgconfigDep('gmodule-2.0') - def test_ldflag_dedup(self): - testdir = os.path.join(self.unit_test_dir, '52 ldflagdedup') - if is_cygwin() or is_osx(): - raise unittest.SkipTest('Not applicable on Cygwin or OSX.') - env = get_fake_env() - cc = detect_c_compiler(env, MachineChoice.HOST) - linker = cc.linker - if not linker.export_dynamic_args(env): - raise unittest.SkipTest('Not applicable for linkers without --export-dynamic') - self.init(testdir) - build_ninja = os.path.join(self.builddir, 'build.ninja') - max_count = 0 - search_term = '-Wl,--export-dynamic' - with open(build_ninja, encoding='utf-8') as f: - for line in f: - max_count = max(max_count, line.count(search_term)) - self.assertEqual(max_count, 1, 'Export dynamic incorrectly deduplicated.') - - def test_compiler_libs_static_dedup(self): - testdir = os.path.join(self.unit_test_dir, '56 dedup compiler libs') - self.init(testdir) - build_ninja = os.path.join(self.builddir, 'build.ninja') - with open(build_ninja, encoding='utf-8') as f: - lines = f.readlines() - for lib in ('-ldl', '-lm', '-lc', '-lrt'): - for line in lines: - if lib not in line: - continue - # Assert that - self.assertEqual(len(line.split(lib)), 2, msg=(lib, line)) - - @skipIfNoPkgconfig - def test_noncross_options(self): - # C_std defined in project options must be in effect also when native compiling. - testdir = os.path.join(self.unit_test_dir, '51 noncross options') - self.init(testdir, extra_args=['-Dpkg_config_path=' + testdir]) - compdb = self.get_compdb() - self.assertEqual(len(compdb), 2) - self.assertRegex(compdb[0]['command'], '-std=c99') - self.assertRegex(compdb[1]['command'], '-std=c99') - self.build() - - def test_identity_cross(self): - testdir = os.path.join(self.unit_test_dir, '61 identity cross') - - nativefile = tempfile.NamedTemporaryFile(mode='w') - nativefile.write(textwrap.dedent('''\ - [binaries] - c = ['{}'] - '''.format(os.path.join(testdir, 'build_wrapper.py')))) - nativefile.flush() - self.meson_native_file = nativefile.name - - crossfile = tempfile.NamedTemporaryFile(mode='w') - crossfile.write(textwrap.dedent('''\ - [binaries] - c = ['{}'] - '''.format(os.path.join(testdir, 'host_wrapper.py')))) - crossfile.flush() - self.meson_cross_file = crossfile.name - - # TODO should someday be explicit about build platform only here - self.init(testdir) - - def test_identity_cross_env(self): - testdir = os.path.join(self.unit_test_dir, '61 identity cross') - env = { - 'CC_FOR_BUILD': '"' + os.path.join(testdir, 'build_wrapper.py') + '"', - } - crossfile = tempfile.NamedTemporaryFile(mode='w') - crossfile.write(textwrap.dedent('''\ - [binaries] - c = ['{}'] - '''.format(os.path.join(testdir, 'host_wrapper.py')))) - crossfile.flush() - self.meson_cross_file = crossfile.name - # TODO should someday be explicit about build platform only here - self.init(testdir, override_envvars=env) - - @skipIfNoPkgconfig - def test_static_link(self): - if is_cygwin(): - raise unittest.SkipTest("Cygwin doesn't support LD_LIBRARY_PATH.") - - # Build some libraries and install them - testdir = os.path.join(self.unit_test_dir, '67 static link/lib') - libdir = os.path.join(self.installdir, self.libdir) - oldprefix = self.prefix - self.prefix = self.installdir - self.init(testdir) - self.install(use_destdir=False) - - # Test that installed libraries works - self.new_builddir() - self.prefix = oldprefix - meson_args = [f'-Dc_link_args=-L{libdir}', - '--fatal-meson-warnings'] - testdir = os.path.join(self.unit_test_dir, '67 static link') - env = {'PKG_CONFIG_LIBDIR': os.path.join(libdir, 'pkgconfig')} - self.init(testdir, extra_args=meson_args, override_envvars=env) - self.build() - self.run_tests() - - def _check_ld(self, check: str, name: str, lang: str, expected: str) -> None: - if is_sunos(): - raise unittest.SkipTest('Solaris currently cannot override the linker.') - if not shutil.which(check): - raise unittest.SkipTest(f'Could not find {check}.') - envvars = [mesonbuild.envconfig.ENV_VAR_PROG_MAP[f'{lang}_ld']] - - # Also test a deprecated variable if there is one. - if f'{lang}_ld' in mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP: - envvars.append( - mesonbuild.envconfig.DEPRECATED_ENV_PROG_MAP[f'{lang}_ld']) - - for envvar in envvars: - with mock.patch.dict(os.environ, {envvar: name}): - env = get_fake_env() - comp = compiler_from_language(env, lang, MachineChoice.HOST) - if isinstance(comp, (mesonbuild.compilers.AppleClangCCompiler, - mesonbuild.compilers.AppleClangCPPCompiler, - mesonbuild.compilers.AppleClangObjCCompiler, - mesonbuild.compilers.AppleClangObjCPPCompiler)): - raise unittest.SkipTest('AppleClang is currently only supported with ld64') - if lang != 'rust' and comp.use_linker_args('bfd') == []: - raise unittest.SkipTest( - f'Compiler {comp.id} does not support using alternative linkers') - self.assertEqual(comp.linker.id, expected) - - def test_ld_environment_variable_bfd(self): - self._check_ld('ld.bfd', 'bfd', 'c', 'ld.bfd') - - def test_ld_environment_variable_gold(self): - self._check_ld('ld.gold', 'gold', 'c', 'ld.gold') - - def test_ld_environment_variable_lld(self): - self._check_ld('ld.lld', 'lld', 'c', 'ld.lld') - - @skip_if_not_language('rust') - @skipIfNoExecutable('ld.gold') # need an additional check here because _check_ld checks for gcc - def test_ld_environment_variable_rust(self): - self._check_ld('gcc', 'gcc -fuse-ld=gold', 'rust', 'ld.gold') - - def test_ld_environment_variable_cpp(self): - self._check_ld('ld.gold', 'gold', 'cpp', 'ld.gold') - - @skip_if_not_language('objc') - def test_ld_environment_variable_objc(self): - self._check_ld('ld.gold', 'gold', 'objc', 'ld.gold') - - @skip_if_not_language('objcpp') - def test_ld_environment_variable_objcpp(self): - self._check_ld('ld.gold', 'gold', 'objcpp', 'ld.gold') - - @skip_if_not_language('fortran') - def test_ld_environment_variable_fortran(self): - self._check_ld('ld.gold', 'gold', 'fortran', 'ld.gold') - - @skip_if_not_language('d') - def test_ld_environment_variable_d(self): - # At least for me, ldc defaults to gold, and gdc defaults to bfd, so - # let's pick lld, which isn't the default for either (currently) - if is_osx(): - expected = 'ld64' - else: - expected = 'ld.lld' - self._check_ld('ld.lld', 'lld', 'd', expected) - - def compute_sha256(self, filename): - with open(filename, 'rb') as f: - return hashlib.sha256(f.read()).hexdigest() - - def test_wrap_with_file_url(self): - testdir = os.path.join(self.unit_test_dir, '73 wrap file url') - source_filename = os.path.join(testdir, 'subprojects', 'foo.tar.xz') - patch_filename = os.path.join(testdir, 'subprojects', 'foo-patch.tar.xz') - wrap_filename = os.path.join(testdir, 'subprojects', 'foo.wrap') - source_hash = self.compute_sha256(source_filename) - patch_hash = self.compute_sha256(patch_filename) - wrap = textwrap.dedent("""\ - [wrap-file] - directory = foo - - source_url = http://server.invalid/foo - source_fallback_url = file://{} - source_filename = foo.tar.xz - source_hash = {} - - patch_url = http://server.invalid/foo - patch_fallback_url = file://{} - patch_filename = foo-patch.tar.xz - patch_hash = {} - """.format(source_filename, source_hash, patch_filename, patch_hash)) - with open(wrap_filename, 'w', encoding='utf-8') as f: - f.write(wrap) - self.init(testdir) - self.build() - self.run_tests() - - windows_proof_rmtree(os.path.join(testdir, 'subprojects', 'packagecache')) - windows_proof_rmtree(os.path.join(testdir, 'subprojects', 'foo')) - os.unlink(wrap_filename) - - def test_no_rpath_for_static(self): - testdir = os.path.join(self.common_test_dir, '5 linkstatic') - self.init(testdir) - self.build() - build_rpath = get_rpath(os.path.join(self.builddir, 'prog')) - self.assertIsNone(build_rpath) - - def test_lookup_system_after_broken_fallback(self): - # Just to generate libfoo.pc so we can test system dependency lookup. - testdir = os.path.join(self.common_test_dir, '44 pkgconfig-gen') - self.init(testdir) - privatedir = self.privatedir - - # Write test project where the first dependency() returns not-found - # because 'broken' subproject does not exit, but that should not prevent - # the 2nd dependency() to lookup on system. - self.new_builddir() - with tempfile.TemporaryDirectory() as d: - with open(os.path.join(d, 'meson.build'), 'w', encoding='utf-8') as f: - f.write(textwrap.dedent('''\ - project('test') - dependency('notfound', fallback: 'broken', required: false) - dependency('libfoo', fallback: 'broken', required: true) - ''')) - self.init(d, override_envvars={'PKG_CONFIG_LIBDIR': privatedir}) - - def test_as_link_whole(self): - testdir = os.path.join(self.unit_test_dir, '77 as link whole') - self.init(testdir) - with open(os.path.join(self.privatedir, 'bar1.pc'), encoding='utf-8') as f: - content = f.read() - self.assertIn('-lfoo', content) - with open(os.path.join(self.privatedir, 'bar2.pc'), encoding='utf-8') as f: - content = f.read() - self.assertNotIn('-lfoo', content) - - def test_prelinking(self): - # Prelinking currently only works on recently new GNU toolchains. - # Skip everything else. When support for other toolchains is added, - # remove limitations as necessary. - if is_osx(): - raise unittest.SkipTest('Prelinking not supported on Darwin.') - if 'clang' in os.environ.get('CC', 'dummy'): - raise unittest.SkipTest('Prelinking not supported with Clang.') - gccver = subprocess.check_output(['cc', '--version']) - if b'7.5.0' in gccver: - raise unittest.SkipTest('GCC on Bionic is too old to be supported.') - testdir = os.path.join(self.unit_test_dir, '87 prelinking') - self.init(testdir) - self.build() - outlib = os.path.join(self.builddir, 'libprelinked.a') - ar = shutil.which('ar') - self.assertTrue(os.path.exists(outlib)) - self.assertTrue(ar is not None) - p = subprocess.run([ar, 't', outlib], - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - universal_newlines=True, timeout=1) - obj_files = p.stdout.strip().split('\n') - self.assertEqual(len(obj_files), 1) - self.assertTrue(obj_files[0].endswith('-prelink.o')) - -class BaseLinuxCrossTests(BasePlatformTests): - # Don't pass --libdir when cross-compiling. We have tests that - # check whether meson auto-detects it correctly. - libdir = None - - -def should_run_cross_arm_tests(): - return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm') - -@unittest.skipUnless(not is_windows() and should_run_cross_arm_tests(), "requires ability to cross compile to ARM") -class LinuxCrossArmTests(BaseLinuxCrossTests): - ''' - Tests that cross-compilation to Linux/ARM works - ''' - - def setUp(self): - super().setUp() - src_root = os.path.dirname(__file__) - self.meson_cross_file = os.path.join(src_root, 'cross', 'ubuntu-armhf.txt') - - def test_cflags_cross_environment_pollution(self): - ''' - Test that the CFLAGS environment variable does not pollute the cross - environment. This can't be an ordinary test case because we need to - inspect the compiler database. - ''' - testdir = os.path.join(self.common_test_dir, '3 static') - self.init(testdir, override_envvars={'CFLAGS': '-DBUILD_ENVIRONMENT_ONLY'}) - compdb = self.get_compdb() - self.assertNotIn('-DBUILD_ENVIRONMENT_ONLY', compdb[0]['command']) - - def test_cross_file_overrides_always_args(self): - ''' - Test that $lang_args in cross files always override get_always_args(). - Needed for overriding the default -D_FILE_OFFSET_BITS=64 on some - architectures such as some Android versions and Raspbian. - https://github.com/mesonbuild/meson/issues/3049 - https://github.com/mesonbuild/meson/issues/3089 - ''' - testdir = os.path.join(self.unit_test_dir, '33 cross file overrides always args') - self.meson_cross_file = os.path.join(testdir, 'ubuntu-armhf-overrides.txt') - self.init(testdir) - compdb = self.get_compdb() - self.assertRegex(compdb[0]['command'], '-D_FILE_OFFSET_BITS=64.*-U_FILE_OFFSET_BITS') - self.build() - - def test_cross_libdir(self): - # When cross compiling "libdir" should default to "lib" - # rather than "lib/x86_64-linux-gnu" or something like that. - testdir = os.path.join(self.common_test_dir, '1 trivial') - self.init(testdir) - for i in self.introspect('--buildoptions'): - if i['name'] == 'libdir': - self.assertEqual(i['value'], 'lib') - return - self.assertTrue(False, 'Option libdir not in introspect data.') - - def test_cross_libdir_subproject(self): - # Guard against a regression where calling "subproject" - # would reset the value of libdir to its default value. - testdir = os.path.join(self.unit_test_dir, '76 subdir libdir') - self.init(testdir, extra_args=['--libdir=fuf']) - for i in self.introspect('--buildoptions'): - if i['name'] == 'libdir': - self.assertEqual(i['value'], 'fuf') - return - self.assertTrue(False, 'Libdir specified on command line gets reset.') - - def test_std_remains(self): - # C_std defined in project options must be in effect also when cross compiling. - testdir = os.path.join(self.unit_test_dir, '51 noncross options') - self.init(testdir) - compdb = self.get_compdb() - self.assertRegex(compdb[0]['command'], '-std=c99') - self.build() - - @skipIfNoPkgconfig - def test_pkg_config_option(self): - if not shutil.which('arm-linux-gnueabihf-pkg-config'): - raise unittest.SkipTest('Cross-pkgconfig not found.') - testdir = os.path.join(self.unit_test_dir, '58 pkg_config_path option') - self.init(testdir, extra_args=[ - '-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(): - return shutil.which('x86_64-w64-mingw32-gcc') and not (is_windows() or is_cygwin()) - -@unittest.skipUnless(not is_windows() and should_run_cross_mingw_tests(), "requires ability to cross compile with MinGW") -class LinuxCrossMingwTests(BaseLinuxCrossTests): - ''' - Tests that cross-compilation to Windows/MinGW works - ''' - - def setUp(self): - super().setUp() - src_root = os.path.dirname(__file__) - self.meson_cross_file = os.path.join(src_root, 'cross', 'linux-mingw-w64-64bit.txt') - - def test_exe_wrapper_behaviour(self): - ''' - Test that an exe wrapper that isn't found doesn't cause compiler sanity - checks and compiler checks to fail, but causes configure to fail if it - requires running a cross-built executable (custom_target or run_target) - and causes the tests to be skipped if they are run. - ''' - testdir = os.path.join(self.unit_test_dir, '36 exe_wrapper behaviour') - # Configures, builds, and tests fine by default - self.init(testdir) - self.build() - self.run_tests() - self.wipe() - os.mkdir(self.builddir) - # Change cross file to use a non-existing exe_wrapper and it should fail - self.meson_cross_file = os.path.join(testdir, 'broken-cross.txt') - # Force tracebacks so we can detect them properly - env = {'MESON_FORCE_BACKTRACE': '1'} - error_message = "An exe_wrapper is needed but was not found. Please define one in cross file and check the command and/or add it to PATH." - - with self.assertRaises(MesonException) as cm: - # Must run in-process or we'll get a generic CalledProcessError - self.init(testdir, extra_args='-Drun-target=false', - inprocess=True, - override_envvars=env) - self.assertEqual(str(cm.exception), error_message) - - with self.assertRaises(MesonException) as cm: - # Must run in-process or we'll get a generic CalledProcessError - self.init(testdir, extra_args='-Dcustom-target=false', - inprocess=True, - override_envvars=env) - self.assertEqual(str(cm.exception), error_message) - - self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false'], - override_envvars=env) - self.build() - - with self.assertRaises(MesonException) as cm: - # Must run in-process or we'll get a generic CalledProcessError - self.run_tests(inprocess=True, override_envvars=env) - self.assertEqual(str(cm.exception), - "The exe_wrapper defined in the cross file 'broken' was not found. Please check the command and/or add it to PATH.") - - @skipIfNoPkgconfig - def test_cross_pkg_config_option(self): - testdir = os.path.join(self.unit_test_dir, '58 pkg_config_path option') - self.init(testdir, extra_args=[ - '-Dbuild.pkg_config_path=' + os.path.join(testdir, 'build_extra_path'), - '-Dpkg_config_path=' + os.path.join(testdir, 'host_extra_path'), - ]) - - -class PythonTests(BasePlatformTests): - ''' - Tests that verify compilation of python extension modules - ''' - - def test_versions(self): - if self.backend is not Backend.ninja: - raise unittest.SkipTest(f'Skipping python tests with {self.backend.name} backend') - - testdir = os.path.join(self.src_root, 'test cases', 'unit', '39 python extmodule') - - # No python version specified, this will use meson's python - self.init(testdir) - self.build() - self.run_tests() - self.wipe() - - # When specifying a known name, (python2 / python3) the module - # will also try 'python' as a fallback and use it if the major - # version matches - try: - self.init(testdir, extra_args=['-Dpython=python2']) - self.build() - self.run_tests() - except unittest.SkipTest: - # python2 is not necessarily installed on the test machine, - # if it is not, or the python headers can't be found, the test - # will raise MESON_SKIP_TEST, we could check beforehand what version - # of python is available, but it's a bit of a chicken and egg situation, - # as that is the job of the module, so we just ask for forgiveness rather - # than permission. - pass - - self.wipe() - - for py in ('pypy', 'pypy3'): - try: - self.init(testdir, extra_args=['-Dpython=%s' % py]) - except unittest.SkipTest: - # Same as above, pypy2 and pypy3 are not expected to be present - # on the test system, the test project only raises in these cases - continue - - # We have a pypy, this is expected to work - self.build() - self.run_tests() - self.wipe() - - # The test is configured to error out with MESON_SKIP_TEST - # in case it could not find python - with self.assertRaises(unittest.SkipTest): - self.init(testdir, extra_args=['-Dpython=not-python']) - self.wipe() - - # While dir is an external command on both Windows and Linux, - # it certainly isn't python - with self.assertRaises(unittest.SkipTest): - self.init(testdir, extra_args=['-Dpython=dir']) - self.wipe() - - -class RewriterTests(BasePlatformTests): - def setUp(self): - super().setUp() - self.maxDiff = None - - def prime(self, dirname): - copy_tree(os.path.join(self.rewrite_test_dir, dirname), self.builddir) - - def rewrite_raw(self, directory, args): - if isinstance(args, str): - args = [args] - command = self.rewrite_command + ['--verbose', '--skip', '--sourcedir', directory] + args - p = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True, timeout=60) - print('STDOUT:') - print(p.stdout) - print('STDERR:') - print(p.stderr) - if p.returncode != 0: - if 'MESON_SKIP_TEST' in p.stdout: - raise unittest.SkipTest('Project requested skipping.') - raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout) - if not p.stderr: - return {} - return json.loads(p.stderr) - - def rewrite(self, directory, args): - if isinstance(args, str): - args = [args] - return self.rewrite_raw(directory, ['command'] + args) - - def test_target_source_list(self): - self.prime('1 basic') - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'target': { - 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp']}, - 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']}, - 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']}, - 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']}, - 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp']}, - } - } - self.assertDictEqual(out, expected) - - def test_target_add_sources(self): - self.prime('1 basic') - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) - expected = { - 'target': { - 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp']}, - 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}, - 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['a7.cpp', 'fileB.cpp', 'fileC.cpp']}, - 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['a5.cpp', 'fileA.cpp', 'main.cpp']}, - 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['a5.cpp', 'main.cpp', 'fileA.cpp']}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['a3.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp']}, - 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp', 'a4.cpp']}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}, - 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}, - 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}, - } - } - self.assertDictEqual(out, expected) - - # Check the written file - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - self.assertDictEqual(out, expected) - - def test_target_add_sources_abs(self): - self.prime('1 basic') - abs_src = [os.path.join(self.builddir, x) for x in ['a1.cpp', 'a2.cpp', 'a6.cpp']] - add = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "src_add", "sources": abs_src}]) - inf = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "info"}]) - self.rewrite(self.builddir, add) - out = self.rewrite(self.builddir, inf) - expected = {'target': {'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}}} - self.assertDictEqual(out, expected) - - def test_target_remove_sources(self): - self.prime('1 basic') - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'rmSrc.json')) - expected = { - 'target': { - 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileC.cpp']}, - 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp']}, - 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileC.cpp']}, - 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp']}, - 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp']}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileC.cpp']}, - 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp']}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileC.cpp', 'main.cpp']}, - 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp']}, - 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp']}, - } - } - self.assertDictEqual(out, expected) - - # Check the written file - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - self.assertDictEqual(out, expected) - - def test_target_subdir(self): - self.prime('2 subdirs') - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) - expected = {'name': 'something', 'sources': ['first.c', 'second.c', 'third.c']} - self.assertDictEqual(list(out['target'].values())[0], expected) - - # Check the written file - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - self.assertDictEqual(list(out['target'].values())[0], expected) - - def test_target_remove(self): - self.prime('1 basic') - self.rewrite(self.builddir, os.path.join(self.builddir, 'rmTgt.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - - expected = { - 'target': { - 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']}, - 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']}, - 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']}, - 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']}, - } - } - self.assertDictEqual(out, expected) - - def test_tatrget_add(self): - self.prime('1 basic') - self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - - expected = { - 'target': { - 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp']}, - 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']}, - 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']}, - 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']}, - 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp']}, - 'trivialprog10@sha': {'name': 'trivialprog10', 'sources': ['new1.cpp', 'new2.cpp']}, - } - } - self.assertDictEqual(out, expected) - - def test_target_remove_subdir(self): - self.prime('2 subdirs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'rmTgt.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - self.assertDictEqual(out, {}) - - def test_target_add_subdir(self): - self.prime('2 subdirs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = {'name': 'something', 'sources': ['first.c', 'second.c']} - self.assertDictEqual(out['target']['94b671c@@something@exe'], expected) - - def test_target_source_sorting(self): - self.prime('5 sorting') - add_json = json.dumps([{'type': 'target', 'target': 'exe1', 'operation': 'src_add', 'sources': ['a666.c']}]) - inf_json = json.dumps([{'type': 'target', 'target': 'exe1', 'operation': 'info'}]) - out = self.rewrite(self.builddir, add_json) - out = self.rewrite(self.builddir, inf_json) - expected = { - 'target': { - 'exe1@exe': { - 'name': 'exe1', - 'sources': [ - 'aaa/a/a1.c', - 'aaa/b/b1.c', - 'aaa/b/b2.c', - 'aaa/f1.c', - 'aaa/f2.c', - 'aaa/f3.c', - 'bbb/a/b1.c', - 'bbb/b/b2.c', - 'bbb/c1/b5.c', - 'bbb/c2/b7.c', - 'bbb/c10/b6.c', - 'bbb/a4.c', - 'bbb/b3.c', - 'bbb/b4.c', - 'bbb/b5.c', - 'a1.c', - 'a2.c', - 'a3.c', - 'a10.c', - 'a20.c', - 'a30.c', - 'a100.c', - 'a101.c', - 'a110.c', - 'a210.c', - 'a666.c', - 'b1.c', - 'c2.c' - ] - } - } - } - self.assertDictEqual(out, expected) - - def test_target_same_name_skip(self): - self.prime('4 same name targets') - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = {'name': 'myExe', 'sources': ['main.cpp']} - self.assertEqual(len(out['target']), 2) - for val in out['target'].values(): - self.assertDictEqual(expected, val) - - def test_kwargs_info(self): - self.prime('3 kwargs') - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'kwargs': { - 'project#/': {'version': '0.0.1'}, - 'target#tgt1': {'build_by_default': True}, - 'dependency#dep1': {'required': False} - } - } - self.assertDictEqual(out, expected) - - def test_kwargs_set(self): - self.prime('3 kwargs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'set.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'kwargs': { - 'project#/': {'version': '0.0.2', 'meson_version': '0.50.0', 'license': ['GPL', 'MIT']}, - 'target#tgt1': {'build_by_default': False, 'build_rpath': '/usr/local', 'dependencies': 'dep1'}, - 'dependency#dep1': {'required': True, 'method': 'cmake'} - } - } - self.assertDictEqual(out, expected) - - def test_kwargs_add(self): - self.prime('3 kwargs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'add.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'kwargs': { - 'project#/': {'version': '0.0.1', 'license': ['GPL', 'MIT', 'BSD', 'Boost']}, - 'target#tgt1': {'build_by_default': True}, - 'dependency#dep1': {'required': False} - } - } - self.assertDictEqual(out, expected) - - def test_kwargs_remove(self): - self.prime('3 kwargs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'remove.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'kwargs': { - 'project#/': {'version': '0.0.1', 'license': 'GPL'}, - 'target#tgt1': {'build_by_default': True}, - 'dependency#dep1': {'required': False} - } - } - self.assertDictEqual(out, expected) - - def test_kwargs_remove_regex(self): - self.prime('3 kwargs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'remove_regex.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'kwargs': { - 'project#/': {'version': '0.0.1', 'default_options': 'debug=true'}, - 'target#tgt1': {'build_by_default': True}, - 'dependency#dep1': {'required': False} - } - } - self.assertDictEqual(out, expected) - - def test_kwargs_delete(self): - self.prime('3 kwargs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'delete.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'kwargs': { - 'project#/': {}, - 'target#tgt1': {}, - 'dependency#dep1': {'required': False} - } - } - self.assertDictEqual(out, expected) - - def test_default_options_set(self): - self.prime('3 kwargs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'defopts_set.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'kwargs': { - 'project#/': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=True', 'cpp_std=c++11']}, - 'target#tgt1': {'build_by_default': True}, - 'dependency#dep1': {'required': False} - } - } - self.assertDictEqual(out, expected) - - def test_default_options_delete(self): - self.prime('3 kwargs') - self.rewrite(self.builddir, os.path.join(self.builddir, 'defopts_delete.json')) - out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) - expected = { - 'kwargs': { - 'project#/': {'version': '0.0.1', 'default_options': ['cpp_std=c++14', 'debug=true']}, - 'target#tgt1': {'build_by_default': True}, - 'dependency#dep1': {'required': False} - } - } - self.assertDictEqual(out, expected) - -class NativeFileTests(BasePlatformTests): - - def setUp(self): - super().setUp() - self.testcase = os.path.join(self.unit_test_dir, '47 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, f'generated{self.current_config}.config') - self.current_config += 1 - with open(filename, 'wt', encoding='utf-8') as f: - for section, entries in values.items(): - f.write(f'[{section}]\n') - for k, v in entries.items(): - if isinstance(v, (bool, int, float)): - f.write(f"{k}={v}\n") - elif isinstance(v, list): - f.write("{}=[{}]\n".format(k, ', '.join([f"'{w}'" for w in v]))) - else: - f.write(f"{k}='{v}'\n") - return filename - - def helper_create_binary_wrapper(self, binary, dir_=None, extra_args=None, **kwargs): - """Creates a wrapper around a binary that overrides specific values.""" - filename = os.path.join(dir_ or self.builddir, f'binary_wrapper{self.current_wrapper}.py') - extra_args = extra_args or {} - self.current_wrapper += 1 - if is_haiku(): - chbang = '#!/bin/env python3' - else: - chbang = '#!/usr/bin/env python3' - - with open(filename, 'wt', encoding='utf-8') as f: - f.write(textwrap.dedent('''\ - {} - import argparse - import subprocess - import sys - - def main(): - parser = argparse.ArgumentParser() - '''.format(chbang))) - for name in chain(extra_args, 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 chain(extra_args.items(), kwargs.items()): - f.write(f' if args.{name}:\n') - 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) - print(ret.stdout.decode('utf-8')) - print(ret.stderr.decode('utf-8'), 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, f'binary_wrapper{self.current_wrapper}.bat') - with open(batfile, 'wt', encoding='utf-8') as f: - f.write(fr'@{sys.executable} {filename} %*') - return batfile - - def helper_for_compiler(self, lang, cb, for_machine = MachineChoice.HOST): - """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 = lambda: compiler_from_language(env, lang, for_machine) - cc = getter() - binary, newid = cb(cc) - env.binaries[for_machine].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']) - - # This test hangs on cygwin. - @unittest.skipIf(os.name != 'posix' or is_cygwin(), 'Uses fifos, which are not available on non Unix OSes.') - def test_native_file_is_pipe(self): - fifo = os.path.join(self.builddir, 'native.file') - os.mkfifo(fifo) - with tempfile.TemporaryDirectory() as d: - wrapper = self.helper_create_binary_wrapper('bash', d, version='12345') - - def filler(): - with open(fifo, 'w', encoding='utf-8') as f: - f.write('[binaries]\n') - f.write(f"bash = '{wrapper}'\n") - - thread = threading.Thread(target=filler) - thread.start() - - self.init(self.testcase, extra_args=['--native-file', fifo, '-Dcase=find_program']) - - thread.join() - os.unlink(fifo) - - self.init(self.testcase, extra_args=['--wipe']) - - 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, entry=None): - wrapper = self.helper_create_binary_wrapper(binary, version='12345') - config = self.helper_create_native_file({'binaries': {entry or binary: wrapper}}) - self.init(self.testcase, extra_args=['--native-file', config, f'-Dcase={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 mesonbuild.environment.detect_msys2_arch(): - raise unittest.SkipTest('Skipped due to problems with LLVM on MSYS2') - 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.') - elif is_osx(): - binary = 'python' - else: - binary = 'python2' - - # We not have python2, check for it - for v in ['2', '2.7', '-2.7']: - rc = subprocess.call(['pkg-config', '--cflags', f'python{v}'], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - if rc == 0: - break - else: - raise unittest.SkipTest('Not running Python 2 tests because dev packages not installed.') - self._simple_test('python', binary, entry='python') - - @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard') - @skip_if_env_set('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 is_real_gnu_compiler(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_set('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 is_real_gnu_compiler(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_set('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 is_real_gnu_compiler(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_set('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 is_real_gnu_compiler(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_set('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.') - if shutil.which('dmd'): - return 'dmd', 'dmd' - raise unittest.SkipTest('No alternative dlang compiler found.') - self.helper_for_compiler('d', cb) - - @skip_if_not_language('cs') - @skip_if_env_set('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_set('FC') - def test_fortran_compiler(self): - def cb(comp): - if comp.id == 'lcc': - if shutil.which('lfortran'): - return 'lfortran', 'lcc' - raise unittest.SkipTest('No alternate Fortran implementation.') - elif comp.id == 'gcc': - if shutil.which('ifort'): - # There is an ICC for windows (windows build, linux host), - # but we don't support that ATM so lets not worry about it. - if is_windows(): - return 'ifort', 'intel-cl' - return 'ifort', 'intel' - elif shutil.which('flang'): - return 'flang', 'flang' - elif shutil.which('pgfortran'): - return 'pgfortran', 'pgi' - # 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 Fortran implementation.') - return 'gfortran', 'gcc' - self.helper_for_compiler('fortran', cb) - - def _single_implementation_compiler(self, lang: str, binary: str, version_str: str, version: str) -> None: - """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() - env.binaries.host.binaries[lang] = [wrapper] - compiler = compiler_from_language(env, lang, MachineChoice.HOST) - self.assertEqual(compiler.version, version) - - @skip_if_not_language('vala') - @skip_if_env_set('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_set('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', - extra_args={'Xlinker': 'macosx_version. PROJECT:ld - 1.2.3'}) - env = get_fake_env() - env.binaries.host.binaries['swift'] = [wrapper] - compiler = detect_swift_compiler(env, MachineChoice.HOST) - self.assertEqual(compiler.version, '1.2345') - - def test_native_file_dirs(self): - testcase = os.path.join(self.unit_test_dir, '60 native file override') - self.init(testcase, default_args=False, - extra_args=['--native-file', os.path.join(testcase, 'nativefile')]) - - def test_native_file_dirs_overridden(self): - testcase = os.path.join(self.unit_test_dir, '60 native file override') - self.init(testcase, default_args=False, - extra_args=['--native-file', os.path.join(testcase, 'nativefile'), - '-Ddef_libdir=liblib', '-Dlibdir=liblib']) - - def test_compile_sys_path(self): - """Compiling with a native file stored in a system path works. - - There was a bug which caused the paths to be stored incorrectly and - would result in ninja invoking meson in an infinite loop. This tests - for that by actually invoking ninja. - """ - testcase = os.path.join(self.common_test_dir, '1 trivial') - - # It really doesn't matter what's in the native file, just that it exists - config = self.helper_create_native_file({'binaries': {'bash': 'false'}}) - - self.init(testcase, extra_args=['--native-file', config]) - self.build() - - def test_user_options(self): - testcase = os.path.join(self.common_test_dir, '40 options') - for opt, value in [('testoption', 'some other val'), ('other_one', True), - ('combo_opt', 'one'), ('array_opt', ['two']), - ('integer_opt', 0), - ('CaseSenSiTivE', 'SOME other Value'), - ('CASESENSITIVE', 'some other Value')]: - config = self.helper_create_native_file({'project options': {opt: value}}) - with self.assertRaises(subprocess.CalledProcessError) as cm: - self.init(testcase, extra_args=['--native-file', config]) - self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option') - - def test_user_options_command_line_overrides(self): - testcase = os.path.join(self.common_test_dir, '40 options') - config = self.helper_create_native_file({'project options': {'other_one': True}}) - self.init(testcase, extra_args=['--native-file', config, '-Dother_one=false']) - - def test_user_options_subproject(self): - testcase = os.path.join(self.unit_test_dir, '79 user options for subproject') - - s = os.path.join(testcase, 'subprojects') - if not os.path.exists(s): - os.mkdir(s) - s = os.path.join(s, 'sub') - if not os.path.exists(s): - sub = os.path.join(self.common_test_dir, '40 options') - shutil.copytree(sub, s) - - for opt, value in [('testoption', 'some other val'), ('other_one', True), - ('combo_opt', 'one'), ('array_opt', ['two']), - ('integer_opt', 0)]: - config = self.helper_create_native_file({'sub:project options': {opt: value}}) - with self.assertRaises(subprocess.CalledProcessError) as cm: - self.init(testcase, extra_args=['--native-file', config]) - self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option') - - def test_option_bool(self): - # Bools are allowed to be unquoted - testcase = os.path.join(self.common_test_dir, '1 trivial') - config = self.helper_create_native_file({'built-in options': {'werror': True}}) - self.init(testcase, extra_args=['--native-file', config]) - configuration = self.introspect('--buildoptions') - for each in configuration: - # Test that no-per subproject options are inherited from the parent - if 'werror' in each['name']: - self.assertEqual(each['value'], True) - break - else: - self.fail('Did not find werror in build options?') - - def test_option_integer(self): - # Bools are allowed to be unquoted - testcase = os.path.join(self.common_test_dir, '1 trivial') - config = self.helper_create_native_file({'built-in options': {'unity_size': 100}}) - self.init(testcase, extra_args=['--native-file', config]) - configuration = self.introspect('--buildoptions') - for each in configuration: - # Test that no-per subproject options are inherited from the parent - if 'unity_size' in each['name']: - self.assertEqual(each['value'], 100) - break - else: - self.fail('Did not find unity_size in build options?') - - def test_builtin_options(self): - testcase = os.path.join(self.common_test_dir, '2 cpp') - config = self.helper_create_native_file({'built-in options': {'cpp_std': 'c++14'}}) - - self.init(testcase, extra_args=['--native-file', config]) - configuration = self.introspect('--buildoptions') - for each in configuration: - if each['name'] == 'cpp_std': - self.assertEqual(each['value'], 'c++14') - break - else: - self.fail('Did not find werror in build options?') - - def test_builtin_options_conf_overrides_env(self): - testcase = os.path.join(self.common_test_dir, '2 cpp') - config = self.helper_create_native_file({'built-in options': {'pkg_config_path': '/foo'}}) - - self.init(testcase, extra_args=['--native-file', config], override_envvars={'PKG_CONFIG_PATH': '/bar'}) - configuration = self.introspect('--buildoptions') - for each in configuration: - if each['name'] == 'pkg_config_path': - self.assertEqual(each['value'], ['/foo']) - break - else: - self.fail('Did not find pkg_config_path in build options?') - - def test_builtin_options_subprojects(self): - testcase = os.path.join(self.common_test_dir, '98 subproject subdir') - config = self.helper_create_native_file({'built-in options': {'default_library': 'both', 'c_args': ['-Dfoo']}, 'sub:built-in options': {'default_library': 'static'}}) - - self.init(testcase, extra_args=['--native-file', config]) - configuration = self.introspect('--buildoptions') - found = 0 - for each in configuration: - # Test that no-per subproject options are inherited from the parent - if 'c_args' in each['name']: - # This path will be hit twice, once for build and once for host, - self.assertEqual(each['value'], ['-Dfoo']) - found += 1 - elif each['name'] == 'default_library': - self.assertEqual(each['value'], 'both') - found += 1 - elif each['name'] == 'sub:default_library': - self.assertEqual(each['value'], 'static') - found += 1 - self.assertEqual(found, 4, 'Did not find all three sections') - - def test_builtin_options_subprojects_overrides_buildfiles(self): - # If the buildfile says subproject(... default_library: shared), ensure that's overwritten - testcase = os.path.join(self.common_test_dir, '223 persubproject options') - config = self.helper_create_native_file({'sub2:built-in options': {'default_library': 'shared'}}) - - with self.assertRaises((RuntimeError, subprocess.CalledProcessError)) as cm: - self.init(testcase, extra_args=['--native-file', config]) - if isinstance(cm, RuntimeError): - check = str(cm.exception) - else: - check = cm.exception.stdout - self.assertIn(check, 'Parent should override default_library') - - def test_builtin_options_subprojects_dont_inherits_parent_override(self): - # If the buildfile says subproject(... default_library: shared), ensure that's overwritten - testcase = os.path.join(self.common_test_dir, '223 persubproject options') - config = self.helper_create_native_file({'built-in options': {'default_library': 'both'}}) - self.init(testcase, extra_args=['--native-file', config]) - - def test_builtin_options_compiler_properties(self): - # the properties section can have lang_args, and those need to be - # overwritten by the built-in options - testcase = os.path.join(self.common_test_dir, '1 trivial') - config = self.helper_create_native_file({ - 'built-in options': {'c_args': ['-DFOO']}, - 'properties': {'c_args': ['-DBAR']}, - }) - - self.init(testcase, extra_args=['--native-file', config]) - configuration = self.introspect('--buildoptions') - for each in configuration: - if each['name'] == 'c_args': - self.assertEqual(each['value'], ['-DFOO']) - break - else: - self.fail('Did not find c_args in build options?') - - def test_builtin_options_compiler_properties_legacy(self): - # The legacy placement in properties is still valid if a 'built-in - # options' setting is present, but doesn't have the lang_args - testcase = os.path.join(self.common_test_dir, '1 trivial') - config = self.helper_create_native_file({ - 'built-in options': {'default_library': 'static'}, - 'properties': {'c_args': ['-DBAR']}, - }) - - self.init(testcase, extra_args=['--native-file', config]) - configuration = self.introspect('--buildoptions') - for each in configuration: - if each['name'] == 'c_args': - self.assertEqual(each['value'], ['-DBAR']) - break - else: - self.fail('Did not find c_args in build options?') - - def test_builtin_options_paths(self): - # the properties section can have lang_args, and those need to be - # overwritten by the built-in options - testcase = os.path.join(self.common_test_dir, '1 trivial') - config = self.helper_create_native_file({ - 'built-in options': {'bindir': 'foo'}, - 'paths': {'bindir': 'bar'}, - }) - - self.init(testcase, extra_args=['--native-file', config]) - configuration = self.introspect('--buildoptions') - for each in configuration: - if each['name'] == 'bindir': - self.assertEqual(each['value'], 'foo') - break - else: - self.fail('Did not find bindir in build options?') - - def test_builtin_options_paths_legacy(self): - testcase = os.path.join(self.common_test_dir, '1 trivial') - config = self.helper_create_native_file({ - 'built-in options': {'default_library': 'static'}, - 'paths': {'bindir': 'bar'}, - }) - - self.init(testcase, extra_args=['--native-file', config]) - configuration = self.introspect('--buildoptions') - for each in configuration: - if each['name'] == 'bindir': - self.assertEqual(each['value'], 'bar') - break - else: - self.fail('Did not find bindir in build options?') - - -class CrossFileTests(BasePlatformTests): - - """Tests for cross file functionality not directly related to - cross compiling. - - This is mainly aimed to testing overrides from cross files. - """ - - def setUp(self): - super().setUp() - self.current_config = 0 - self.current_wrapper = 0 - - def _cross_file_generator(self, *, needs_exe_wrapper: bool = False, - exe_wrapper: T.Optional[T.List[str]] = None) -> str: - if is_windows(): - raise unittest.SkipTest('Cannot run this test on non-mingw/non-cygwin windows') - - return textwrap.dedent(f"""\ - [binaries] - c = '{shutil.which('gcc' if is_sunos() else 'cc')}' - ar = '{shutil.which('ar')}' - strip = '{shutil.which('strip')}' - exe_wrapper = {str(exe_wrapper) if exe_wrapper is not None else '[]'} - - [properties] - needs_exe_wrapper = {needs_exe_wrapper} - - [host_machine] - system = 'linux' - cpu_family = 'x86' - cpu = 'i686' - endian = 'little' - """) - - def _stub_exe_wrapper(self) -> str: - return textwrap.dedent('''\ - #!/usr/bin/env python3 - import subprocess - import sys - - sys.exit(subprocess.run(sys.argv[1:]).returncode) - ''') - - def test_needs_exe_wrapper_true(self): - testdir = os.path.join(self.unit_test_dir, '71 cross test passed') - with tempfile.TemporaryDirectory() as d: - p = Path(d) / 'crossfile' - with p.open('wt', encoding='utf-8') as f: - f.write(self._cross_file_generator(needs_exe_wrapper=True)) - self.init(testdir, extra_args=['--cross-file=' + str(p)]) - out = self.run_target('test') - self.assertRegex(out, r'Skipped:\s*1\s*\n') - - def test_needs_exe_wrapper_false(self): - testdir = os.path.join(self.unit_test_dir, '71 cross test passed') - with tempfile.TemporaryDirectory() as d: - p = Path(d) / 'crossfile' - with p.open('wt', encoding='utf-8') as f: - f.write(self._cross_file_generator(needs_exe_wrapper=False)) - self.init(testdir, extra_args=['--cross-file=' + str(p)]) - out = self.run_target('test') - self.assertNotRegex(out, r'Skipped:\s*1\n') - - def test_needs_exe_wrapper_true_wrapper(self): - testdir = os.path.join(self.unit_test_dir, '71 cross test passed') - with tempfile.TemporaryDirectory() as d: - s = Path(d) / 'wrapper.py' - with s.open('wt', encoding='utf-8') as f: - f.write(self._stub_exe_wrapper()) - s.chmod(0o774) - p = Path(d) / 'crossfile' - with p.open('wt', encoding='utf-8') as f: - f.write(self._cross_file_generator( - needs_exe_wrapper=True, - exe_wrapper=[str(s)])) - - self.init(testdir, extra_args=['--cross-file=' + str(p), '-Dexpect=true']) - out = self.run_target('test') - self.assertRegex(out, r'Ok:\s*3\s*\n') - - def test_cross_exe_passed_no_wrapper(self): - testdir = os.path.join(self.unit_test_dir, '71 cross test passed') - with tempfile.TemporaryDirectory() as d: - p = Path(d) / 'crossfile' - with p.open('wt', encoding='utf-8') as f: - f.write(self._cross_file_generator(needs_exe_wrapper=True)) - - self.init(testdir, extra_args=['--cross-file=' + str(p)]) - self.build() - out = self.run_target('test') - self.assertRegex(out, r'Skipped:\s*1\s*\n') - - # The test uses mocking and thus requires that the current process is the - # one to run the Meson steps. If we are using an external test executable - # (most commonly in Debian autopkgtests) then the mocking won't work. - @unittest.skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.') - def test_cross_file_system_paths(self): - if is_windows(): - raise unittest.SkipTest('system crossfile paths not defined for Windows (yet)') - - testdir = os.path.join(self.common_test_dir, '1 trivial') - cross_content = self._cross_file_generator() - with tempfile.TemporaryDirectory() as d: - dir_ = os.path.join(d, 'meson', 'cross') - os.makedirs(dir_) - with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f: - f.write(cross_content) - name = os.path.basename(f.name) - - with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}): - self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) - self.wipe() - - with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}): - os.environ.pop('XDG_DATA_HOME', None) - self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) - self.wipe() - - with tempfile.TemporaryDirectory() as d: - dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross') - os.makedirs(dir_) - with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f: - f.write(cross_content) - name = os.path.basename(f.name) - - # If XDG_DATA_HOME is set in the environment running the - # tests this test will fail, os mock the environment, pop - # it, then test - with mock.patch.dict(os.environ): - os.environ.pop('XDG_DATA_HOME', None) - with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)): - self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) - self.wipe() - - def helper_create_cross_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, f'generated{self.current_config}.config') - self.current_config += 1 - with open(filename, 'wt', encoding='utf-8') as f: - for section, entries in values.items(): - f.write(f'[{section}]\n') - for k, v in entries.items(): - f.write(f"{k}={v!r}\n") - return filename - - def test_cross_file_dirs(self): - testcase = os.path.join(self.unit_test_dir, '60 native file override') - self.init(testcase, default_args=False, - extra_args=['--native-file', os.path.join(testcase, 'nativefile'), - '--cross-file', os.path.join(testcase, 'crossfile'), - '-Ddef_bindir=binbar', - '-Ddef_datadir=databar', - '-Ddef_includedir=includebar', - '-Ddef_infodir=infobar', - '-Ddef_libdir=libbar', - '-Ddef_libexecdir=libexecbar', - '-Ddef_localedir=localebar', - '-Ddef_localstatedir=localstatebar', - '-Ddef_mandir=manbar', - '-Ddef_sbindir=sbinbar', - '-Ddef_sharedstatedir=sharedstatebar', - '-Ddef_sysconfdir=sysconfbar']) - - def test_cross_file_dirs_overridden(self): - testcase = os.path.join(self.unit_test_dir, '60 native file override') - self.init(testcase, default_args=False, - extra_args=['--native-file', os.path.join(testcase, 'nativefile'), - '--cross-file', os.path.join(testcase, 'crossfile'), - '-Ddef_libdir=liblib', '-Dlibdir=liblib', - '-Ddef_bindir=binbar', - '-Ddef_datadir=databar', - '-Ddef_includedir=includebar', - '-Ddef_infodir=infobar', - '-Ddef_libexecdir=libexecbar', - '-Ddef_localedir=localebar', - '-Ddef_localstatedir=localstatebar', - '-Ddef_mandir=manbar', - '-Ddef_sbindir=sbinbar', - '-Ddef_sharedstatedir=sharedstatebar', - '-Ddef_sysconfdir=sysconfbar']) - - def test_cross_file_dirs_chain(self): - # crossfile2 overrides crossfile overrides nativefile - testcase = os.path.join(self.unit_test_dir, '60 native file override') - self.init(testcase, default_args=False, - extra_args=['--native-file', os.path.join(testcase, 'nativefile'), - '--cross-file', os.path.join(testcase, 'crossfile'), - '--cross-file', os.path.join(testcase, 'crossfile2'), - '-Ddef_bindir=binbar2', - '-Ddef_datadir=databar', - '-Ddef_includedir=includebar', - '-Ddef_infodir=infobar', - '-Ddef_libdir=libbar', - '-Ddef_libexecdir=libexecbar', - '-Ddef_localedir=localebar', - '-Ddef_localstatedir=localstatebar', - '-Ddef_mandir=manbar', - '-Ddef_sbindir=sbinbar', - '-Ddef_sharedstatedir=sharedstatebar', - '-Ddef_sysconfdir=sysconfbar']) - - def test_user_options(self): - # This is just a touch test for cross file, since the implementation - # shares code after loading from the files - testcase = os.path.join(self.common_test_dir, '40 options') - config = self.helper_create_cross_file({'project options': {'testoption': 'some other value'}}) - with self.assertRaises(subprocess.CalledProcessError) as cm: - self.init(testcase, extra_args=['--cross-file', config]) - self.assertRegex(cm.exception.stdout, r'Incorrect value to [a-z]+ option') - - def test_builtin_options(self): - testcase = os.path.join(self.common_test_dir, '2 cpp') - config = self.helper_create_cross_file({'built-in options': {'cpp_std': 'c++14'}}) - - self.init(testcase, extra_args=['--cross-file', config]) - configuration = self.introspect('--buildoptions') - for each in configuration: - if each['name'] == 'cpp_std': - self.assertEqual(each['value'], 'c++14') - break - else: - self.fail('No c++ standard set?') - - def test_builtin_options_per_machine(self): - """Test options that are allowed to be set on a per-machine basis. - - Such options could be passed twice, once for the build machine, and - once for the host machine. I've picked pkg-config path, but any would - do that can be set for both. - """ - testcase = os.path.join(self.common_test_dir, '2 cpp') - cross = self.helper_create_cross_file({'built-in options': {'pkg_config_path': '/cross/path', 'cpp_std': 'c++17'}}) - native = self.helper_create_cross_file({'built-in options': {'pkg_config_path': '/native/path', 'cpp_std': 'c++14'}}) - - # Ensure that PKG_CONFIG_PATH is not set in the environment - with mock.patch.dict('os.environ'): - for k in ['PKG_CONFIG_PATH', 'PKG_CONFIG_PATH_FOR_BUILD']: - try: - del os.environ[k] - except KeyError: - pass - self.init(testcase, extra_args=['--cross-file', cross, '--native-file', native]) - - configuration = self.introspect('--buildoptions') - found = 0 - for each in configuration: - if each['name'] == 'pkg_config_path': - self.assertEqual(each['value'], ['/cross/path']) - found += 1 - elif each['name'] == 'cpp_std': - self.assertEqual(each['value'], 'c++17') - found += 1 - elif each['name'] == 'build.pkg_config_path': - self.assertEqual(each['value'], ['/native/path']) - found += 1 - elif each['name'] == 'build.cpp_std': - self.assertEqual(each['value'], 'c++14') - found += 1 - - if found == 4: - break - self.assertEqual(found, 4, 'Did not find all sections.') - - def test_builtin_options_conf_overrides_env(self): - testcase = os.path.join(self.common_test_dir, '2 cpp') - config = self.helper_create_cross_file({'built-in options': {'pkg_config_path': '/native', 'cpp_args': '-DFILE'}}) - cross = self.helper_create_cross_file({'built-in options': {'pkg_config_path': '/cross', 'cpp_args': '-DFILE'}}) - - self.init(testcase, extra_args=['--native-file', config, '--cross-file', cross], - override_envvars={'PKG_CONFIG_PATH': '/bar', 'PKG_CONFIG_PATH_FOR_BUILD': '/dir', - 'CXXFLAGS': '-DENV', 'CXXFLAGS_FOR_BUILD': '-DENV'}) - configuration = self.introspect('--buildoptions') - found = 0 - expected = 4 - for each in configuration: - if each['name'] == 'pkg_config_path': - self.assertEqual(each['value'], ['/cross']) - found += 1 - elif each['name'] == 'build.pkg_config_path': - self.assertEqual(each['value'], ['/native']) - found += 1 - elif each['name'].endswith('cpp_args'): - self.assertEqual(each['value'], ['-DFILE']) - found += 1 - if found == expected: - break - self.assertEqual(found, expected, 'Did not find all sections.') - - def test_for_build_env_vars(self) -> None: - testcase = os.path.join(self.common_test_dir, '2 cpp') - config = self.helper_create_cross_file({'built-in options': {}}) - cross = self.helper_create_cross_file({'built-in options': {}}) - - self.init(testcase, extra_args=['--native-file', config, '--cross-file', cross], - override_envvars={'PKG_CONFIG_PATH': '/bar', 'PKG_CONFIG_PATH_FOR_BUILD': '/dir'}) - configuration = self.introspect('--buildoptions') - found = 0 - for each in configuration: - if each['name'] == 'pkg_config_path': - self.assertEqual(each['value'], ['/bar']) - found += 1 - elif each['name'] == 'build.pkg_config_path': - self.assertEqual(each['value'], ['/dir']) - found += 1 - if found == 2: - break - self.assertEqual(found, 2, 'Did not find all sections.') - - def test_project_options_native_only(self) -> None: - # Do not load project options from a native file when doing a cross - # build - testcase = os.path.join(self.unit_test_dir, '19 array option') - config = self.helper_create_cross_file({'project options': {'list': ['bar', 'foo']}}) - cross = self.helper_create_cross_file({'binaries': {}}) - - self.init(testcase, extra_args=['--native-file', config, '--cross-file', cross]) - configuration = self.introspect('--buildoptions') - for each in configuration: - if each['name'] == 'list': - self.assertEqual(each['value'], ['foo', 'bar']) - break - else: - self.fail('Did not find expected option.') - - -class TAPParserTests(unittest.TestCase): - def assert_test(self, events, **kwargs): - if 'explanation' not in kwargs: - kwargs['explanation'] = None - self.assertEqual(next(events), TAPParser.Test(**kwargs)) - - def assert_plan(self, events, **kwargs): - if 'skipped' not in kwargs: - kwargs['skipped'] = False - if 'explanation' not in kwargs: - kwargs['explanation'] = None - self.assertEqual(next(events), TAPParser.Plan(**kwargs)) - - def assert_version(self, events, **kwargs): - self.assertEqual(next(events), TAPParser.Version(**kwargs)) - - def assert_error(self, events): - self.assertEqual(type(next(events)), TAPParser.Error) - - def assert_bailout(self, events, **kwargs): - self.assertEqual(next(events), TAPParser.Bailout(**kwargs)) - - def assert_last(self, events): - with self.assertRaises(StopIteration): - next(events) - - def parse_tap(self, s): - parser = TAPParser() - return iter(parser.parse(io.StringIO(s))) - - def parse_tap_v13(self, s): - events = self.parse_tap('TAP version 13\n' + s) - self.assert_version(events, version=13) - return events - - def test_empty(self): - events = self.parse_tap('') - self.assert_last(events) - - def test_empty_plan(self): - events = self.parse_tap('1..0') - self.assert_plan(events, num_tests=0, late=False, skipped=True) - self.assert_last(events) - - def test_plan_directive(self): - events = self.parse_tap('1..0 # skipped for some reason') - self.assert_plan(events, num_tests=0, late=False, skipped=True, - explanation='for some reason') - self.assert_last(events) - - events = self.parse_tap('1..1 # skipped for some reason\nok 1') - self.assert_error(events) - self.assert_plan(events, num_tests=1, late=False, skipped=True, - explanation='for some reason') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - events = self.parse_tap('1..1 # todo not supported here\nok 1') - self.assert_error(events) - self.assert_plan(events, num_tests=1, late=False, skipped=False, - explanation='not supported here') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - def test_one_test_ok(self): - events = self.parse_tap('ok') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - def test_one_test_with_number(self): - events = self.parse_tap('ok 1') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - def test_one_test_with_name(self): - events = self.parse_tap('ok 1 abc') - self.assert_test(events, number=1, name='abc', result=TestResult.OK) - self.assert_last(events) - - def test_one_test_not_ok(self): - events = self.parse_tap('not ok') - self.assert_test(events, number=1, name='', result=TestResult.FAIL) - self.assert_last(events) - - def test_one_test_todo(self): - events = self.parse_tap('not ok 1 abc # TODO') - self.assert_test(events, number=1, name='abc', result=TestResult.EXPECTEDFAIL) - self.assert_last(events) - - events = self.parse_tap('ok 1 abc # TODO') - self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS) - self.assert_last(events) - - def test_one_test_skip(self): - events = self.parse_tap('ok 1 abc # SKIP') - self.assert_test(events, number=1, name='abc', result=TestResult.SKIP) - self.assert_last(events) - - def test_one_test_skip_failure(self): - events = self.parse_tap('not ok 1 abc # SKIP') - self.assert_test(events, number=1, name='abc', result=TestResult.FAIL) - self.assert_last(events) - - def test_many_early_plan(self): - events = self.parse_tap('1..4\nok 1\nnot ok 2\nok 3\nnot ok 4') - self.assert_plan(events, num_tests=4, late=False) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_test(events, number=2, name='', result=TestResult.FAIL) - self.assert_test(events, number=3, name='', result=TestResult.OK) - self.assert_test(events, number=4, name='', result=TestResult.FAIL) - self.assert_last(events) - - def test_many_late_plan(self): - events = self.parse_tap('ok 1\nnot ok 2\nok 3\nnot ok 4\n1..4') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_test(events, number=2, name='', result=TestResult.FAIL) - self.assert_test(events, number=3, name='', result=TestResult.OK) - self.assert_test(events, number=4, name='', result=TestResult.FAIL) - self.assert_plan(events, num_tests=4, late=True) - self.assert_last(events) - - def test_directive_case(self): - events = self.parse_tap('ok 1 abc # skip') - self.assert_test(events, number=1, name='abc', result=TestResult.SKIP) - self.assert_last(events) - - events = self.parse_tap('ok 1 abc # ToDo') - self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS) - self.assert_last(events) - - def test_directive_explanation(self): - events = self.parse_tap('ok 1 abc # skip why') - self.assert_test(events, number=1, name='abc', result=TestResult.SKIP, - explanation='why') - self.assert_last(events) - - events = self.parse_tap('ok 1 abc # ToDo Because') - self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS, - explanation='Because') - self.assert_last(events) - - def test_one_test_early_plan(self): - events = self.parse_tap('1..1\nok') - self.assert_plan(events, num_tests=1, late=False) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - def test_one_test_late_plan(self): - events = self.parse_tap('ok\n1..1') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_plan(events, num_tests=1, late=True) - self.assert_last(events) - - def test_out_of_order(self): - events = self.parse_tap('ok 2') - self.assert_error(events) - self.assert_test(events, number=2, name='', result=TestResult.OK) - self.assert_last(events) - - def test_middle_plan(self): - events = self.parse_tap('ok 1\n1..2\nok 2') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_plan(events, num_tests=2, late=True) - self.assert_error(events) - self.assert_test(events, number=2, name='', result=TestResult.OK) - self.assert_last(events) - - def test_too_many_plans(self): - events = self.parse_tap('1..1\n1..2\nok 1') - self.assert_plan(events, num_tests=1, late=False) - self.assert_error(events) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - def test_too_many(self): - events = self.parse_tap('ok 1\nnot ok 2\n1..1') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_test(events, number=2, name='', result=TestResult.FAIL) - self.assert_plan(events, num_tests=1, late=True) - self.assert_error(events) - self.assert_last(events) - - events = self.parse_tap('1..1\nok 1\nnot ok 2') - self.assert_plan(events, num_tests=1, late=False) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_test(events, number=2, name='', result=TestResult.FAIL) - self.assert_error(events) - self.assert_last(events) - - def test_too_few(self): - events = self.parse_tap('ok 1\nnot ok 2\n1..3') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_test(events, number=2, name='', result=TestResult.FAIL) - self.assert_plan(events, num_tests=3, late=True) - self.assert_error(events) - self.assert_last(events) - - events = self.parse_tap('1..3\nok 1\nnot ok 2') - self.assert_plan(events, num_tests=3, late=False) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_test(events, number=2, name='', result=TestResult.FAIL) - self.assert_error(events) - self.assert_last(events) - - def test_too_few_bailout(self): - events = self.parse_tap('1..3\nok 1\nnot ok 2\nBail out! no third test') - self.assert_plan(events, num_tests=3, late=False) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_test(events, number=2, name='', result=TestResult.FAIL) - self.assert_bailout(events, message='no third test') - self.assert_last(events) - - def test_diagnostics(self): - events = self.parse_tap('1..1\n# ignored\nok 1') - self.assert_plan(events, num_tests=1, late=False) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - events = self.parse_tap('# ignored\n1..1\nok 1\n# ignored too') - self.assert_plan(events, num_tests=1, late=False) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - events = self.parse_tap('# ignored\nok 1\n1..1\n# ignored too') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_plan(events, num_tests=1, late=True) - self.assert_last(events) - - def test_empty_line(self): - events = self.parse_tap('1..1\n\nok 1') - self.assert_plan(events, num_tests=1, late=False) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - def test_unexpected(self): - events = self.parse_tap('1..1\ninvalid\nok 1') - self.assert_plan(events, num_tests=1, late=False) - self.assert_error(events) - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_last(events) - - def test_version(self): - events = self.parse_tap('TAP version 13\n') - self.assert_version(events, version=13) - self.assert_last(events) - - events = self.parse_tap('TAP version 12\n') - self.assert_error(events) - self.assert_last(events) - - events = self.parse_tap('1..0\nTAP version 13\n') - self.assert_plan(events, num_tests=0, late=False, skipped=True) - self.assert_error(events) - self.assert_last(events) - - def test_yaml(self): - events = self.parse_tap_v13('ok\n ---\n foo: abc\n bar: def\n ...\nok 2') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_test(events, number=2, name='', result=TestResult.OK) - self.assert_last(events) - - events = self.parse_tap_v13('ok\n ---\n foo: abc\n bar: def') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_error(events) - self.assert_last(events) - - events = self.parse_tap_v13('ok 1\n ---\n foo: abc\n bar: def\nnot ok 2') - self.assert_test(events, number=1, name='', result=TestResult.OK) - self.assert_error(events) - self.assert_test(events, number=2, name='', result=TestResult.FAIL) - self.assert_last(events) - -class SubprojectsCommandTests(BasePlatformTests): - def setUp(self): - super().setUp() - self.root_dir = Path(self.builddir) - - self.project_dir = self.root_dir / 'src' - self._create_project(self.project_dir) - - self.subprojects_dir = self.project_dir / 'subprojects' - os.makedirs(str(self.subprojects_dir)) - self.packagecache_dir = self.subprojects_dir / 'packagecache' - os.makedirs(str(self.packagecache_dir)) - - def _create_project(self, path, project_name='dummy'): - os.makedirs(str(path), exist_ok=True) - with open(str(path / 'meson.build'), 'w', encoding='utf-8') as f: - f.write(f"project('{project_name}')") - - def _git(self, cmd, workdir): - return git(cmd, str(workdir), check=True)[1].strip() - - def _git_config(self, workdir): - self._git(['config', 'user.name', 'Meson Test'], workdir) - self._git(['config', 'user.email', 'meson.test@example.com'], workdir) - - def _git_remote(self, cmd, name): - return self._git(cmd, self.root_dir / name) - - def _git_local(self, cmd, name): - return self._git(cmd, self.subprojects_dir / name) - - def _git_local_branch(self, name): - # Same as `git branch --show-current` but compatible with older git version - branch = self._git_local(['rev-parse', '--abbrev-ref', 'HEAD'], name) - return branch if branch != 'HEAD' else '' - - def _git_local_commit(self, name, ref='HEAD'): - return self._git_local(['rev-parse', ref], name) - - def _git_remote_commit(self, name, ref='HEAD'): - return self._git_remote(['rev-parse', ref], name) - - def _git_create_repo(self, path): - # If a user has git configuration init.defaultBranch set we want to override that - with tempfile.TemporaryDirectory() as d: - out = git(['--version'], str(d))[1] - if version_compare(search_version(out), '>= 2.28'): - extra_cmd = ['--initial-branch', 'master'] - else: - extra_cmd = [] - - self._create_project(path) - self._git(['init'] + extra_cmd, path) - self._git_config(path) - self._git(['add', '.'], path) - self._git(['commit', '-m', 'Initial commit'], path) - - def _git_create_remote_repo(self, name): - self._git_create_repo(self.root_dir / name) - - def _git_create_local_repo(self, name): - self._git_create_repo(self.subprojects_dir / name) - - def _git_create_remote_commit(self, name, branch): - self._git_remote(['checkout', branch], name) - self._git_remote(['commit', '--allow-empty', '-m', f'initial {branch} commit'], name) - - def _git_create_remote_branch(self, name, branch): - self._git_remote(['checkout', '-b', branch], name) - self._git_remote(['commit', '--allow-empty', '-m', f'initial {branch} commit'], name) - - def _git_create_remote_tag(self, name, tag): - self._git_remote(['commit', '--allow-empty', '-m', f'tag {tag} commit'], name) - self._git_remote(['tag', tag], name) - - def _wrap_create_git(self, name, revision='master'): - path = self.root_dir / name - with open(str((self.subprojects_dir / name).with_suffix('.wrap')), 'w', encoding='utf-8') as f: - f.write(textwrap.dedent( - ''' - [wrap-git] - url={} - revision={} - '''.format(os.path.abspath(str(path)), revision))) - - def _wrap_create_file(self, name, tarball='dummy.tar.gz'): - path = self.root_dir / tarball - with open(str((self.subprojects_dir / name).with_suffix('.wrap')), 'w', encoding='utf-8') as f: - f.write(textwrap.dedent( - f''' - [wrap-file] - source_url={os.path.abspath(str(path))} - source_filename={tarball} - ''')) - Path(self.packagecache_dir / tarball).touch() - - def _subprojects_cmd(self, args): - return self._run(self.meson_command + ['subprojects'] + args, workdir=str(self.project_dir)) - - def test_git_update(self): - subp_name = 'sub1' - - # Create a fake remote git repository and a wrap file. Checks that - # "meson subprojects download" works. - self._git_create_remote_repo(subp_name) - self._wrap_create_git(subp_name) - self._subprojects_cmd(['download']) - self.assertPathExists(str(self.subprojects_dir / subp_name)) - self._git_config(self.subprojects_dir / subp_name) - - # Create a new remote branch and update the wrap file. Checks that - # "meson subprojects update --reset" checkout the new branch. - self._git_create_remote_branch(subp_name, 'newbranch') - self._wrap_create_git(subp_name, 'newbranch') - self._subprojects_cmd(['update', '--reset']) - self.assertEqual(self._git_local_branch(subp_name), 'newbranch') - self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch')) - - # Update remote newbranch. Checks the new commit is pulled into existing - # local newbranch. Make sure it does not print spurious 'git stash' message. - self._git_create_remote_commit(subp_name, 'newbranch') - out = self._subprojects_cmd(['update', '--reset']) - self.assertNotIn('No local changes to save', out) - self.assertEqual(self._git_local_branch(subp_name), 'newbranch') - self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch')) - - # Update remote newbranch and switch to another branch. Checks that it - # switch current branch to newbranch and pull latest commit. - self._git_local(['checkout', 'master'], subp_name) - self._git_create_remote_commit(subp_name, 'newbranch') - self._subprojects_cmd(['update', '--reset']) - self.assertEqual(self._git_local_branch(subp_name), 'newbranch') - self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch')) - - # Stage some local changes then update. Checks that local changes got - # stashed. - self._create_project(self.subprojects_dir / subp_name, 'new_project_name') - self._git_local(['add', '.'], subp_name) - self._git_create_remote_commit(subp_name, 'newbranch') - self._subprojects_cmd(['update', '--reset']) - self.assertEqual(self._git_local_branch(subp_name), 'newbranch') - self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newbranch')) - self.assertTrue(self._git_local(['stash', 'list'], subp_name)) - - # Create a new remote tag and update the wrap file. Checks that - # "meson subprojects update --reset" checkout the new tag in detached mode. - self._git_create_remote_tag(subp_name, 'newtag') - self._wrap_create_git(subp_name, 'newtag') - self._subprojects_cmd(['update', '--reset']) - self.assertEqual(self._git_local_branch(subp_name), '') - self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name, 'newtag')) - - # Create a new remote commit and update the wrap file with the commit id. - # Checks that "meson subprojects update --reset" checkout the new commit - # in detached mode. - self._git_local(['checkout', 'master'], subp_name) - self._git_create_remote_commit(subp_name, 'newbranch') - new_commit = self._git_remote(['rev-parse', 'HEAD'], subp_name) - self._wrap_create_git(subp_name, new_commit) - self._subprojects_cmd(['update', '--reset']) - self.assertEqual(self._git_local_branch(subp_name), '') - self.assertEqual(self._git_local_commit(subp_name), new_commit) - - # Create a local project not in a git repository, then update it with - # a git wrap. Without --reset it should print error message and return - # failure. With --reset it should delete existing project and clone the - # new project. - subp_name = 'sub2' - self._create_project(self.subprojects_dir / subp_name) - self._git_create_remote_repo(subp_name) - self._wrap_create_git(subp_name) - with self.assertRaises(subprocess.CalledProcessError) as cm: - self._subprojects_cmd(['update']) - self.assertIn('Not a git repository', cm.exception.output) - self._subprojects_cmd(['update', '--reset']) - self.assertEqual(self._git_local_commit(subp_name), self._git_remote_commit(subp_name)) - - @skipIfNoExecutable('true') - def test_foreach(self): - self._create_project(self.subprojects_dir / 'sub_file') - self._wrap_create_file('sub_file') - self._git_create_local_repo('sub_git') - self._wrap_create_git('sub_git') - self._git_create_local_repo('sub_git_no_wrap') - - def ran_in(s): - ret = [] - prefix = 'Executing command in ' - for l in s.splitlines(): - if l.startswith(prefix): - ret.append(l[len(prefix):]) - return sorted(ret) - - dummy_cmd = ['true'] - out = self._subprojects_cmd(['foreach'] + dummy_cmd) - self.assertEqual(ran_in(out), sorted(['subprojects/sub_file', 'subprojects/sub_git', 'subprojects/sub_git_no_wrap'])) - out = self._subprojects_cmd(['foreach', '--types', 'git,file'] + dummy_cmd) - self.assertEqual(ran_in(out), sorted(['subprojects/sub_file', 'subprojects/sub_git'])) - out = self._subprojects_cmd(['foreach', '--types', 'file'] + dummy_cmd) - self.assertEqual(ran_in(out), ['subprojects/sub_file']) - out = self._subprojects_cmd(['foreach', '--types', 'git'] + dummy_cmd) - self.assertEqual(ran_in(out), ['subprojects/sub_git']) - - def test_purge(self): - self._create_project(self.subprojects_dir / 'sub_file') - self._wrap_create_file('sub_file') - self._git_create_local_repo('sub_git') - self._wrap_create_git('sub_git') - - sub_file_subprojects_dir = self.subprojects_dir / 'sub_file' / 'subprojects' - sub_file_subprojects_dir.mkdir(exist_ok=True, parents=True) - real_dir = Path('sub_file') / 'subprojects' / 'real' - - self._wrap_create_file(real_dir, tarball='dummy2.tar.gz') - - with open(str((self.subprojects_dir / 'redirect').with_suffix('.wrap')), 'w', encoding='utf-8') as f: - f.write(textwrap.dedent( - f''' - [wrap-redirect] - filename = {real_dir}.wrap - ''')) - - def deleting(s: str) -> T.List[str]: - ret = [] - prefix = 'Deleting ' - for l in s.splitlines(): - if l.startswith(prefix): - ret.append(l[len(prefix):]) - return sorted(ret) - - out = self._subprojects_cmd(['purge']) - self.assertEqual(deleting(out), sorted([ - str(self.subprojects_dir / 'redirect.wrap'), - str(self.subprojects_dir / 'sub_file'), - str(self.subprojects_dir / 'sub_git'), - ])) - out = self._subprojects_cmd(['purge', '--include-cache']) - self.assertEqual(deleting(out), sorted([ - str(self.subprojects_dir / 'sub_git'), - str(self.subprojects_dir / 'redirect.wrap'), - str(self.subprojects_dir / 'packagecache' / 'dummy.tar.gz'), - str(self.subprojects_dir / 'packagecache' / 'dummy2.tar.gz'), - str(self.subprojects_dir / 'sub_file'), - ])) - out = self._subprojects_cmd(['purge', '--include-cache', '--confirm']) - self.assertEqual(deleting(out), sorted([ - str(self.subprojects_dir / 'sub_git'), - str(self.subprojects_dir / 'redirect.wrap'), - str(self.subprojects_dir / 'packagecache' / 'dummy.tar.gz'), - str(self.subprojects_dir / 'packagecache' / 'dummy2.tar.gz'), - str(self.subprojects_dir / 'sub_file'), - ])) - self.assertFalse(Path(self.subprojects_dir / 'packagecache' / 'dummy.tar.gz').exists()) - self.assertFalse(Path(self.subprojects_dir / 'sub_file').exists()) - self.assertFalse(Path(self.subprojects_dir / 'sub_git').exists()) - self.assertFalse(Path(self.subprojects_dir / 'redirect.wrap').exists()) - -def _clang_at_least(compiler: 'Compiler', minver: str, apple_minver: T.Optional[str]) -> bool: - """ - check that Clang compiler is at least a specified version, whether AppleClang or regular Clang - - Parameters - ---------- - compiler: - Meson compiler object - minver: str - Clang minimum version - apple_minver: str - AppleCLang minimum version - - Returns - ------- - at_least: bool - Clang is at least the specified version - """ - if isinstance(compiler, (mesonbuild.compilers.AppleClangCCompiler, - mesonbuild.compilers.AppleClangCPPCompiler)): - if apple_minver is None: - return False - return version_compare(compiler.version, apple_minver) - return version_compare(compiler.version, minver) +from unittests.allplatformstests import AllPlatformTests +from unittests.darwintests import DarwinTests +from unittests.failuretests import FailureTests +from unittests.linuxcrosstests import LinuxCrossArmTests, LinuxCrossMingwTests +from unittests.machinefiletests import NativeFileTests, CrossFileTests +from unittests.rewritetests import RewriterTests +from unittests.taptests import TAPParserTests +from unittests.datatests import DataTests +from unittests.internaltests import InternalTests +from unittests.linuxliketests import LinuxlikeTests +from unittests.pythontests import PythonTests +from unittests.subprojectscommandtests import SubprojectsCommandTests +from unittests.windowstests import WindowsTests def unset_envs(): # For unit tests we must fully control all command lines -- cgit v1.1