parent
dbf2ace6ca
commit
7eb4c23156
20 changed files with 10737 additions and 10209 deletions
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,438 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import subprocess |
||||
import re |
||||
import json |
||||
import tempfile |
||||
import os |
||||
from unittest import mock, TestCase, SkipTest |
||||
from pathlib import PurePath |
||||
|
||||
import mesonbuild.mlog |
||||
import mesonbuild.depfile |
||||
import mesonbuild.dependencies.base |
||||
import mesonbuild.dependencies.factory |
||||
import mesonbuild.compilers |
||||
import mesonbuild.envconfig |
||||
import mesonbuild.environment |
||||
import mesonbuild.coredata |
||||
import mesonbuild.modules.gnome |
||||
from mesonbuild.mesonlib import ( |
||||
is_cygwin, windows_proof_rmtree, python_command |
||||
) |
||||
import mesonbuild.modules.pkgconfig |
||||
|
||||
|
||||
from run_tests import ( |
||||
Backend, ensure_backend_detects_changes, get_backend_commands, |
||||
get_builddir_target_args, get_meson_script, run_configure_inprocess, |
||||
run_mtest_inprocess |
||||
) |
||||
|
||||
|
||||
class BasePlatformTests(TestCase): |
||||
prefix = '/usr' |
||||
libdir = 'lib' |
||||
|
||||
def setUp(self): |
||||
super().setUp() |
||||
self.maxDiff = None |
||||
src_root = str(PurePath(__file__).parents[1]) |
||||
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 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 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 SkipTest: |
||||
raise 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 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 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 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 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 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 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 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) |
||||
|
@ -0,0 +1,151 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import subprocess |
||||
import re |
||||
import os |
||||
import unittest |
||||
|
||||
from mesonbuild.mesonlib import ( |
||||
MachineChoice, is_osx |
||||
) |
||||
from mesonbuild.compilers import ( |
||||
detect_c_compiler |
||||
) |
||||
|
||||
|
||||
from run_tests import ( |
||||
get_fake_env |
||||
) |
||||
|
||||
from .baseplatformtests import BasePlatformTests |
||||
from .helpers import * |
||||
|
||||
@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']) |
||||
|
@ -0,0 +1,272 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import re |
||||
import textwrap |
||||
import unittest |
||||
import hashlib |
||||
from itertools import chain |
||||
from pathlib import Path |
||||
import typing as T |
||||
|
||||
import mesonbuild.mlog |
||||
import mesonbuild.depfile |
||||
import mesonbuild.dependencies.base |
||||
import mesonbuild.dependencies.factory |
||||
import mesonbuild.envconfig |
||||
import mesonbuild.environment |
||||
import mesonbuild.coredata |
||||
import mesonbuild.modules.gnome |
||||
from mesonbuild.interpreter import Interpreter |
||||
from mesonbuild.ast import AstInterpreter |
||||
from mesonbuild.mesonlib import ( |
||||
MachineChoice, OptionKey |
||||
) |
||||
from mesonbuild.compilers import ( |
||||
detect_c_compiler, detect_cpp_compiler |
||||
) |
||||
import mesonbuild.modules.pkgconfig |
||||
|
||||
|
||||
from run_tests import ( |
||||
FakeBuild, get_fake_env |
||||
) |
||||
|
||||
from .helpers import * |
||||
|
||||
@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__).parents[1] |
||||
|
||||
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') |
||||
|
@ -0,0 +1,393 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import subprocess |
||||
import tempfile |
||||
import os |
||||
import shutil |
||||
import unittest |
||||
from contextlib import contextmanager |
||||
|
||||
from mesonbuild.mesonlib import ( |
||||
MachineChoice, is_windows, is_osx, windows_proof_rmtree, windows_proof_rm |
||||
) |
||||
from mesonbuild.compilers import ( |
||||
detect_objc_compiler, detect_objcpp_compiler |
||||
) |
||||
from mesonbuild.mesonlib import EnvironmentException, MesonException |
||||
from mesonbuild.programs import ExternalProgram |
||||
|
||||
|
||||
from run_tests import ( |
||||
get_fake_env |
||||
) |
||||
|
||||
from .baseplatformtests import BasePlatformTests |
||||
from .helpers import * |
||||
|
||||
@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 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") |
||||
|
@ -0,0 +1,172 @@ |
||||
import subprocess |
||||
import os |
||||
import shutil |
||||
import unittest |
||||
import functools |
||||
import re |
||||
import typing as T |
||||
from contextlib import contextmanager |
||||
|
||||
from mesonbuild.compilers import detect_c_compiler, compiler_from_language |
||||
from mesonbuild.mesonlib import ( |
||||
MachineChoice, is_osx, is_cygwin, EnvironmentException, OptionKey, MachineChoice |
||||
) |
||||
from run_tests import get_fake_env |
||||
|
||||
|
||||
def is_ci(): |
||||
if 'CI' in os.environ: |
||||
return True |
||||
return False |
||||
|
||||
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 |
||||
|
||||
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 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 is_tarball(): |
||||
if not os.path.isdir('docs'): |
||||
return True |
||||
return False |
||||
|
||||
@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 |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,193 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import os |
||||
import shutil |
||||
import unittest |
||||
import platform |
||||
|
||||
from mesonbuild.mesonlib import ( |
||||
is_windows, is_cygwin |
||||
) |
||||
from mesonbuild.mesonlib import MesonException |
||||
|
||||
|
||||
|
||||
from .baseplatformtests import BasePlatformTests |
||||
from .helpers import * |
||||
|
||||
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() |
||||
self.meson_cross_file = os.path.join(self.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() |
||||
self.meson_cross_file = os.path.join(self.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'), |
||||
]) |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,936 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import subprocess |
||||
import tempfile |
||||
import textwrap |
||||
import os |
||||
import shutil |
||||
import functools |
||||
import threading |
||||
import sys |
||||
from itertools import chain |
||||
from unittest import mock, skipIf, SkipTest |
||||
from pathlib import Path |
||||
import typing as T |
||||
|
||||
import mesonbuild.mlog |
||||
import mesonbuild.depfile |
||||
import mesonbuild.dependencies.factory |
||||
import mesonbuild.envconfig |
||||
import mesonbuild.environment |
||||
import mesonbuild.coredata |
||||
import mesonbuild.modules.gnome |
||||
from mesonbuild.mesonlib import ( |
||||
MachineChoice, is_windows, is_osx, is_cygwin, is_haiku, is_sunos |
||||
) |
||||
from mesonbuild.compilers import ( |
||||
detect_swift_compiler, compiler_from_language |
||||
) |
||||
import mesonbuild.modules.pkgconfig |
||||
|
||||
|
||||
from run_tests import ( |
||||
get_fake_env |
||||
) |
||||
|
||||
from .baseplatformtests import BasePlatformTests |
||||
from .helpers import * |
||||
|
||||
@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 |
||||
|
||||
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. |
||||
@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 SkipTest('Skipped due to problems with LLVM on MSYS2') |
||||
if not shutil.which('llvm-config'): |
||||
raise 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 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 SkipTest('Not running Python 2 tests because dev packages not installed.') |
||||
self._simple_test('python', binary, entry='python') |
||||
|
||||
@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 SkipTest('Only one compiler found, cannot test.') |
||||
return 'clang', 'clang' |
||||
if not is_real_gnu_compiler(shutil.which('gcc')): |
||||
raise SkipTest('Only one compiler found, cannot test.') |
||||
return 'gcc', 'gcc' |
||||
self.helper_for_compiler('c', cb) |
||||
|
||||
@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 SkipTest('Only one compiler found, cannot test.') |
||||
return 'clang++', 'clang' |
||||
if not is_real_gnu_compiler(shutil.which('g++')): |
||||
raise 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 SkipTest('Only one compiler found, cannot test.') |
||||
return 'clang', 'clang' |
||||
if not is_real_gnu_compiler(shutil.which('gcc')): |
||||
raise 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 SkipTest('Only one compiler found, cannot test.') |
||||
return 'clang++', 'clang' |
||||
if not is_real_gnu_compiler(shutil.which('g++')): |
||||
raise 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 SkipTest('No alternative dlang compiler found.') |
||||
if shutil.which('dmd'): |
||||
return 'dmd', 'dmd' |
||||
raise 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 SkipTest('No alternate C# implementation.') |
||||
return 'mcs', 'mcs' |
||||
if not shutil.which('csc'): |
||||
raise 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 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 SkipTest('No alternate Fortran implementation.') |
||||
if not shutil.which('gfortran'): |
||||
raise 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 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. |
||||
@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 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.') |
||||
|
@ -0,0 +1,83 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import os |
||||
import unittest |
||||
|
||||
from run_tests import ( |
||||
Backend |
||||
) |
||||
|
||||
from .baseplatformtests import BasePlatformTests |
||||
|
||||
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() |
||||
|
@ -0,0 +1,348 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import subprocess |
||||
import json |
||||
import os |
||||
import unittest |
||||
from distutils.dir_util import copy_tree |
||||
|
||||
from .baseplatformtests import BasePlatformTests |
||||
|
||||
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) |
||||
|
@ -0,0 +1,287 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import subprocess |
||||
import tempfile |
||||
import textwrap |
||||
import os |
||||
from pathlib import Path |
||||
import typing as T |
||||
|
||||
from mesonbuild.mesonlib import ( |
||||
version_compare, git, search_version |
||||
) |
||||
|
||||
|
||||
|
||||
from .baseplatformtests import BasePlatformTests |
||||
from .helpers import * |
||||
|
||||
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()) |
||||
|
@ -0,0 +1,292 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import unittest |
||||
import io |
||||
|
||||
from mesonbuild.mtest import TAPParser, TestResult |
||||
|
||||
|
||||
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) |
||||
|
@ -0,0 +1,362 @@ |
||||
# Copyright 2016-2021 The Meson development team |
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
import subprocess |
||||
import re |
||||
import os |
||||
import shutil |
||||
from unittest import mock, SkipTest, skipUnless, skipIf |
||||
from glob import glob |
||||
|
||||
import mesonbuild.mlog |
||||
import mesonbuild.depfile |
||||
import mesonbuild.dependencies.factory |
||||
import mesonbuild.envconfig |
||||
import mesonbuild.environment |
||||
import mesonbuild.coredata |
||||
import mesonbuild.modules.gnome |
||||
from mesonbuild.mesonlib import ( |
||||
MachineChoice, is_windows, is_cygwin, python_command, version_compare, |
||||
EnvironmentException, OptionKey |
||||
) |
||||
from mesonbuild.compilers import ( |
||||
detect_c_compiler, detect_d_compiler, compiler_from_language, |
||||
GnuLikeCompiler |
||||
) |
||||
from mesonbuild.programs import ExternalProgram |
||||
import mesonbuild.dependencies.base |
||||
import mesonbuild.modules.pkgconfig |
||||
|
||||
|
||||
from run_tests import ( |
||||
Backend, get_fake_env |
||||
) |
||||
|
||||
from .baseplatformtests import BasePlatformTests |
||||
from .helpers import * |
||||
|
||||
@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') |
||||
|
||||
@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 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 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 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.assertIn('prog.pdb', files) |
||||
|
||||
def _check_ld(self, name: str, lang: str, expected: str) -> None: |
||||
if not shutil.which(name): |
||||
raise 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 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, GnuLikeCompiler): |
||||
raise 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, GnuLikeCompiler): |
||||
raise 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, GnuLikeCompiler): |
||||
raise 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 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 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 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 SkipTest('QMake not found') |
||||
output = subprocess.getoutput('qmake --version') |
||||
if 'Qt version 5' not in output and not is_ci(): |
||||
raise 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 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 SkipTest(f'C++ modules only work with the Ninja backend (not {self.backend.name}).') |
||||
if 'VSCMD_VER' not in os.environ: |
||||
raise SkipTest('C++ modules is only supported with Visual Studio.') |
||||
if version_compare(os.environ['VSCMD_VER'], '<16.10.0'): |
||||
raise 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() |
||||
|
Loading…
Reference in new issue