aboutsummaryrefslogtreecommitdiff
path: root/run_unittests.py
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.com>2021-07-24 18:31:45 -0400
committerXavier Claessens <xclaesse@gmail.com>2021-07-26 15:19:13 -0400
commit7eb4c231561dfc5a322814e587b9b89969745367 (patch)
tree75b078a932ebb1726dadb9cb56ab2f6f8d1dd9e4 /run_unittests.py
parentdbf2ace6ca1ce39aa01497f815b65856079cc581 (diff)
downloadmeson-7eb4c231561dfc5a322814e587b9b89969745367.zip
meson-7eb4c231561dfc5a322814e587b9b89969745367.tar.gz
meson-7eb4c231561dfc5a322814e587b9b89969745367.tar.bz2
Split run_unittests.py file
Diffstat (limited to 'run_unittests.py')
-rwxr-xr-xrun_unittests.py10223
1 files changed, 14 insertions, 10209 deletions
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 <teh_coderz@example.com>', 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<stdio.h>
-
- 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 <class 'str'>"),
- ("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