1#!/usr/bin/env python3 2# Copyright 2016-2017 The Meson development team 3 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7 8# http://www.apache.org/licenses/LICENSE-2.0 9 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import typing as T 17import stat 18import subprocess 19import re 20import json 21import tempfile 22import textwrap 23import os 24import shutil 25import sys 26import unittest 27import platform 28import pickle 29import functools 30import io 31import operator 32import threading 33import urllib.error 34import urllib.request 35import zipfile 36import hashlib 37from itertools import chain 38from unittest import mock 39from configparser import ConfigParser 40from contextlib import contextmanager 41from glob import glob 42from pathlib import (PurePath, Path) 43from distutils.dir_util import copy_tree 44import typing as T 45 46import mesonbuild.mlog 47import mesonbuild.depfile 48import mesonbuild.dependencies.base 49import mesonbuild.compilers 50import mesonbuild.envconfig 51import mesonbuild.environment 52import mesonbuild.mesonlib 53import mesonbuild.coredata 54import mesonbuild.modules.gnome 55from mesonbuild.interpreter import Interpreter, ObjectHolder 56from mesonbuild.ast import AstInterpreter 57from mesonbuild.mesonlib import ( 58 BuildDirLock, LibType, MachineChoice, PerMachine, Version, is_windows, 59 is_osx, is_cygwin, is_dragonflybsd, is_openbsd, is_haiku, is_sunos, 60 windows_proof_rmtree, python_command, version_compare, split_args, 61 quote_arg, relpath, is_linux 62) 63from mesonbuild.environment import detect_ninja 64from mesonbuild.mesonlib import MesonException, EnvironmentException 65from mesonbuild.dependencies import PkgConfigDependency, ExternalProgram 66import mesonbuild.dependencies.base 67from mesonbuild.build import Target, ConfigurationData 68import mesonbuild.modules.pkgconfig 69 70from mesonbuild.mtest import TAPParser, TestResult 71 72from run_tests import ( 73 Backend, FakeBuild, FakeCompilerOptions, 74 ensure_backend_detects_changes, exe_suffix, get_backend_commands, 75 get_builddir_target_args, get_fake_env, get_fake_options, get_meson_script, 76 run_configure_inprocess, run_mtest_inprocess 77) 78 79 80URLOPEN_TIMEOUT = 5 81 82@contextmanager 83def chdir(path: str): 84 curdir = os.getcwd() 85 os.chdir(path) 86 yield 87 os.chdir(curdir) 88 89 90def get_dynamic_section_entry(fname, entry): 91 if is_cygwin() or is_osx(): 92 raise unittest.SkipTest('Test only applicable to ELF platforms') 93 94 try: 95 raw_out = subprocess.check_output(['readelf', '-d', fname], 96 universal_newlines=True) 97 except FileNotFoundError: 98 # FIXME: Try using depfixer.py:Elf() as a fallback 99 raise unittest.SkipTest('readelf not found') 100 pattern = re.compile(entry + r': \[(.*?)\]') 101 for line in raw_out.split('\n'): 102 m = pattern.search(line) 103 if m is not None: 104 return m.group(1) 105 return None # The file did not contain the specified entry. 106 107def get_soname(fname): 108 return get_dynamic_section_entry(fname, 'soname') 109 110def get_rpath(fname): 111 return get_dynamic_section_entry(fname, r'(?:rpath|runpath)') 112 113def is_tarball(): 114 if not os.path.isdir('docs'): 115 return True 116 return False 117 118def is_ci(): 119 if 'CI' in os.environ: 120 return True 121 return False 122 123def is_pull(): 124 # Travis 125 if os.environ.get('TRAVIS_PULL_REQUEST', 'false') != 'false': 126 return True 127 # Azure 128 if 'SYSTEM_PULLREQUEST_ISFORK' in os.environ: 129 return True 130 return False 131 132def _git_init(project_dir): 133 subprocess.check_call(['git', 'init'], cwd=project_dir, stdout=subprocess.DEVNULL) 134 subprocess.check_call(['git', 'config', 135 'user.name', 'Author Person'], cwd=project_dir) 136 subprocess.check_call(['git', 'config', 137 'user.email', 'teh_coderz@example.com'], cwd=project_dir) 138 subprocess.check_call('git add *', cwd=project_dir, shell=True, 139 stdout=subprocess.DEVNULL) 140 subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir, 141 stdout=subprocess.DEVNULL) 142 143@functools.lru_cache() 144def is_real_gnu_compiler(path): 145 ''' 146 Check if the gcc we have is a real gcc and not a macOS wrapper around clang 147 ''' 148 if not path: 149 return False 150 out = subprocess.check_output([path, '--version'], universal_newlines=True, stderr=subprocess.STDOUT) 151 return 'Free Software Foundation' in out 152 153def skipIfNoExecutable(exename): 154 ''' 155 Skip this test if the given executable is not found. 156 ''' 157 def wrapper(func): 158 @functools.wraps(func) 159 def wrapped(*args, **kwargs): 160 if shutil.which(exename) is None: 161 raise unittest.SkipTest(exename + ' not found') 162 return func(*args, **kwargs) 163 return wrapped 164 return wrapper 165 166def skipIfNoPkgconfig(f): 167 ''' 168 Skip this test if no pkg-config is found, unless we're on CI. 169 This allows users to run our test suite without having 170 pkg-config installed on, f.ex., macOS, while ensuring that our CI does not 171 silently skip the test because of misconfiguration. 172 173 Note: Yes, we provide pkg-config even while running Windows CI 174 ''' 175 @functools.wraps(f) 176 def wrapped(*args, **kwargs): 177 if not is_ci() and shutil.which('pkg-config') is None: 178 raise unittest.SkipTest('pkg-config not found') 179 return f(*args, **kwargs) 180 return wrapped 181 182def skipIfNoPkgconfigDep(depname): 183 ''' 184 Skip this test if the given pkg-config dep is not found, unless we're on CI. 185 ''' 186 def wrapper(func): 187 @functools.wraps(func) 188 def wrapped(*args, **kwargs): 189 if not is_ci() and shutil.which('pkg-config') is None: 190 raise unittest.SkipTest('pkg-config not found') 191 if not is_ci() and subprocess.call(['pkg-config', '--exists', depname]) != 0: 192 raise unittest.SkipTest('pkg-config dependency {} not found.'.format(depname)) 193 return func(*args, **kwargs) 194 return wrapped 195 return wrapper 196 197def skip_if_no_cmake(f): 198 ''' 199 Skip this test if no cmake is found, unless we're on CI. 200 This allows users to run our test suite without having 201 cmake installed on, f.ex., macOS, while ensuring that our CI does not 202 silently skip the test because of misconfiguration. 203 ''' 204 @functools.wraps(f) 205 def wrapped(*args, **kwargs): 206 if not is_ci() and shutil.which('cmake') is None: 207 raise unittest.SkipTest('cmake not found') 208 return f(*args, **kwargs) 209 return wrapped 210 211def skip_if_not_language(lang): 212 def wrapper(func): 213 @functools.wraps(func) 214 def wrapped(*args, **kwargs): 215 try: 216 env = get_fake_env() 217 f = getattr(env, 'detect_{}_compiler'.format(lang)) 218 f(MachineChoice.HOST) 219 except EnvironmentException: 220 raise unittest.SkipTest('No {} compiler found.'.format(lang)) 221 return func(*args, **kwargs) 222 return wrapped 223 return wrapper 224 225def skip_if_env_set(key): 226 ''' 227 Skip a test if a particular env is set, except when running under CI 228 ''' 229 def wrapper(func): 230 @functools.wraps(func) 231 def wrapped(*args, **kwargs): 232 old = None 233 if key in os.environ: 234 if not is_ci(): 235 raise unittest.SkipTest('Env var {!r} set, skipping'.format(key)) 236 old = os.environ.pop(key) 237 try: 238 return func(*args, **kwargs) 239 finally: 240 if old is not None: 241 os.environ[key] = old 242 return wrapped 243 return wrapper 244 245def skip_if_not_base_option(feature): 246 """Skip tests if The compiler does not support a given base option. 247 248 for example, ICC doesn't currently support b_sanitize. 249 """ 250 def actual(f): 251 @functools.wraps(f) 252 def wrapped(*args, **kwargs): 253 env = get_fake_env() 254 cc = env.detect_c_compiler(MachineChoice.HOST) 255 if feature not in cc.base_options: 256 raise unittest.SkipTest( 257 '{} not available with {}'.format(feature, cc.id)) 258 return f(*args, **kwargs) 259 return wrapped 260 return actual 261 262 263@contextmanager 264def temp_filename(): 265 '''A context manager which provides a filename to an empty temporary file. 266 267 On exit the file will be deleted. 268 ''' 269 270 fd, filename = tempfile.mkstemp() 271 os.close(fd) 272 try: 273 yield filename 274 finally: 275 try: 276 os.remove(filename) 277 except OSError: 278 pass 279 280@contextmanager 281def no_pkgconfig(): 282 ''' 283 A context manager that overrides shutil.which and ExternalProgram to force 284 them to return None for pkg-config to simulate it not existing. 285 ''' 286 old_which = shutil.which 287 old_search = ExternalProgram._search 288 289 def new_search(self, name, search_dir): 290 if name == 'pkg-config': 291 return [None] 292 return old_search(self, name, search_dir) 293 294 def new_which(cmd, *kwargs): 295 if cmd == 'pkg-config': 296 return None 297 return old_which(cmd, *kwargs) 298 299 shutil.which = new_which 300 ExternalProgram._search = new_search 301 try: 302 yield 303 finally: 304 shutil.which = old_which 305 ExternalProgram._search = old_search 306 307 308class InternalTests(unittest.TestCase): 309 310 def test_version_number(self): 311 searchfunc = mesonbuild.environment.search_version 312 self.assertEqual(searchfunc('foobar 1.2.3'), '1.2.3') 313 self.assertEqual(searchfunc('1.2.3'), '1.2.3') 314 self.assertEqual(searchfunc('foobar 2016.10.28 1.2.3'), '1.2.3') 315 self.assertEqual(searchfunc('2016.10.28 1.2.3'), '1.2.3') 316 self.assertEqual(searchfunc('foobar 2016.10.128'), '2016.10.128') 317 self.assertEqual(searchfunc('2016.10.128'), '2016.10.128') 318 self.assertEqual(searchfunc('2016.10'), '2016.10') 319 self.assertEqual(searchfunc('2016.10 1.2.3'), '1.2.3') 320 self.assertEqual(searchfunc('oops v1.2.3'), '1.2.3') 321 self.assertEqual(searchfunc('2016.oops 1.2.3'), '1.2.3') 322 self.assertEqual(searchfunc('2016.x'), 'unknown version') 323 324 325 def test_mode_symbolic_to_bits(self): 326 modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits 327 self.assertEqual(modefunc('---------'), 0) 328 self.assertEqual(modefunc('r--------'), stat.S_IRUSR) 329 self.assertEqual(modefunc('---r-----'), stat.S_IRGRP) 330 self.assertEqual(modefunc('------r--'), stat.S_IROTH) 331 self.assertEqual(modefunc('-w-------'), stat.S_IWUSR) 332 self.assertEqual(modefunc('----w----'), stat.S_IWGRP) 333 self.assertEqual(modefunc('-------w-'), stat.S_IWOTH) 334 self.assertEqual(modefunc('--x------'), stat.S_IXUSR) 335 self.assertEqual(modefunc('-----x---'), stat.S_IXGRP) 336 self.assertEqual(modefunc('--------x'), stat.S_IXOTH) 337 self.assertEqual(modefunc('--S------'), stat.S_ISUID) 338 self.assertEqual(modefunc('-----S---'), stat.S_ISGID) 339 self.assertEqual(modefunc('--------T'), stat.S_ISVTX) 340 self.assertEqual(modefunc('--s------'), stat.S_ISUID | stat.S_IXUSR) 341 self.assertEqual(modefunc('-----s---'), stat.S_ISGID | stat.S_IXGRP) 342 self.assertEqual(modefunc('--------t'), stat.S_ISVTX | stat.S_IXOTH) 343 self.assertEqual(modefunc('rwx------'), stat.S_IRWXU) 344 self.assertEqual(modefunc('---rwx---'), stat.S_IRWXG) 345 self.assertEqual(modefunc('------rwx'), stat.S_IRWXO) 346 # We could keep listing combinations exhaustively but that seems 347 # tedious and pointless. Just test a few more. 348 self.assertEqual(modefunc('rwxr-xr-x'), 349 stat.S_IRWXU | 350 stat.S_IRGRP | stat.S_IXGRP | 351 stat.S_IROTH | stat.S_IXOTH) 352 self.assertEqual(modefunc('rw-r--r--'), 353 stat.S_IRUSR | stat.S_IWUSR | 354 stat.S_IRGRP | 355 stat.S_IROTH) 356 self.assertEqual(modefunc('rwsr-x---'), 357 stat.S_IRWXU | stat.S_ISUID | 358 stat.S_IRGRP | stat.S_IXGRP) 359 360 def test_compiler_args_class_none_flush(self): 361 cc = mesonbuild.compilers.CCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock()) 362 a = cc.compiler_args(['-I.']) 363 #first we are checking if the tree construction deduplicates the correct -I argument 364 a += ['-I..'] 365 a += ['-I./tests/'] 366 a += ['-I./tests2/'] 367 #think this here as assertion, we cannot apply it, otherwise the CompilerArgs would already flush the changes: 368 # assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..', '-I.']) 369 a += ['-I.'] 370 a += ['-I.', '-I./tests/'] 371 self.assertEqual(a, ['-I.', '-I./tests/', '-I./tests2/', '-I..']) 372 373 #then we are checking that when CompilerArgs already have a build container list, that the deduplication is taking the correct one 374 a += ['-I.', '-I./tests2/'] 375 self.assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..']) 376 377 def test_compiler_args_class_d(self): 378 d = mesonbuild.compilers.DCompiler([], 'fake', MachineChoice.HOST, 'info', 'arch', False, None) 379 # check include order is kept when deduplicating 380 a = d.compiler_args(['-Ifirst', '-Isecond', '-Ithird']) 381 a += ['-Ifirst'] 382 self.assertEqual(a, ['-Ifirst', '-Isecond', '-Ithird']) 383 384 def test_compiler_args_class(self): 385 cc = mesonbuild.compilers.CCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock()) 386 # Test that empty initialization works 387 a = cc.compiler_args() 388 self.assertEqual(a, []) 389 # Test that list initialization works 390 a = cc.compiler_args(['-I.', '-I..']) 391 self.assertEqual(a, ['-I.', '-I..']) 392 # Test that there is no de-dup on initialization 393 self.assertEqual(cc.compiler_args(['-I.', '-I.']), ['-I.', '-I.']) 394 395 ## Test that appending works 396 a.append('-I..') 397 self.assertEqual(a, ['-I..', '-I.']) 398 a.append('-O3') 399 self.assertEqual(a, ['-I..', '-I.', '-O3']) 400 401 ## Test that in-place addition works 402 a += ['-O2', '-O2'] 403 self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2', '-O2']) 404 # Test that removal works 405 a.remove('-O2') 406 self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2']) 407 # Test that de-dup happens on addition 408 a += ['-Ifoo', '-Ifoo'] 409 self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2']) 410 411 # .extend() is just +=, so we don't test it 412 413 ## Test that addition works 414 # Test that adding a list with just one old arg works and yields the same array 415 a = a + ['-Ifoo'] 416 self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2']) 417 # Test that adding a list with one arg new and one old works 418 a = a + ['-Ifoo', '-Ibaz'] 419 self.assertEqual(a, ['-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2']) 420 # Test that adding args that must be prepended and appended works 421 a = a + ['-Ibar', '-Wall'] 422 self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall']) 423 424 ## Test that reflected addition works 425 # Test that adding to a list with just one old arg works and yields the same array 426 a = ['-Ifoo'] + a 427 self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall']) 428 # Test that adding to a list with just one new arg that is not pre-pended works 429 a = ['-Werror'] + a 430 self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Werror', '-O3', '-O2', '-Wall']) 431 # Test that adding to a list with two new args preserves the order 432 a = ['-Ldir', '-Lbah'] + a 433 self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall']) 434 # Test that adding to a list with old args does nothing 435 a = ['-Ibar', '-Ibaz', '-Ifoo'] + a 436 self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall']) 437 438 ## Test that adding libraries works 439 l = cc.compiler_args(['-Lfoodir', '-lfoo']) 440 self.assertEqual(l, ['-Lfoodir', '-lfoo']) 441 # Adding a library and a libpath appends both correctly 442 l += ['-Lbardir', '-lbar'] 443 self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar']) 444 # Adding the same library again does nothing 445 l += ['-lbar'] 446 self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar']) 447 448 ## Test that 'direct' append and extend works 449 l = cc.compiler_args(['-Lfoodir', '-lfoo']) 450 self.assertEqual(l, ['-Lfoodir', '-lfoo']) 451 # Direct-adding a library and a libpath appends both correctly 452 l.extend_direct(['-Lbardir', '-lbar']) 453 self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar']) 454 # Direct-adding the same library again still adds it 455 l.append_direct('-lbar') 456 self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar']) 457 # Direct-adding with absolute path deduplicates 458 l.append_direct('/libbaz.a') 459 self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a']) 460 # Adding libbaz again does nothing 461 l.append_direct('/libbaz.a') 462 self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a']) 463 464 def test_compiler_args_class_gnuld(self): 465 ## Test --start/end-group 466 linker = mesonbuild.linkers.GnuDynamicLinker([], MachineChoice.HOST, 'fake', '-Wl,', []) 467 gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker) 468 ## Ensure that the fake compiler is never called by overriding the relevant function 469 gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include'] 470 ## Test that 'direct' append and extend works 471 l = gcc.compiler_args(['-Lfoodir', '-lfoo']) 472 self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group']) 473 # Direct-adding a library and a libpath appends both correctly 474 l.extend_direct(['-Lbardir', '-lbar']) 475 self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-Wl,--end-group']) 476 # Direct-adding the same library again still adds it 477 l.append_direct('-lbar') 478 self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '-Wl,--end-group']) 479 # Direct-adding with absolute path deduplicates 480 l.append_direct('/libbaz.a') 481 self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group']) 482 # Adding libbaz again does nothing 483 l.append_direct('/libbaz.a') 484 self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group']) 485 # Adding a non-library argument doesn't include it in the group 486 l += ['-Lfoo', '-Wl,--export-dynamic'] 487 self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group', '-Wl,--export-dynamic']) 488 # -Wl,-lfoo is detected as a library and gets added to the group 489 l.append('-Wl,-ldl') 490 self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--export-dynamic', '-Wl,-ldl', '-Wl,--end-group']) 491 492 def test_compiler_args_remove_system(self): 493 ## Test --start/end-group 494 linker = mesonbuild.linkers.GnuDynamicLinker([], MachineChoice.HOST, 'fake', '-Wl,', []) 495 gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker) 496 ## Ensure that the fake compiler is never called by overriding the relevant function 497 gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include'] 498 ## Test that 'direct' append and extend works 499 l = gcc.compiler_args(['-Lfoodir', '-lfoo']) 500 self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group']) 501 ## Test that to_native removes all system includes 502 l += ['-isystem/usr/include', '-isystem=/usr/share/include', '-DSOMETHING_IMPORTANT=1', '-isystem', '/usr/local/include'] 503 self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group', '-DSOMETHING_IMPORTANT=1']) 504 505 def test_string_templates_substitution(self): 506 dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict 507 substfunc = mesonbuild.mesonlib.substitute_values 508 ME = mesonbuild.mesonlib.MesonException 509 510 # Identity 511 self.assertEqual(dictfunc([], []), {}) 512 513 # One input, no outputs 514 inputs = ['bar/foo.c.in'] 515 outputs = [] 516 ret = dictfunc(inputs, outputs) 517 d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], 518 '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'} 519 # Check dictionary 520 self.assertEqual(ret, d) 521 # Check substitutions 522 cmd = ['some', 'ordinary', 'strings'] 523 self.assertEqual(substfunc(cmd, d), cmd) 524 cmd = ['@INPUT@.out', 'ordinary', 'strings'] 525 self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:]) 526 cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', 'strings'] 527 self.assertEqual(substfunc(cmd, d), 528 [inputs[0] + '.out'] + [d['@PLAINNAME@'] + '.ok'] + cmd[2:]) 529 cmd = ['@INPUT@', '@BASENAME@.hah', 'strings'] 530 self.assertEqual(substfunc(cmd, d), 531 inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:]) 532 cmd = ['@OUTPUT@'] 533 self.assertRaises(ME, substfunc, cmd, d) 534 535 # One input, one output 536 inputs = ['bar/foo.c.in'] 537 outputs = ['out.c'] 538 ret = dictfunc(inputs, outputs) 539 d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], 540 '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', 541 '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'} 542 # Check dictionary 543 self.assertEqual(ret, d) 544 # Check substitutions 545 cmd = ['some', 'ordinary', 'strings'] 546 self.assertEqual(substfunc(cmd, d), cmd) 547 cmd = ['@INPUT@.out', '@OUTPUT@', 'strings'] 548 self.assertEqual(substfunc(cmd, d), 549 [inputs[0] + '.out'] + outputs + cmd[2:]) 550 cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', '@OUTPUT0@'] 551 self.assertEqual(substfunc(cmd, d), 552 [inputs[0] + '.out', d['@PLAINNAME@'] + '.ok'] + outputs) 553 cmd = ['@INPUT@', '@BASENAME@.hah', 'strings'] 554 self.assertEqual(substfunc(cmd, d), 555 inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:]) 556 557 # One input, one output with a subdir 558 outputs = ['dir/out.c'] 559 ret = dictfunc(inputs, outputs) 560 d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], 561 '@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c', 562 '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'} 563 # Check dictionary 564 self.assertEqual(ret, d) 565 566 # Two inputs, no outputs 567 inputs = ['bar/foo.c.in', 'baz/foo.c.in'] 568 outputs = [] 569 ret = dictfunc(inputs, outputs) 570 d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1]} 571 # Check dictionary 572 self.assertEqual(ret, d) 573 # Check substitutions 574 cmd = ['some', 'ordinary', 'strings'] 575 self.assertEqual(substfunc(cmd, d), cmd) 576 cmd = ['@INPUT@', 'ordinary', 'strings'] 577 self.assertEqual(substfunc(cmd, d), inputs + cmd[1:]) 578 cmd = ['@INPUT0@.out', 'ordinary', 'strings'] 579 self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:]) 580 cmd = ['@INPUT0@.out', '@INPUT1@.ok', 'strings'] 581 self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:]) 582 cmd = ['@INPUT0@', '@INPUT1@', 'strings'] 583 self.assertEqual(substfunc(cmd, d), inputs + cmd[2:]) 584 # Many inputs, can't use @INPUT@ like this 585 cmd = ['@INPUT@.out', 'ordinary', 'strings'] 586 self.assertRaises(ME, substfunc, cmd, d) 587 # Not enough inputs 588 cmd = ['@INPUT2@.out', 'ordinary', 'strings'] 589 self.assertRaises(ME, substfunc, cmd, d) 590 # Too many inputs 591 cmd = ['@PLAINNAME@'] 592 self.assertRaises(ME, substfunc, cmd, d) 593 cmd = ['@BASENAME@'] 594 self.assertRaises(ME, substfunc, cmd, d) 595 # No outputs 596 cmd = ['@OUTPUT@'] 597 self.assertRaises(ME, substfunc, cmd, d) 598 cmd = ['@OUTPUT0@'] 599 self.assertRaises(ME, substfunc, cmd, d) 600 cmd = ['@OUTDIR@'] 601 self.assertRaises(ME, substfunc, cmd, d) 602 603 # Two inputs, one output 604 outputs = ['dir/out.c'] 605 ret = dictfunc(inputs, outputs) 606 d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1], 607 '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'} 608 # Check dictionary 609 self.assertEqual(ret, d) 610 # Check substitutions 611 cmd = ['some', 'ordinary', 'strings'] 612 self.assertEqual(substfunc(cmd, d), cmd) 613 cmd = ['@OUTPUT@', 'ordinary', 'strings'] 614 self.assertEqual(substfunc(cmd, d), outputs + cmd[1:]) 615 cmd = ['@OUTPUT@.out', 'ordinary', 'strings'] 616 self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out'] + cmd[1:]) 617 cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', 'strings'] 618 self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:]) 619 # Many inputs, can't use @INPUT@ like this 620 cmd = ['@INPUT@.out', 'ordinary', 'strings'] 621 self.assertRaises(ME, substfunc, cmd, d) 622 # Not enough inputs 623 cmd = ['@INPUT2@.out', 'ordinary', 'strings'] 624 self.assertRaises(ME, substfunc, cmd, d) 625 # Not enough outputs 626 cmd = ['@OUTPUT2@.out', 'ordinary', 'strings'] 627 self.assertRaises(ME, substfunc, cmd, d) 628 629 # Two inputs, two outputs 630 outputs = ['dir/out.c', 'dir/out2.c'] 631 ret = dictfunc(inputs, outputs) 632 d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1], 633 '@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1], 634 '@OUTDIR@': 'dir'} 635 # Check dictionary 636 self.assertEqual(ret, d) 637 # Check substitutions 638 cmd = ['some', 'ordinary', 'strings'] 639 self.assertEqual(substfunc(cmd, d), cmd) 640 cmd = ['@OUTPUT@', 'ordinary', 'strings'] 641 self.assertEqual(substfunc(cmd, d), outputs + cmd[1:]) 642 cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings'] 643 self.assertEqual(substfunc(cmd, d), outputs + cmd[2:]) 644 cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@'] 645 self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir']) 646 # Many inputs, can't use @INPUT@ like this 647 cmd = ['@INPUT@.out', 'ordinary', 'strings'] 648 self.assertRaises(ME, substfunc, cmd, d) 649 # Not enough inputs 650 cmd = ['@INPUT2@.out', 'ordinary', 'strings'] 651 self.assertRaises(ME, substfunc, cmd, d) 652 # Not enough outputs 653 cmd = ['@OUTPUT2@.out', 'ordinary', 'strings'] 654 self.assertRaises(ME, substfunc, cmd, d) 655 # Many outputs, can't use @OUTPUT@ like this 656 cmd = ['@OUTPUT@.out', 'ordinary', 'strings'] 657 self.assertRaises(ME, substfunc, cmd, d) 658 659 def test_needs_exe_wrapper_override(self): 660 config = ConfigParser() 661 config['binaries'] = { 662 'c': '\'/usr/bin/gcc\'', 663 } 664 config['host_machine'] = { 665 'system': '\'linux\'', 666 'cpu_family': '\'arm\'', 667 'cpu': '\'armv7\'', 668 'endian': '\'little\'', 669 } 670 # Can not be used as context manager because we need to 671 # open it a second time and this is not possible on 672 # Windows. 673 configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False) 674 configfilename = configfile.name 675 config.write(configfile) 676 configfile.flush() 677 configfile.close() 678 opts = get_fake_options() 679 opts.cross_file = (configfilename,) 680 env = get_fake_env(opts=opts) 681 detected_value = env.need_exe_wrapper() 682 os.unlink(configfilename) 683 684 desired_value = not detected_value 685 config['properties'] = { 686 'needs_exe_wrapper': 'true' if desired_value else 'false' 687 } 688 689 configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False) 690 configfilename = configfile.name 691 config.write(configfile) 692 configfile.close() 693 opts = get_fake_options() 694 opts.cross_file = (configfilename,) 695 env = get_fake_env(opts=opts) 696 forced_value = env.need_exe_wrapper() 697 os.unlink(configfilename) 698 699 self.assertEqual(forced_value, desired_value) 700 701 def test_listify(self): 702 listify = mesonbuild.mesonlib.listify 703 # Test sanity 704 self.assertEqual([1], listify(1)) 705 self.assertEqual([], listify([])) 706 self.assertEqual([1], listify([1])) 707 # Test flattening 708 self.assertEqual([1, 2, 3], listify([1, [2, 3]])) 709 self.assertEqual([1, 2, 3], listify([1, [2, [3]]])) 710 self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False)) 711 # Test flattening and unholdering 712 holder1 = ObjectHolder(1) 713 self.assertEqual([holder1], listify(holder1)) 714 self.assertEqual([holder1], listify([holder1])) 715 self.assertEqual([holder1, 2], listify([holder1, 2])) 716 self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]])) 717 718 def test_unholder(self): 719 unholder = mesonbuild.mesonlib.unholder 720 721 holder1 = ObjectHolder(1) 722 holder3 = ObjectHolder(3) 723 holders = [holder1, holder3] 724 725 self.assertEqual(1, unholder(holder1)) 726 self.assertEqual([1], unholder([holder1])) 727 self.assertEqual([1, 3], unholder(holders)) 728 729 def test_extract_as_list(self): 730 extract = mesonbuild.mesonlib.extract_as_list 731 # Test sanity 732 kwargs = {'sources': [1, 2, 3]} 733 self.assertEqual([1, 2, 3], extract(kwargs, 'sources')) 734 self.assertEqual(kwargs, {'sources': [1, 2, 3]}) 735 self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True)) 736 self.assertEqual(kwargs, {}) 737 738 # Test unholding 739 holder3 = ObjectHolder(3) 740 kwargs = {'sources': [1, 2, holder3]} 741 self.assertEqual(kwargs, {'sources': [1, 2, holder3]}) 742 743 # flatten nested lists 744 kwargs = {'sources': [1, [2, [3]]]} 745 self.assertEqual([1, 2, 3], extract(kwargs, 'sources')) 746 747 def test_pkgconfig_module(self): 748 dummystate = mock.Mock() 749 dummystate.subproject = 'dummy' 750 _mock = mock.Mock(spec=mesonbuild.dependencies.ExternalDependency) 751 _mock.pcdep = mock.Mock() 752 _mock.pcdep.name = "some_name" 753 _mock.version_reqs = [] 754 _mock = mock.Mock(held_object=_mock) 755 756 # pkgconfig dependency as lib 757 deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib") 758 deps.add_pub_libs([_mock]) 759 self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name") 760 761 # pkgconfig dependency as requires 762 deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib") 763 deps.add_pub_reqs([_mock]) 764 self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name") 765 766 def _test_all_naming(self, cc, env, patterns, platform): 767 shr = patterns[platform]['shared'] 768 stc = patterns[platform]['static'] 769 shrstc = shr + tuple([x for x in stc if x not in shr]) 770 stcshr = stc + tuple([x for x in shr if x not in stc]) 771 p = cc.get_library_naming(env, LibType.SHARED) 772 self.assertEqual(p, shr) 773 p = cc.get_library_naming(env, LibType.STATIC) 774 self.assertEqual(p, stc) 775 p = cc.get_library_naming(env, LibType.PREFER_STATIC) 776 self.assertEqual(p, stcshr) 777 p = cc.get_library_naming(env, LibType.PREFER_SHARED) 778 self.assertEqual(p, shrstc) 779 # Test find library by mocking up openbsd 780 if platform != 'openbsd': 781 return 782 with tempfile.TemporaryDirectory() as tmpdir: 783 with open(os.path.join(tmpdir, 'libfoo.so.6.0'), 'w') as f: 784 f.write('') 785 with open(os.path.join(tmpdir, 'libfoo.so.5.0'), 'w') as f: 786 f.write('') 787 with open(os.path.join(tmpdir, 'libfoo.so.54.0'), 'w') as f: 788 f.write('') 789 with open(os.path.join(tmpdir, 'libfoo.so.66a.0b'), 'w') as f: 790 f.write('') 791 with open(os.path.join(tmpdir, 'libfoo.so.70.0.so.1'), 'w') as f: 792 f.write('') 793 found = cc.find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED) 794 self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0') 795 796 def test_find_library_patterns(self): 797 ''' 798 Unit test for the library search patterns used by find_library() 799 ''' 800 unix_static = ('lib{}.a', '{}.a') 801 msvc_static = ('lib{}.a', 'lib{}.lib', '{}.a', '{}.lib') 802 # This is the priority list of pattern matching for library searching 803 patterns = {'openbsd': {'shared': ('lib{}.so', '{}.so', 'lib{}.so.[0-9]*.[0-9]*', '{}.so.[0-9]*.[0-9]*'), 804 'static': unix_static}, 805 'linux': {'shared': ('lib{}.so', '{}.so'), 806 'static': unix_static}, 807 'darwin': {'shared': ('lib{}.dylib', 'lib{}.so', '{}.dylib', '{}.so'), 808 'static': unix_static}, 809 'cygwin': {'shared': ('cyg{}.dll', 'cyg{}.dll.a', 'lib{}.dll', 810 'lib{}.dll.a', '{}.dll', '{}.dll.a'), 811 'static': ('cyg{}.a',) + unix_static}, 812 'windows-msvc': {'shared': ('lib{}.lib', '{}.lib'), 813 'static': msvc_static}, 814 'windows-mingw': {'shared': ('lib{}.dll.a', 'lib{}.lib', 'lib{}.dll', 815 '{}.dll.a', '{}.lib', '{}.dll'), 816 'static': msvc_static}} 817 env = get_fake_env() 818 cc = env.detect_c_compiler(MachineChoice.HOST) 819 if is_osx(): 820 self._test_all_naming(cc, env, patterns, 'darwin') 821 elif is_cygwin(): 822 self._test_all_naming(cc, env, patterns, 'cygwin') 823 elif is_windows(): 824 if cc.get_argument_syntax() == 'msvc': 825 self._test_all_naming(cc, env, patterns, 'windows-msvc') 826 else: 827 self._test_all_naming(cc, env, patterns, 'windows-mingw') 828 elif is_openbsd(): 829 self._test_all_naming(cc, env, patterns, 'openbsd') 830 else: 831 self._test_all_naming(cc, env, patterns, 'linux') 832 env.machines.host.system = 'openbsd' 833 self._test_all_naming(cc, env, patterns, 'openbsd') 834 env.machines.host.system = 'darwin' 835 self._test_all_naming(cc, env, patterns, 'darwin') 836 env.machines.host.system = 'cygwin' 837 self._test_all_naming(cc, env, patterns, 'cygwin') 838 env.machines.host.system = 'windows' 839 self._test_all_naming(cc, env, patterns, 'windows-mingw') 840 841 @skipIfNoPkgconfig 842 def test_pkgconfig_parse_libs(self): 843 ''' 844 Unit test for parsing of pkg-config output to search for libraries 845 846 https://github.com/mesonbuild/meson/issues/3951 847 ''' 848 def create_static_lib(name): 849 if not is_osx(): 850 name.open('w').close() 851 return 852 src = name.with_suffix('.c') 853 out = name.with_suffix('.o') 854 with src.open('w') as f: 855 f.write('int meson_foobar (void) { return 0; }') 856 subprocess.check_call(['clang', '-c', str(src), '-o', str(out)]) 857 subprocess.check_call(['ar', 'csr', str(name), str(out)]) 858 859 with tempfile.TemporaryDirectory() as tmpdir: 860 pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True) 861 env = get_fake_env() 862 compiler = env.detect_c_compiler(MachineChoice.HOST) 863 env.coredata.compilers.host = {'c': compiler} 864 env.coredata.compiler_options.host['c']['link_args'] = FakeCompilerOptions() 865 p1 = Path(tmpdir) / '1' 866 p2 = Path(tmpdir) / '2' 867 p1.mkdir() 868 p2.mkdir() 869 # libfoo.a is in one prefix 870 create_static_lib(p1 / 'libfoo.a') 871 # libbar.a is in both prefixes 872 create_static_lib(p1 / 'libbar.a') 873 create_static_lib(p2 / 'libbar.a') 874 # Ensure that we never statically link to these 875 create_static_lib(p1 / 'libpthread.a') 876 create_static_lib(p1 / 'libm.a') 877 create_static_lib(p1 / 'libc.a') 878 create_static_lib(p1 / 'libdl.a') 879 create_static_lib(p1 / 'librt.a') 880 881 def fake_call_pkgbin(self, args, env=None): 882 if '--libs' not in args: 883 return 0, '', '' 884 if args[0] == 'foo': 885 return 0, '-L{} -lfoo -L{} -lbar'.format(p2.as_posix(), p1.as_posix()), '' 886 if args[0] == 'bar': 887 return 0, '-L{} -lbar'.format(p2.as_posix()), '' 888 if args[0] == 'internal': 889 return 0, '-L{} -lpthread -lm -lc -lrt -ldl'.format(p1.as_posix()), '' 890 891 old_call = PkgConfigDependency._call_pkgbin 892 old_check = PkgConfigDependency.check_pkgconfig 893 PkgConfigDependency._call_pkgbin = fake_call_pkgbin 894 PkgConfigDependency.check_pkgconfig = lambda x, _: pkgbin 895 # Test begins 896 try: 897 kwargs = {'required': True, 'silent': True} 898 foo_dep = PkgConfigDependency('foo', env, kwargs) 899 self.assertEqual(foo_dep.get_link_args(), 900 [(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()]) 901 bar_dep = PkgConfigDependency('bar', env, kwargs) 902 self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()]) 903 internal_dep = PkgConfigDependency('internal', env, kwargs) 904 if compiler.get_argument_syntax() == 'msvc': 905 self.assertEqual(internal_dep.get_link_args(), []) 906 else: 907 link_args = internal_dep.get_link_args() 908 for link_arg in link_args: 909 for lib in ('pthread', 'm', 'c', 'dl', 'rt'): 910 self.assertNotIn('lib{}.a'.format(lib), link_arg, msg=link_args) 911 finally: 912 # Test ends 913 PkgConfigDependency._call_pkgbin = old_call 914 PkgConfigDependency.check_pkgconfig = old_check 915 # Reset dependency class to ensure that in-process configure doesn't mess up 916 PkgConfigDependency.pkgbin_cache = {} 917 PkgConfigDependency.class_pkgbin = PerMachine(None, None) 918 919 def test_version_compare(self): 920 comparefunc = mesonbuild.mesonlib.version_compare_many 921 for (a, b, result) in [ 922 ('0.99.beta19', '>= 0.99.beta14', True), 923 ]: 924 self.assertEqual(comparefunc(a, b)[0], result) 925 926 for (a, b, op) in [ 927 # examples from https://fedoraproject.org/wiki/Archive:Tools/RPM/VersionComparison 928 ("1.0010", "1.9", operator.gt), 929 ("1.05", "1.5", operator.eq), 930 ("1.0", "1", operator.gt), 931 ("2.50", "2.5", operator.gt), 932 ("fc4", "fc.4", operator.eq), 933 ("FC5", "fc4", operator.lt), 934 ("2a", "2.0", operator.lt), 935 ("1.0", "1.fc4", operator.gt), 936 ("3.0.0_fc", "3.0.0.fc", operator.eq), 937 # from RPM tests 938 ("1.0", "1.0", operator.eq), 939 ("1.0", "2.0", operator.lt), 940 ("2.0", "1.0", operator.gt), 941 ("2.0.1", "2.0.1", operator.eq), 942 ("2.0", "2.0.1", operator.lt), 943 ("2.0.1", "2.0", operator.gt), 944 ("2.0.1a", "2.0.1a", operator.eq), 945 ("2.0.1a", "2.0.1", operator.gt), 946 ("2.0.1", "2.0.1a", operator.lt), 947 ("5.5p1", "5.5p1", operator.eq), 948 ("5.5p1", "5.5p2", operator.lt), 949 ("5.5p2", "5.5p1", operator.gt), 950 ("5.5p10", "5.5p10", operator.eq), 951 ("5.5p1", "5.5p10", operator.lt), 952 ("5.5p10", "5.5p1", operator.gt), 953 ("10xyz", "10.1xyz", operator.lt), 954 ("10.1xyz", "10xyz", operator.gt), 955 ("xyz10", "xyz10", operator.eq), 956 ("xyz10", "xyz10.1", operator.lt), 957 ("xyz10.1", "xyz10", operator.gt), 958 ("xyz.4", "xyz.4", operator.eq), 959 ("xyz.4", "8", operator.lt), 960 ("8", "xyz.4", operator.gt), 961 ("xyz.4", "2", operator.lt), 962 ("2", "xyz.4", operator.gt), 963 ("5.5p2", "5.6p1", operator.lt), 964 ("5.6p1", "5.5p2", operator.gt), 965 ("5.6p1", "6.5p1", operator.lt), 966 ("6.5p1", "5.6p1", operator.gt), 967 ("6.0.rc1", "6.0", operator.gt), 968 ("6.0", "6.0.rc1", operator.lt), 969 ("10b2", "10a1", operator.gt), 970 ("10a2", "10b2", operator.lt), 971 ("1.0aa", "1.0aa", operator.eq), 972 ("1.0a", "1.0aa", operator.lt), 973 ("1.0aa", "1.0a", operator.gt), 974 ("10.0001", "10.0001", operator.eq), 975 ("10.0001", "10.1", operator.eq), 976 ("10.1", "10.0001", operator.eq), 977 ("10.0001", "10.0039", operator.lt), 978 ("10.0039", "10.0001", operator.gt), 979 ("4.999.9", "5.0", operator.lt), 980 ("5.0", "4.999.9", operator.gt), 981 ("20101121", "20101121", operator.eq), 982 ("20101121", "20101122", operator.lt), 983 ("20101122", "20101121", operator.gt), 984 ("2_0", "2_0", operator.eq), 985 ("2.0", "2_0", operator.eq), 986 ("2_0", "2.0", operator.eq), 987 ("a", "a", operator.eq), 988 ("a+", "a+", operator.eq), 989 ("a+", "a_", operator.eq), 990 ("a_", "a+", operator.eq), 991 ("+a", "+a", operator.eq), 992 ("+a", "_a", operator.eq), 993 ("_a", "+a", operator.eq), 994 ("+_", "+_", operator.eq), 995 ("_+", "+_", operator.eq), 996 ("_+", "_+", operator.eq), 997 ("+", "_", operator.eq), 998 ("_", "+", operator.eq), 999 # other tests 1000 ('0.99.beta19', '0.99.beta14', operator.gt), 1001 ("1.0.0", "2.0.0", operator.lt), 1002 (".0.0", "2.0.0", operator.lt), 1003 ("alpha", "beta", operator.lt), 1004 ("1.0", "1.0.0", operator.lt), 1005 ("2.456", "2.1000", operator.lt), 1006 ("2.1000", "3.111", operator.lt), 1007 ("2.001", "2.1", operator.eq), 1008 ("2.34", "2.34", operator.eq), 1009 ("6.1.2", "6.3.8", operator.lt), 1010 ("1.7.3.0", "2.0.0", operator.lt), 1011 ("2.24.51", "2.25", operator.lt), 1012 ("2.1.5+20120813+gitdcbe778", "2.1.5", operator.gt), 1013 ("3.4.1", "3.4b1", operator.gt), 1014 ("041206", "200090325", operator.lt), 1015 ("0.6.2+git20130413", "0.6.2", operator.gt), 1016 ("2.6.0+bzr6602", "2.6.0", operator.gt), 1017 ("2.6.0", "2.6b2", operator.gt), 1018 ("2.6.0+bzr6602", "2.6b2x", operator.gt), 1019 ("0.6.7+20150214+git3a710f9", "0.6.7", operator.gt), 1020 ("15.8b", "15.8.0.1", operator.lt), 1021 ("1.2rc1", "1.2.0", operator.lt), 1022 ]: 1023 ver_a = Version(a) 1024 ver_b = Version(b) 1025 if op is operator.eq: 1026 for o, name in [(op, 'eq'), (operator.ge, 'ge'), (operator.le, 'le')]: 1027 self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) 1028 if op is operator.lt: 1029 for o, name in [(op, 'lt'), (operator.le, 'le'), (operator.ne, 'ne')]: 1030 self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) 1031 for o, name in [(operator.gt, 'gt'), (operator.ge, 'ge'), (operator.eq, 'eq')]: 1032 self.assertFalse(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) 1033 if op is operator.gt: 1034 for o, name in [(op, 'gt'), (operator.ge, 'ge'), (operator.ne, 'ne')]: 1035 self.assertTrue(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) 1036 for o, name in [(operator.lt, 'lt'), (operator.le, 'le'), (operator.eq, 'eq')]: 1037 self.assertFalse(o(ver_a, ver_b), '{} {} {}'.format(ver_a, name, ver_b)) 1038 1039 def test_msvc_toolset_version(self): 1040 ''' 1041 Ensure that the toolset version returns the correct value for this MSVC 1042 ''' 1043 env = get_fake_env() 1044 cc = env.detect_c_compiler(MachineChoice.HOST) 1045 if cc.get_argument_syntax() != 'msvc': 1046 raise unittest.SkipTest('Test only applies to MSVC-like compilers') 1047 toolset_ver = cc.get_toolset_version() 1048 self.assertIsNotNone(toolset_ver) 1049 # Visual Studio 2015 and older versions do not define VCToolsVersion 1050 # TODO: ICL doesn't set this in the VSC2015 profile either 1051 if cc.id == 'msvc' and int(''.join(cc.version.split('.')[0:2])) < 1910: 1052 return 1053 if 'VCToolsVersion' in os.environ: 1054 vctools_ver = os.environ['VCToolsVersion'] 1055 else: 1056 self.assertIn('VCINSTALLDIR', os.environ) 1057 # See https://devblogs.microsoft.com/cppblog/finding-the-visual-c-compiler-tools-in-visual-studio-2017/ 1058 vctools_ver = (Path(os.environ['VCINSTALLDIR']) / 'Auxiliary' / 'Build' / 'Microsoft.VCToolsVersion.default.txt').read_text() 1059 self.assertTrue(vctools_ver.startswith(toolset_ver), 1060 msg='{!r} does not start with {!r}'.format(vctools_ver, toolset_ver)) 1061 1062 def test_split_args(self): 1063 split_args = mesonbuild.mesonlib.split_args 1064 join_args = mesonbuild.mesonlib.join_args 1065 if is_windows(): 1066 test_data = [ 1067 # examples from https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments 1068 (r'"a b c" d e', ['a b c', 'd', 'e'], True), 1069 (r'"ab\"c" "\\" d', ['ab"c', '\\', 'd'], False), 1070 (r'a\\\b d"e f"g h', [r'a\\\b', 'de fg', 'h'], False), 1071 (r'a\\\"b c d', [r'a\"b', 'c', 'd'], False), 1072 (r'a\\\\"b c" d e', [r'a\\b c', 'd', 'e'], False), 1073 # other basics 1074 (r'""', [''], True), 1075 (r'a b c d "" e', ['a', 'b', 'c', 'd', '', 'e'], True), 1076 (r"'a b c' d e", ["'a", 'b', "c'", 'd', 'e'], True), 1077 (r"'a&b&c' d e", ["'a&b&c'", 'd', 'e'], True), 1078 (r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], True), 1079 (r"'a & b & c d e'", ["'a", '&', 'b', '&', 'c', 'd', "e'"], True), 1080 ('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False), 1081 # more illustrative tests 1082 (r'cl test.cpp /O1 /Fe:test.exe', ['cl', 'test.cpp', '/O1', '/Fe:test.exe'], True), 1083 (r'cl "test.cpp /O1 /Fe:test.exe"', ['cl', 'test.cpp /O1 /Fe:test.exe'], True), 1084 (r'cl /DNAME=\"Bob\" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], False), 1085 (r'cl "/DNAME=\"Bob\"" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], True), 1086 (r'cl /DNAME=\"Bob, Alice\" test.cpp', ['cl', '/DNAME="Bob,', 'Alice"', 'test.cpp'], False), 1087 (r'cl "/DNAME=\"Bob, Alice\"" test.cpp', ['cl', '/DNAME="Bob, Alice"', 'test.cpp'], True), 1088 (r'cl C:\path\with\backslashes.cpp', ['cl', r'C:\path\with\backslashes.cpp'], True), 1089 (r'cl C:\\path\\with\\double\\backslashes.cpp', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], True), 1090 (r'cl "C:\\path\\with\\double\\backslashes.cpp"', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], False), 1091 (r'cl C:\path with spaces\test.cpp', ['cl', r'C:\path', 'with', r'spaces\test.cpp'], False), 1092 (r'cl "C:\path with spaces\test.cpp"', ['cl', r'C:\path with spaces\test.cpp'], True), 1093 (r'cl /DPATH="C:\path\with\backslashes test.cpp', ['cl', r'/DPATH=C:\path\with\backslashes test.cpp'], False), 1094 (r'cl /DPATH=\"C:\\ends\\with\\backslashes\\\" test.cpp', ['cl', r'/DPATH="C:\\ends\\with\\backslashes\"', 'test.cpp'], False), 1095 (r'cl /DPATH="C:\\ends\\with\\backslashes\\" test.cpp', ['cl', '/DPATH=C:\\\\ends\\\\with\\\\backslashes\\', 'test.cpp'], False), 1096 (r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\"', 'test.cpp'], True), 1097 (r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\ test.cpp'], False), 1098 (r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\"', 'test.cpp'], True), 1099 ] 1100 else: 1101 test_data = [ 1102 (r"'a b c' d e", ['a b c', 'd', 'e'], True), 1103 (r"a/b/c d e", ['a/b/c', 'd', 'e'], True), 1104 (r"a\b\c d e", [r'abc', 'd', 'e'], False), 1105 (r"a\\b\\c d e", [r'a\b\c', 'd', 'e'], False), 1106 (r'"a b c" d e', ['a b c', 'd', 'e'], False), 1107 (r'"a\\b\\c\\" d e', ['a\\b\\c\\', 'd', 'e'], False), 1108 (r"'a\b\c\' d e", ['a\\b\\c\\', 'd', 'e'], True), 1109 (r"'a&b&c' d e", ['a&b&c', 'd', 'e'], True), 1110 (r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], False), 1111 (r"'a & b & c d e'", ['a & b & c d e'], True), 1112 (r"abd'e f'g h", [r'abde fg', 'h'], False), 1113 ('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False), 1114 1115 ('g++ -DNAME="Bob" test.cpp', ['g++', '-DNAME=Bob', 'test.cpp'], False), 1116 ("g++ '-DNAME=\"Bob\"' test.cpp", ['g++', '-DNAME="Bob"', 'test.cpp'], True), 1117 ('g++ -DNAME="Bob, Alice" test.cpp', ['g++', '-DNAME=Bob, Alice', 'test.cpp'], False), 1118 ("g++ '-DNAME=\"Bob, Alice\"' test.cpp", ['g++', '-DNAME="Bob, Alice"', 'test.cpp'], True), 1119 ] 1120 1121 for (cmd, expected, roundtrip) in test_data: 1122 self.assertEqual(split_args(cmd), expected) 1123 if roundtrip: 1124 self.assertEqual(join_args(expected), cmd) 1125 1126 def test_quote_arg(self): 1127 split_args = mesonbuild.mesonlib.split_args 1128 quote_arg = mesonbuild.mesonlib.quote_arg 1129 if is_windows(): 1130 test_data = [ 1131 ('', '""'), 1132 ('arg1', 'arg1'), 1133 ('/option1', '/option1'), 1134 ('/Ovalue', '/Ovalue'), 1135 ('/OBob&Alice', '/OBob&Alice'), 1136 ('/Ovalue with spaces', r'"/Ovalue with spaces"'), 1137 (r'/O"value with spaces"', r'"/O\"value with spaces\""'), 1138 (r'/OC:\path with spaces\test.exe', r'"/OC:\path with spaces\test.exe"'), 1139 ('/LIBPATH:C:\\path with spaces\\ends\\with\\backslashes\\', r'"/LIBPATH:C:\path with spaces\ends\with\backslashes\\"'), 1140 ('/LIBPATH:"C:\\path with spaces\\ends\\with\\backslashes\\\\"', r'"/LIBPATH:\"C:\path with spaces\ends\with\backslashes\\\\\""'), 1141 (r'/DMSG="Alice said: \"Let\'s go\""', r'"/DMSG=\"Alice said: \\\"Let\'s go\\\"\""'), 1142 ] 1143 else: 1144 test_data = [ 1145 ('arg1', 'arg1'), 1146 ('--option1', '--option1'), 1147 ('-O=value', '-O=value'), 1148 ('-O=Bob&Alice', "'-O=Bob&Alice'"), 1149 ('-O=value with spaces', "'-O=value with spaces'"), 1150 ('-O="value with spaces"', '\'-O=\"value with spaces\"\''), 1151 ('-O=/path with spaces/test', '\'-O=/path with spaces/test\''), 1152 ('-DMSG="Alice said: \\"Let\'s go\\""', "'-DMSG=\"Alice said: \\\"Let'\"'\"'s go\\\"\"'"), 1153 ] 1154 1155 for (arg, expected) in test_data: 1156 self.assertEqual(quote_arg(arg), expected) 1157 self.assertEqual(split_args(expected)[0], arg) 1158 1159 def test_depfile(self): 1160 for (f, target, expdeps) in [ 1161 # empty, unknown target 1162 ([''], 'unknown', set()), 1163 # simple target & deps 1164 (['meson/foo.o : foo.c foo.h'], 'meson/foo.o', set({'foo.c', 'foo.h'})), 1165 (['meson/foo.o: foo.c foo.h'], 'foo.c', set()), 1166 # get all deps 1167 (['meson/foo.o: foo.c foo.h', 1168 'foo.c: gen.py'], 'meson/foo.o', set({'foo.c', 'foo.h', 'gen.py'})), 1169 (['meson/foo.o: foo.c foo.h', 1170 'foo.c: gen.py'], 'foo.c', set({'gen.py'})), 1171 # linue continuation, multiple targets 1172 (['foo.o \\', 'foo.h: bar'], 'foo.h', set({'bar'})), 1173 (['foo.o \\', 'foo.h: bar'], 'foo.o', set({'bar'})), 1174 # \\ handling 1175 (['foo: Program\\ F\\iles\\\\X'], 'foo', set({'Program Files\\X'})), 1176 # $ handling 1177 (['f$o.o: c/b'], 'f$o.o', set({'c/b'})), 1178 (['f$$o.o: c/b'], 'f$o.o', set({'c/b'})), 1179 # cycles 1180 (['a: b', 'b: a'], 'a', set({'a', 'b'})), 1181 (['a: b', 'b: a'], 'b', set({'a', 'b'})), 1182 ]: 1183 d = mesonbuild.depfile.DepFile(f) 1184 deps = d.get_all_dependencies(target) 1185 self.assertEqual(deps, expdeps) 1186 1187 def test_log_once(self): 1188 f = io.StringIO() 1189 with mock.patch('mesonbuild.mlog.log_file', f), \ 1190 mock.patch('mesonbuild.mlog._logged_once', set()): 1191 mesonbuild.mlog.log_once('foo') 1192 mesonbuild.mlog.log_once('foo') 1193 actual = f.getvalue().strip() 1194 self.assertEqual(actual, 'foo', actual) 1195 1196 def test_log_once_ansi(self): 1197 f = io.StringIO() 1198 with mock.patch('mesonbuild.mlog.log_file', f), \ 1199 mock.patch('mesonbuild.mlog._logged_once', set()): 1200 mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo')) 1201 mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo')) 1202 actual = f.getvalue().strip() 1203 self.assertEqual(actual.count('foo'), 1, actual) 1204 1205 mesonbuild.mlog.log_once('foo') 1206 actual = f.getvalue().strip() 1207 self.assertEqual(actual.count('foo'), 1, actual) 1208 1209 f.truncate() 1210 1211 mesonbuild.mlog.warning('bar', once=True) 1212 mesonbuild.mlog.warning('bar', once=True) 1213 actual = f.getvalue().strip() 1214 self.assertEqual(actual.count('bar'), 1, actual) 1215 1216 def test_sort_libpaths(self): 1217 sort_libpaths = mesonbuild.dependencies.base.sort_libpaths 1218 self.assertEqual(sort_libpaths( 1219 ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'], 1220 ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']), 1221 ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib']) 1222 self.assertEqual(sort_libpaths( 1223 ['/usr/local/lib', '/home/mesonuser/.local/lib', '/usr/lib'], 1224 ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']), 1225 ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib']) 1226 self.assertEqual(sort_libpaths( 1227 ['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'], 1228 ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']), 1229 ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib']) 1230 self.assertEqual(sort_libpaths( 1231 ['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'], 1232 ['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/libdata/pkgconfig']), 1233 ['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib']) 1234 1235 def test_dependency_factory_order(self): 1236 b = mesonbuild.dependencies.base 1237 with tempfile.TemporaryDirectory() as tmpdir: 1238 with chdir(tmpdir): 1239 env = get_fake_env() 1240 1241 f = b.DependencyFactory( 1242 'test_dep', 1243 methods=[b.DependencyMethods.PKGCONFIG, b.DependencyMethods.CMAKE] 1244 ) 1245 actual = [m() for m in f(env, MachineChoice.HOST, {'required': False})] 1246 self.assertListEqual([m.type_name for m in actual], ['pkgconfig', 'cmake']) 1247 1248 f = b.DependencyFactory( 1249 'test_dep', 1250 methods=[b.DependencyMethods.CMAKE, b.DependencyMethods.PKGCONFIG] 1251 ) 1252 actual = [m() for m in f(env, MachineChoice.HOST, {'required': False})] 1253 self.assertListEqual([m.type_name for m in actual], ['cmake', 'pkgconfig']) 1254 1255 def test_validate_json(self) -> None: 1256 """Validate the json schema for the test cases.""" 1257 try: 1258 from jsonschema import validate, ValidationError 1259 except ImportError: 1260 if is_ci(): 1261 raise 1262 raise unittest.SkipTest('Python jsonschema module not found.') 1263 1264 with Path('data/test.schema.json').open() as f: 1265 schema = json.load(f) 1266 1267 errors = [] # type: T.Tuple[str, Exception] 1268 for p in Path('test cases').glob('**/test.json'): 1269 with p.open() as f: 1270 try: 1271 validate(json.load(f), schema=schema) 1272 except ValidationError as e: 1273 errors.append((p.resolve(), e)) 1274 1275 for f, e in errors: 1276 print('Failed to validate: "{}"'.format(f)) 1277 print(str(e)) 1278 1279 self.assertFalse(errors) 1280 1281@unittest.skipIf(is_tarball(), 'Skipping because this is a tarball release') 1282class DataTests(unittest.TestCase): 1283 1284 def test_snippets(self): 1285 hashcounter = re.compile('^ *(#)+') 1286 snippet_dir = Path('docs/markdown/snippets') 1287 self.assertTrue(snippet_dir.is_dir()) 1288 for f in snippet_dir.glob('*'): 1289 self.assertTrue(f.is_file()) 1290 if f.parts[-1].endswith('~'): 1291 continue 1292 if f.suffix == '.md': 1293 in_code_block = False 1294 with f.open() as snippet: 1295 for line in snippet: 1296 if line.startswith(' '): 1297 continue 1298 if line.startswith('```'): 1299 in_code_block = not in_code_block 1300 if in_code_block: 1301 continue 1302 m = re.match(hashcounter, line) 1303 if m: 1304 self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name) 1305 self.assertFalse(in_code_block, 'Unclosed code block.') 1306 else: 1307 if f.name != 'add_release_note_snippets_here': 1308 self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name) 1309 1310 def test_compiler_options_documented(self): 1311 ''' 1312 Test that C and C++ compiler options and base options are documented in 1313 Builtin-Options.md. Only tests the default compiler for the current 1314 platform on the CI. 1315 ''' 1316 md = None 1317 with open('docs/markdown/Builtin-options.md', encoding='utf-8') as f: 1318 md = f.read() 1319 self.assertIsNotNone(md) 1320 env = get_fake_env() 1321 # FIXME: Support other compilers 1322 cc = env.detect_c_compiler(MachineChoice.HOST) 1323 cpp = env.detect_cpp_compiler(MachineChoice.HOST) 1324 for comp in (cc, cpp): 1325 for opt in comp.get_options().keys(): 1326 self.assertIn(opt, md) 1327 for opt in comp.base_options: 1328 self.assertIn(opt, md) 1329 self.assertNotIn('b_unknown', md) 1330 1331 @staticmethod 1332 def _get_section_content(name, sections, md): 1333 for section in sections: 1334 if section and section.group(1) == name: 1335 try: 1336 next_section = next(sections) 1337 end = next_section.start() 1338 except StopIteration: 1339 end = len(md) 1340 # Extract the content for this section 1341 return md[section.end():end] 1342 raise RuntimeError('Could not find "{}" heading'.format(name)) 1343 1344 def test_builtin_options_documented(self): 1345 ''' 1346 Test that universal options and base options are documented in 1347 Builtin-Options.md. 1348 ''' 1349 from itertools import tee 1350 md = None 1351 with open('docs/markdown/Builtin-options.md', encoding='utf-8') as f: 1352 md = f.read() 1353 self.assertIsNotNone(md) 1354 1355 found_entries = set() 1356 sections = re.finditer(r"^## (.+)$", md, re.MULTILINE) 1357 # Extract the content for this section 1358 content = self._get_section_content("Universal options", sections, md) 1359 subsections = tee(re.finditer(r"^### (.+)$", content, re.MULTILINE)) 1360 subcontent1 = self._get_section_content("Directories", subsections[0], content) 1361 subcontent2 = self._get_section_content("Core options", subsections[1], content) 1362 for subcontent in (subcontent1, subcontent2): 1363 # Find the option names 1364 options = set() 1365 # Match either a table row or a table heading separator: | ------ | 1366 rows = re.finditer(r"^\|(?: (\w+) .* | *-+ *)\|", subcontent, re.MULTILINE) 1367 # Skip the header of the first table 1368 next(rows) 1369 # Skip the heading separator of the first table 1370 next(rows) 1371 for m in rows: 1372 value = m.group(1) 1373 # End when the `buildtype` table starts 1374 if value is None: 1375 break 1376 options.add(value) 1377 self.assertEqual(len(found_entries & options), 0) 1378 found_entries |= options 1379 1380 self.assertEqual(found_entries, set([ 1381 *mesonbuild.coredata.builtin_options.keys(), 1382 *mesonbuild.coredata.builtin_options_per_machine.keys() 1383 ])) 1384 1385 # Check that `buildtype` table inside `Core options` matches how 1386 # setting of builtin options behaves 1387 # 1388 # Find all tables inside this subsection 1389 tables = re.finditer(r"^\| (\w+) .* \|\n\| *[-|\s]+ *\|$", subcontent2, re.MULTILINE) 1390 # Get the table we want using the header of the first column 1391 table = self._get_section_content('buildtype', tables, subcontent2) 1392 # Get table row data 1393 rows = re.finditer(r"^\|(?: (\w+)\s+\| (\w+)\s+\| (\w+) .* | *-+ *)\|", table, re.MULTILINE) 1394 env = get_fake_env() 1395 for m in rows: 1396 buildtype, debug, opt = m.groups() 1397 if debug == 'true': 1398 debug = True 1399 elif debug == 'false': 1400 debug = False 1401 else: 1402 raise RuntimeError('Invalid debug value {!r} in row:\n{}'.format(debug, m.group())) 1403 env.coredata.set_builtin_option('buildtype', buildtype) 1404 self.assertEqual(env.coredata.builtins['buildtype'].value, buildtype) 1405 self.assertEqual(env.coredata.builtins['optimization'].value, opt) 1406 self.assertEqual(env.coredata.builtins['debug'].value, debug) 1407 1408 def test_cpu_families_documented(self): 1409 with open("docs/markdown/Reference-tables.md", encoding='utf-8') as f: 1410 md = f.read() 1411 self.assertIsNotNone(md) 1412 1413 sections = re.finditer(r"^## (.+)$", md, re.MULTILINE) 1414 content = self._get_section_content("CPU families", sections, md) 1415 # Find the list entries 1416 arches = [m.group(1) for m in re.finditer(r"^\| (\w+) +\|", content, re.MULTILINE)] 1417 # Drop the header 1418 arches = set(arches[1:]) 1419 self.assertEqual(arches, set(mesonbuild.environment.known_cpu_families)) 1420 1421 def test_markdown_files_in_sitemap(self): 1422 ''' 1423 Test that each markdown files in docs/markdown is referenced in sitemap.txt 1424 ''' 1425 with open("docs/sitemap.txt", encoding='utf-8') as f: 1426 md = f.read() 1427 self.assertIsNotNone(md) 1428 toc = list(m.group(1) for m in re.finditer(r"^\s*(\w.*)$", md, re.MULTILINE)) 1429 markdownfiles = [f.name for f in Path("docs/markdown").iterdir() if f.is_file() and f.suffix == '.md'] 1430 exceptions = ['_Sidebar.md'] 1431 for f in markdownfiles: 1432 if f not in exceptions: 1433 self.assertIn(f, toc) 1434 1435 def test_vim_syntax_highlighting(self): 1436 ''' 1437 Ensure that vim syntax highlighting files were updated for new 1438 functions in the global namespace in build files. 1439 ''' 1440 env = get_fake_env() 1441 interp = Interpreter(FakeBuild(env), mock=True) 1442 with open('data/syntax-highlighting/vim/syntax/meson.vim') as f: 1443 res = re.search(r'syn keyword mesonBuiltin(\s+\\\s\w+)+', f.read(), re.MULTILINE) 1444 defined = set([a.strip() for a in res.group().split('\\')][1:]) 1445 self.assertEqual(defined, set(chain(interp.funcs.keys(), interp.builtin.keys()))) 1446 1447 @unittest.skipIf(is_pull(), 'Skipping because this is a pull request') 1448 def test_json_grammar_syntax_highlighting(self): 1449 ''' 1450 Ensure that syntax highlighting JSON grammar written by TingPing was 1451 updated for new functions in the global namespace in build files. 1452 https://github.com/TingPing/language-meson/ 1453 ''' 1454 env = get_fake_env() 1455 interp = Interpreter(FakeBuild(env), mock=True) 1456 url = 'https://raw.githubusercontent.com/TingPing/language-meson/master/grammars/meson.json' 1457 try: 1458 # Use a timeout to avoid blocking forever in case the network is 1459 # slow or unavailable in a weird way 1460 r = urllib.request.urlopen(url, timeout=URLOPEN_TIMEOUT) 1461 except urllib.error.URLError as e: 1462 # Skip test when network is not available, such as during packaging 1463 # by a distro or Flatpak 1464 if not isinstance(e, urllib.error.HTTPError): 1465 raise unittest.SkipTest('Network unavailable') 1466 # Don't fail the test if github is down, but do fail if 4xx 1467 if e.code >= 500: 1468 raise unittest.SkipTest('Server error ' + str(e.code)) 1469 raise e 1470 # On Python 3.5, we must decode bytes to string. Newer versions don't require that. 1471 grammar = json.loads(r.read().decode('utf-8', 'surrogatepass')) 1472 for each in grammar['patterns']: 1473 if 'name' in each and each['name'] == 'support.function.builtin.meson': 1474 # The string is of the form: (?x)\\b(func1|func2|...\n)\\b\\s*(?=\\() and 1475 # we convert that to [func1, func2, ...] without using regex to parse regex 1476 funcs = set(each['match'].split('\\b(')[1].split('\n')[0].split('|')) 1477 if 'name' in each and each['name'] == 'support.variable.meson': 1478 # \\b(builtin1|builtin2...)\\b 1479 builtin = set(each['match'].split('\\b(')[1].split(')\\b')[0].split('|')) 1480 self.assertEqual(builtin, set(interp.builtin.keys())) 1481 self.assertEqual(funcs, set(interp.funcs.keys())) 1482 1483 def test_all_functions_defined_in_ast_interpreter(self): 1484 ''' 1485 Ensure that the all functions defined in the Interpreter are also defined 1486 in the AstInterpreter (and vice versa). 1487 ''' 1488 env = get_fake_env() 1489 interp = Interpreter(FakeBuild(env), mock=True) 1490 astint = AstInterpreter('.', '', '') 1491 self.assertEqual(set(interp.funcs.keys()), set(astint.funcs.keys())) 1492 1493 def test_mesondata_is_up_to_date(self): 1494 from mesonbuild.mesondata import mesondata 1495 err_msg = textwrap.dedent(''' 1496 1497 ########################################################### 1498 ### mesonbuild.mesondata is not up-to-date ### 1499 ### Please regenerate it by running tools/gen_data.py ### 1500 ########################################################### 1501 1502 ''') 1503 1504 root_dir = Path(__file__).resolve().parent 1505 mesonbuild_dir = root_dir / 'mesonbuild' 1506 1507 data_dirs = mesonbuild_dir.glob('**/data') 1508 data_files = [] # type: T.List[T.Tuple(str, str)] 1509 1510 for i in data_dirs: 1511 for p in i.iterdir(): 1512 data_files += [(p.relative_to(mesonbuild_dir).as_posix(), hashlib.sha256(p.read_bytes()).hexdigest())] 1513 1514 from pprint import pprint 1515 current_files = set(mesondata.keys()) 1516 scanned_files = set([x[0] for x in data_files]) 1517 1518 self.assertSetEqual(current_files, scanned_files, err_msg + 'Data files were added or removed\n') 1519 errors = [] 1520 for i in data_files: 1521 if mesondata[i[0]].sha256sum != i[1]: 1522 errors += [i[0]] 1523 1524 self.assertListEqual(errors, [], err_msg + 'Files were changed') 1525 1526class BasePlatformTests(unittest.TestCase): 1527 prefix = '/usr' 1528 libdir = 'lib' 1529 1530 def setUp(self): 1531 super().setUp() 1532 self.maxDiff = None 1533 src_root = os.path.dirname(__file__) 1534 src_root = os.path.join(os.getcwd(), src_root) 1535 self.src_root = src_root 1536 # Get the backend 1537 # FIXME: Extract this from argv? 1538 self.backend = getattr(Backend, os.environ.get('MESON_UNIT_TEST_BACKEND', 'ninja')) 1539 self.meson_args = ['--backend=' + self.backend.name] 1540 self.meson_native_file = None 1541 self.meson_cross_file = None 1542 self.meson_command = python_command + [get_meson_script()] 1543 self.setup_command = self.meson_command + self.meson_args 1544 self.mconf_command = self.meson_command + ['configure'] 1545 self.mintro_command = self.meson_command + ['introspect'] 1546 self.wrap_command = self.meson_command + ['wrap'] 1547 self.rewrite_command = self.meson_command + ['rewrite'] 1548 # Backend-specific build commands 1549 self.build_command, self.clean_command, self.test_command, self.install_command, \ 1550 self.uninstall_command = get_backend_commands(self.backend) 1551 # Test directories 1552 self.common_test_dir = os.path.join(src_root, 'test cases/common') 1553 self.vala_test_dir = os.path.join(src_root, 'test cases/vala') 1554 self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks') 1555 self.unit_test_dir = os.path.join(src_root, 'test cases/unit') 1556 self.rewrite_test_dir = os.path.join(src_root, 'test cases/rewrite') 1557 self.linuxlike_test_dir = os.path.join(src_root, 'test cases/linuxlike') 1558 # Misc stuff 1559 self.orig_env = os.environ.copy() 1560 if self.backend is Backend.ninja: 1561 self.no_rebuild_stdout = ['ninja: no work to do.', 'samu: nothing to do'] 1562 else: 1563 # VS doesn't have a stable output when no changes are done 1564 # XCode backend is untested with unit tests, help welcome! 1565 self.no_rebuild_stdout = ['UNKNOWN BACKEND {!r}'.format(self.backend.name)] 1566 1567 self.builddirs = [] 1568 self.new_builddir() 1569 1570 def change_builddir(self, newdir): 1571 self.builddir = newdir 1572 self.privatedir = os.path.join(self.builddir, 'meson-private') 1573 self.logdir = os.path.join(self.builddir, 'meson-logs') 1574 self.installdir = os.path.join(self.builddir, 'install') 1575 self.distdir = os.path.join(self.builddir, 'meson-dist') 1576 self.mtest_command = self.meson_command + ['test', '-C', self.builddir] 1577 self.builddirs.append(self.builddir) 1578 1579 def new_builddir(self): 1580 if not is_cygwin(): 1581 # Keep builddirs inside the source tree so that virus scanners 1582 # don't complain 1583 newdir = tempfile.mkdtemp(dir=os.getcwd()) 1584 else: 1585 # But not on Cygwin because that breaks the umask tests. See: 1586 # https://github.com/mesonbuild/meson/pull/5546#issuecomment-509666523 1587 newdir = tempfile.mkdtemp() 1588 # In case the directory is inside a symlinked directory, find the real 1589 # path otherwise we might not find the srcdir from inside the builddir. 1590 newdir = os.path.realpath(newdir) 1591 self.change_builddir(newdir) 1592 1593 def _print_meson_log(self): 1594 log = os.path.join(self.logdir, 'meson-log.txt') 1595 if not os.path.isfile(log): 1596 print("{!r} doesn't exist".format(log)) 1597 return 1598 with open(log, 'r', encoding='utf-8') as f: 1599 print(f.read()) 1600 1601 def tearDown(self): 1602 for path in self.builddirs: 1603 try: 1604 windows_proof_rmtree(path) 1605 except FileNotFoundError: 1606 pass 1607 os.environ.clear() 1608 os.environ.update(self.orig_env) 1609 super().tearDown() 1610 1611 def _run(self, command, *, workdir=None, override_envvars=None): 1612 ''' 1613 Run a command while printing the stdout and stderr to stdout, 1614 and also return a copy of it 1615 ''' 1616 # If this call hangs CI will just abort. It is very hard to distinguish 1617 # between CI issue and test bug in that case. Set timeout and fail loud 1618 # instead. 1619 if override_envvars is None: 1620 env = None 1621 else: 1622 env = os.environ.copy() 1623 env.update(override_envvars) 1624 1625 p = subprocess.run(command, stdout=subprocess.PIPE, 1626 stderr=subprocess.STDOUT, env=env, 1627 universal_newlines=True, cwd=workdir, timeout=60 * 5) 1628 print(p.stdout) 1629 if p.returncode != 0: 1630 if 'MESON_SKIP_TEST' in p.stdout: 1631 raise unittest.SkipTest('Project requested skipping.') 1632 raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout) 1633 return p.stdout 1634 1635 def init(self, srcdir, *, 1636 extra_args=None, 1637 default_args=True, 1638 inprocess=False, 1639 override_envvars=None, 1640 workdir=None): 1641 self.assertPathExists(srcdir) 1642 if extra_args is None: 1643 extra_args = [] 1644 if not isinstance(extra_args, list): 1645 extra_args = [extra_args] 1646 args = [srcdir, self.builddir] 1647 if default_args: 1648 args += ['--prefix', self.prefix] 1649 if self.libdir: 1650 args += ['--libdir', self.libdir] 1651 if self.meson_native_file: 1652 args += ['--native-file', self.meson_native_file] 1653 if self.meson_cross_file: 1654 args += ['--cross-file', self.meson_cross_file] 1655 self.privatedir = os.path.join(self.builddir, 'meson-private') 1656 if inprocess: 1657 try: 1658 if override_envvars is not None: 1659 old_envvars = os.environ.copy() 1660 os.environ.update(override_envvars) 1661 (returncode, out, err) = run_configure_inprocess(self.meson_args + args + extra_args) 1662 if override_envvars is not None: 1663 os.environ.clear() 1664 os.environ.update(old_envvars) 1665 if 'MESON_SKIP_TEST' in out: 1666 raise unittest.SkipTest('Project requested skipping.') 1667 if returncode != 0: 1668 self._print_meson_log() 1669 print('Stdout:\n') 1670 print(out) 1671 print('Stderr:\n') 1672 print(err) 1673 raise RuntimeError('Configure failed') 1674 except Exception: 1675 self._print_meson_log() 1676 raise 1677 finally: 1678 # Close log file to satisfy Windows file locking 1679 mesonbuild.mlog.shutdown() 1680 mesonbuild.mlog.log_dir = None 1681 mesonbuild.mlog.log_file = None 1682 else: 1683 try: 1684 out = self._run(self.setup_command + args + extra_args, override_envvars=override_envvars, workdir=workdir) 1685 except unittest.SkipTest: 1686 raise unittest.SkipTest('Project requested skipping: ' + srcdir) 1687 except Exception: 1688 self._print_meson_log() 1689 raise 1690 return out 1691 1692 def build(self, target=None, *, extra_args=None, override_envvars=None): 1693 if extra_args is None: 1694 extra_args = [] 1695 # Add arguments for building the target (if specified), 1696 # and using the build dir (if required, with VS) 1697 args = get_builddir_target_args(self.backend, self.builddir, target) 1698 return self._run(self.build_command + args + extra_args, workdir=self.builddir, override_envvars=override_envvars) 1699 1700 def clean(self, *, override_envvars=None): 1701 dir_args = get_builddir_target_args(self.backend, self.builddir, None) 1702 self._run(self.clean_command + dir_args, workdir=self.builddir, override_envvars=override_envvars) 1703 1704 def run_tests(self, *, inprocess=False, override_envvars=None): 1705 if not inprocess: 1706 self._run(self.test_command, workdir=self.builddir, override_envvars=override_envvars) 1707 else: 1708 if override_envvars is not None: 1709 old_envvars = os.environ.copy() 1710 os.environ.update(override_envvars) 1711 try: 1712 run_mtest_inprocess(['-C', self.builddir]) 1713 finally: 1714 if override_envvars is not None: 1715 os.environ.clear() 1716 os.environ.update(old_envvars) 1717 1718 def install(self, *, use_destdir=True, override_envvars=None): 1719 if self.backend is not Backend.ninja: 1720 raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name)) 1721 if use_destdir: 1722 destdir = {'DESTDIR': self.installdir} 1723 if override_envvars is None: 1724 override_envvars = destdir 1725 else: 1726 override_envvars.update(destdir) 1727 self._run(self.install_command, workdir=self.builddir, override_envvars=override_envvars) 1728 1729 def uninstall(self, *, override_envvars=None): 1730 self._run(self.uninstall_command, workdir=self.builddir, override_envvars=override_envvars) 1731 1732 def run_target(self, target, *, override_envvars=None): 1733 ''' 1734 Run a Ninja target while printing the stdout and stderr to stdout, 1735 and also return a copy of it 1736 ''' 1737 return self.build(target=target, override_envvars=override_envvars) 1738 1739 def setconf(self, arg, will_build=True): 1740 if not isinstance(arg, list): 1741 arg = [arg] 1742 if will_build: 1743 ensure_backend_detects_changes(self.backend) 1744 self._run(self.mconf_command + arg + [self.builddir]) 1745 1746 def wipe(self): 1747 windows_proof_rmtree(self.builddir) 1748 1749 def utime(self, f): 1750 ensure_backend_detects_changes(self.backend) 1751 os.utime(f) 1752 1753 def get_compdb(self): 1754 if self.backend is not Backend.ninja: 1755 raise unittest.SkipTest('Compiler db not available with {} backend'.format(self.backend.name)) 1756 try: 1757 with open(os.path.join(self.builddir, 'compile_commands.json')) as ifile: 1758 contents = json.load(ifile) 1759 except FileNotFoundError: 1760 raise unittest.SkipTest('Compiler db not found') 1761 # If Ninja is using .rsp files, generate them, read their contents, and 1762 # replace it as the command for all compile commands in the parsed json. 1763 if len(contents) > 0 and contents[0]['command'].endswith('.rsp'): 1764 # Pretend to build so that the rsp files are generated 1765 self.build(extra_args=['-d', 'keeprsp', '-n']) 1766 for each in contents: 1767 # Extract the actual command from the rsp file 1768 compiler, rsp = each['command'].split(' @') 1769 rsp = os.path.join(self.builddir, rsp) 1770 # Replace the command with its contents 1771 with open(rsp, 'r', encoding='utf-8') as f: 1772 each['command'] = compiler + ' ' + f.read() 1773 return contents 1774 1775 def get_meson_log(self): 1776 with open(os.path.join(self.builddir, 'meson-logs', 'meson-log.txt')) as f: 1777 return f.readlines() 1778 1779 def get_meson_log_compiler_checks(self): 1780 ''' 1781 Fetch a list command-lines run by meson for compiler checks. 1782 Each command-line is returned as a list of arguments. 1783 ''' 1784 log = self.get_meson_log() 1785 prefix = 'Command line:' 1786 cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)] 1787 return cmds 1788 1789 def get_meson_log_sanitychecks(self): 1790 ''' 1791 Same as above, but for the sanity checks that were run 1792 ''' 1793 log = self.get_meson_log() 1794 prefix = 'Sanity check compiler command line:' 1795 cmds = [l[len(prefix):].split() for l in log if l.startswith(prefix)] 1796 return cmds 1797 1798 def introspect(self, args): 1799 if isinstance(args, str): 1800 args = [args] 1801 out = subprocess.check_output(self.mintro_command + args + [self.builddir], 1802 universal_newlines=True) 1803 return json.loads(out) 1804 1805 def introspect_directory(self, directory, args): 1806 if isinstance(args, str): 1807 args = [args] 1808 out = subprocess.check_output(self.mintro_command + args + [directory], 1809 universal_newlines=True) 1810 try: 1811 obj = json.loads(out) 1812 except Exception as e: 1813 print(out) 1814 raise e 1815 return obj 1816 1817 def assertPathEqual(self, path1, path2): 1818 ''' 1819 Handles a lot of platform-specific quirks related to paths such as 1820 separator, case-sensitivity, etc. 1821 ''' 1822 self.assertEqual(PurePath(path1), PurePath(path2)) 1823 1824 def assertPathListEqual(self, pathlist1, pathlist2): 1825 self.assertEqual(len(pathlist1), len(pathlist2)) 1826 worklist = list(zip(pathlist1, pathlist2)) 1827 for i in worklist: 1828 if i[0] is None: 1829 self.assertEqual(i[0], i[1]) 1830 else: 1831 self.assertPathEqual(i[0], i[1]) 1832 1833 def assertPathBasenameEqual(self, path, basename): 1834 msg = '{!r} does not end with {!r}'.format(path, basename) 1835 # We cannot use os.path.basename because it returns '' when the path 1836 # ends with '/' for some silly reason. This is not how the UNIX utility 1837 # `basename` works. 1838 path_basename = PurePath(path).parts[-1] 1839 self.assertEqual(PurePath(path_basename), PurePath(basename), msg) 1840 1841 def assertReconfiguredBuildIsNoop(self): 1842 'Assert that we reconfigured and then there was nothing to do' 1843 ret = self.build() 1844 self.assertIn('The Meson build system', ret) 1845 if self.backend is Backend.ninja: 1846 for line in ret.split('\n'): 1847 if line in self.no_rebuild_stdout: 1848 break 1849 else: 1850 raise AssertionError('build was reconfigured, but was not no-op') 1851 elif self.backend is Backend.vs: 1852 # Ensure that some target said that no rebuild was done 1853 # XXX: Note CustomBuild did indeed rebuild, because of the regen checker! 1854 self.assertIn('ClCompile:\n All outputs are up-to-date.', ret) 1855 self.assertIn('Link:\n All outputs are up-to-date.', ret) 1856 # Ensure that no targets were built 1857 self.assertNotRegex(ret, re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE)) 1858 self.assertNotRegex(ret, re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE)) 1859 elif self.backend is Backend.xcode: 1860 raise unittest.SkipTest('Please help us fix this test on the xcode backend') 1861 else: 1862 raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name)) 1863 1864 def assertBuildIsNoop(self): 1865 ret = self.build() 1866 if self.backend is Backend.ninja: 1867 self.assertIn(ret.split('\n')[-2], self.no_rebuild_stdout) 1868 elif self.backend is Backend.vs: 1869 # Ensure that some target of each type said that no rebuild was done 1870 # We always have at least one CustomBuild target for the regen checker 1871 self.assertIn('CustomBuild:\n All outputs are up-to-date.', ret) 1872 self.assertIn('ClCompile:\n All outputs are up-to-date.', ret) 1873 self.assertIn('Link:\n All outputs are up-to-date.', ret) 1874 # Ensure that no targets were built 1875 self.assertNotRegex(ret, re.compile('CustomBuild:\n [^\n]*cl', flags=re.IGNORECASE)) 1876 self.assertNotRegex(ret, re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE)) 1877 self.assertNotRegex(ret, re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE)) 1878 elif self.backend is Backend.xcode: 1879 raise unittest.SkipTest('Please help us fix this test on the xcode backend') 1880 else: 1881 raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name)) 1882 1883 def assertRebuiltTarget(self, target): 1884 ret = self.build() 1885 if self.backend is Backend.ninja: 1886 self.assertIn('Linking target {}'.format(target), ret) 1887 elif self.backend is Backend.vs: 1888 # Ensure that this target was rebuilt 1889 linkre = re.compile('Link:\n [^\n]*link[^\n]*' + target, flags=re.IGNORECASE) 1890 self.assertRegex(ret, linkre) 1891 elif self.backend is Backend.xcode: 1892 raise unittest.SkipTest('Please help us fix this test on the xcode backend') 1893 else: 1894 raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name)) 1895 1896 @staticmethod 1897 def get_target_from_filename(filename): 1898 base = os.path.splitext(filename)[0] 1899 if base.startswith(('lib', 'cyg')): 1900 return base[3:] 1901 return base 1902 1903 def assertBuildRelinkedOnlyTarget(self, target): 1904 ret = self.build() 1905 if self.backend is Backend.ninja: 1906 linked_targets = [] 1907 for line in ret.split('\n'): 1908 if 'Linking target' in line: 1909 fname = line.rsplit('target ')[-1] 1910 linked_targets.append(self.get_target_from_filename(fname)) 1911 self.assertEqual(linked_targets, [target]) 1912 elif self.backend is Backend.vs: 1913 # Ensure that this target was rebuilt 1914 linkre = re.compile(r'Link:\n [^\n]*link.exe[^\n]*/OUT:".\\([^"]*)"', flags=re.IGNORECASE) 1915 matches = linkre.findall(ret) 1916 self.assertEqual(len(matches), 1, msg=matches) 1917 self.assertEqual(self.get_target_from_filename(matches[0]), target) 1918 elif self.backend is Backend.xcode: 1919 raise unittest.SkipTest('Please help us fix this test on the xcode backend') 1920 else: 1921 raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name)) 1922 1923 def assertPathExists(self, path): 1924 m = 'Path {!r} should exist'.format(path) 1925 self.assertTrue(os.path.exists(path), msg=m) 1926 1927 def assertPathDoesNotExist(self, path): 1928 m = 'Path {!r} should not exist'.format(path) 1929 self.assertFalse(os.path.exists(path), msg=m) 1930 1931 1932class AllPlatformTests(BasePlatformTests): 1933 ''' 1934 Tests that should run on all platforms 1935 ''' 1936 1937 def test_default_options_prefix(self): 1938 ''' 1939 Tests that setting a prefix in default_options in project() works. 1940 Can't be an ordinary test because we pass --prefix to meson there. 1941 https://github.com/mesonbuild/meson/issues/1349 1942 ''' 1943 testdir = os.path.join(self.common_test_dir, '90 default options') 1944 self.init(testdir, default_args=False) 1945 opts = self.introspect('--buildoptions') 1946 for opt in opts: 1947 if opt['name'] == 'prefix': 1948 prefix = opt['value'] 1949 self.assertEqual(prefix, '/absoluteprefix') 1950 1951 def test_do_conf_file_preserve_newlines(self): 1952 1953 def conf_file(in_data, confdata): 1954 with temp_filename() as fin: 1955 with open(fin, 'wb') as fobj: 1956 fobj.write(in_data.encode('utf-8')) 1957 with temp_filename() as fout: 1958 mesonbuild.mesonlib.do_conf_file(fin, fout, confdata, 'meson') 1959 with open(fout, 'rb') as fobj: 1960 return fobj.read().decode('utf-8') 1961 1962 confdata = {'VAR': ('foo', 'bar')} 1963 self.assertEqual(conf_file('@VAR@\n@VAR@\n', confdata), 'foo\nfoo\n') 1964 self.assertEqual(conf_file('@VAR@\r\n@VAR@\r\n', confdata), 'foo\r\nfoo\r\n') 1965 1966 def test_do_conf_file_by_format(self): 1967 def conf_str(in_data, confdata, vformat): 1968 (result, missing_variables, confdata_useless) = mesonbuild.mesonlib.do_conf_str(in_data, confdata, variable_format = vformat) 1969 return '\n'.join(result) 1970 1971 def check_formats(confdata, result): 1972 self.assertEqual(conf_str(['#mesondefine VAR'], confdata, 'meson'), result) 1973 self.assertEqual(conf_str(['#cmakedefine VAR ${VAR}'], confdata, 'cmake'), result) 1974 self.assertEqual(conf_str(['#cmakedefine VAR @VAR@'], confdata, 'cmake@'), result) 1975 1976 confdata = ConfigurationData() 1977 # Key error as they do not exists 1978 check_formats(confdata, '/* #undef VAR */\n') 1979 1980 # Check boolean 1981 confdata.values = {'VAR': (False, 'description')} 1982 check_formats(confdata, '#undef VAR\n') 1983 confdata.values = {'VAR': (True, 'description')} 1984 check_formats(confdata, '#define VAR\n') 1985 1986 # Check string 1987 confdata.values = {'VAR': ('value', 'description')} 1988 check_formats(confdata, '#define VAR value\n') 1989 1990 # Check integer 1991 confdata.values = {'VAR': (10, 'description')} 1992 check_formats(confdata, '#define VAR 10\n') 1993 1994 # Check multiple string with cmake formats 1995 confdata.values = {'VAR': ('value', 'description')} 1996 self.assertEqual(conf_str(['#cmakedefine VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value\n') 1997 self.assertEqual(conf_str(['#define VAR xxx @VAR@ yyy @VAR@'], confdata, 'cmake@'), '#define VAR xxx value yyy value') 1998 self.assertEqual(conf_str(['#cmakedefine VAR xxx ${VAR} yyy ${VAR}'], confdata, 'cmake'), '#define VAR xxx value yyy value\n') 1999 self.assertEqual(conf_str(['#define VAR xxx ${VAR} yyy ${VAR}'], confdata, 'cmake'), '#define VAR xxx value yyy value') 2000 2001 # Handles meson format exceptions 2002 # Unknown format 2003 self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR xxx'], confdata, 'unknown_format') 2004 # More than 2 params in mesondefine 2005 self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR xxx'], confdata, 'meson') 2006 # Mismatched line with format 2007 self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#cmakedefine VAR'], confdata, 'meson') 2008 self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR'], confdata, 'cmake') 2009 self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR'], confdata, 'cmake@') 2010 # Dict value in confdata 2011 confdata.values = {'VAR': (['value'], 'description')} 2012 self.assertRaises(mesonbuild.mesonlib.MesonException, conf_str, ['#mesondefine VAR'], confdata, 'meson') 2013 2014 def test_absolute_prefix_libdir(self): 2015 ''' 2016 Tests that setting absolute paths for --prefix and --libdir work. Can't 2017 be an ordinary test because these are set via the command-line. 2018 https://github.com/mesonbuild/meson/issues/1341 2019 https://github.com/mesonbuild/meson/issues/1345 2020 ''' 2021 testdir = os.path.join(self.common_test_dir, '90 default options') 2022 # on Windows, /someabs is *not* an absolute path 2023 prefix = 'x:/someabs' if is_windows() else '/someabs' 2024 libdir = 'libdir' 2025 extra_args = ['--prefix=' + prefix, 2026 # This can just be a relative path, but we want to test 2027 # that passing this as an absolute path also works 2028 '--libdir=' + prefix + '/' + libdir] 2029 self.init(testdir, extra_args=extra_args, default_args=False) 2030 opts = self.introspect('--buildoptions') 2031 for opt in opts: 2032 if opt['name'] == 'prefix': 2033 self.assertEqual(prefix, opt['value']) 2034 elif opt['name'] == 'libdir': 2035 self.assertEqual(libdir, opt['value']) 2036 2037 def test_libdir_must_be_inside_prefix(self): 2038 ''' 2039 Tests that libdir is forced to be inside prefix no matter how it is set. 2040 Must be a unit test for obvious reasons. 2041 ''' 2042 testdir = os.path.join(self.common_test_dir, '1 trivial') 2043 # libdir being inside prefix is ok 2044 if is_windows(): 2045 args = ['--prefix', 'x:/opt', '--libdir', 'x:/opt/lib32'] 2046 else: 2047 args = ['--prefix', '/opt', '--libdir', '/opt/lib32'] 2048 self.init(testdir, extra_args=args) 2049 self.wipe() 2050 # libdir not being inside prefix is not ok 2051 if is_windows(): 2052 args = ['--prefix', 'x:/usr', '--libdir', 'x:/opt/lib32'] 2053 else: 2054 args = ['--prefix', '/usr', '--libdir', '/opt/lib32'] 2055 self.assertRaises(subprocess.CalledProcessError, self.init, testdir, extra_args=args) 2056 self.wipe() 2057 # libdir must be inside prefix even when set via mesonconf 2058 self.init(testdir) 2059 if is_windows(): 2060 self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=x:/opt', False) 2061 else: 2062 self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt', False) 2063 2064 def test_prefix_dependent_defaults(self): 2065 ''' 2066 Tests that configured directory paths are set to prefix dependent 2067 defaults. 2068 ''' 2069 testdir = os.path.join(self.common_test_dir, '1 trivial') 2070 expected = { 2071 '/opt': {'prefix': '/opt', 2072 'bindir': 'bin', 'datadir': 'share', 'includedir': 'include', 2073 'infodir': 'share/info', 2074 'libexecdir': 'libexec', 'localedir': 'share/locale', 2075 'localstatedir': 'var', 'mandir': 'share/man', 2076 'sbindir': 'sbin', 'sharedstatedir': 'com', 2077 'sysconfdir': 'etc'}, 2078 '/usr': {'prefix': '/usr', 2079 'bindir': 'bin', 'datadir': 'share', 'includedir': 'include', 2080 'infodir': 'share/info', 2081 'libexecdir': 'libexec', 'localedir': 'share/locale', 2082 'localstatedir': '/var', 'mandir': 'share/man', 2083 'sbindir': 'sbin', 'sharedstatedir': '/var/lib', 2084 'sysconfdir': '/etc'}, 2085 '/usr/local': {'prefix': '/usr/local', 2086 'bindir': 'bin', 'datadir': 'share', 2087 'includedir': 'include', 'infodir': 'share/info', 2088 'libexecdir': 'libexec', 2089 'localedir': 'share/locale', 2090 'localstatedir': '/var/local', 'mandir': 'share/man', 2091 'sbindir': 'sbin', 'sharedstatedir': '/var/local/lib', 2092 'sysconfdir': 'etc'}, 2093 # N.B. We don't check 'libdir' as it's platform dependent, see 2094 # default_libdir(): 2095 } 2096 2097 if mesonbuild.mesonlib.default_prefix() == '/usr/local': 2098 expected[None] = expected['/usr/local'] 2099 2100 for prefix in expected: 2101 args = [] 2102 if prefix: 2103 args += ['--prefix', prefix] 2104 self.init(testdir, extra_args=args, default_args=False) 2105 opts = self.introspect('--buildoptions') 2106 for opt in opts: 2107 name = opt['name'] 2108 value = opt['value'] 2109 if name in expected[prefix]: 2110 self.assertEqual(value, expected[prefix][name]) 2111 self.wipe() 2112 2113 def test_default_options_prefix_dependent_defaults(self): 2114 ''' 2115 Tests that setting a prefix in default_options in project() sets prefix 2116 dependent defaults for other options, and that those defaults can 2117 be overridden in default_options or by the command line. 2118 ''' 2119 testdir = os.path.join(self.common_test_dir, '168 default options prefix dependent defaults') 2120 expected = { 2121 '': 2122 {'prefix': '/usr', 2123 'sysconfdir': '/etc', 2124 'localstatedir': '/var', 2125 'sharedstatedir': '/sharedstate'}, 2126 '--prefix=/usr': 2127 {'prefix': '/usr', 2128 'sysconfdir': '/etc', 2129 'localstatedir': '/var', 2130 'sharedstatedir': '/sharedstate'}, 2131 '--sharedstatedir=/var/state': 2132 {'prefix': '/usr', 2133 'sysconfdir': '/etc', 2134 'localstatedir': '/var', 2135 'sharedstatedir': '/var/state'}, 2136 '--sharedstatedir=/var/state --prefix=/usr --sysconfdir=sysconf': 2137 {'prefix': '/usr', 2138 'sysconfdir': 'sysconf', 2139 'localstatedir': '/var', 2140 'sharedstatedir': '/var/state'}, 2141 } 2142 for args in expected: 2143 self.init(testdir, extra_args=args.split(), default_args=False) 2144 opts = self.introspect('--buildoptions') 2145 for opt in opts: 2146 name = opt['name'] 2147 value = opt['value'] 2148 if name in expected[args]: 2149 self.assertEqual(value, expected[args][name]) 2150 self.wipe() 2151 2152 def test_clike_get_library_dirs(self): 2153 env = get_fake_env() 2154 cc = env.detect_c_compiler(MachineChoice.HOST) 2155 for d in cc.get_library_dirs(env): 2156 self.assertTrue(os.path.exists(d)) 2157 self.assertTrue(os.path.isdir(d)) 2158 self.assertTrue(os.path.isabs(d)) 2159 2160 def test_static_library_overwrite(self): 2161 ''' 2162 Tests that static libraries are never appended to, always overwritten. 2163 Has to be a unit test because this involves building a project, 2164 reconfiguring, and building it again so that `ar` is run twice on the 2165 same static library. 2166 https://github.com/mesonbuild/meson/issues/1355 2167 ''' 2168 testdir = os.path.join(self.common_test_dir, '3 static') 2169 env = get_fake_env(testdir, self.builddir, self.prefix) 2170 cc = env.detect_c_compiler(MachineChoice.HOST) 2171 static_linker = env.detect_static_linker(cc) 2172 if is_windows(): 2173 raise unittest.SkipTest('https://github.com/mesonbuild/meson/issues/1526') 2174 if not isinstance(static_linker, mesonbuild.linkers.ArLinker): 2175 raise unittest.SkipTest('static linker is not `ar`') 2176 # Configure 2177 self.init(testdir) 2178 # Get name of static library 2179 targets = self.introspect('--targets') 2180 self.assertEqual(len(targets), 1) 2181 libname = targets[0]['filename'][0] 2182 # Build and get contents of static library 2183 self.build() 2184 before = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split() 2185 # Filter out non-object-file contents 2186 before = [f for f in before if f.endswith(('.o', '.obj'))] 2187 # Static library should contain only one object 2188 self.assertEqual(len(before), 1, msg=before) 2189 # Change the source to be built into the static library 2190 self.setconf('-Dsource=libfile2.c') 2191 self.build() 2192 after = self._run(['ar', 't', os.path.join(self.builddir, libname)]).split() 2193 # Filter out non-object-file contents 2194 after = [f for f in after if f.endswith(('.o', '.obj'))] 2195 # Static library should contain only one object 2196 self.assertEqual(len(after), 1, msg=after) 2197 # and the object must have changed 2198 self.assertNotEqual(before, after) 2199 2200 def test_static_compile_order(self): 2201 ''' 2202 Test that the order of files in a compiler command-line while compiling 2203 and linking statically is deterministic. This can't be an ordinary test 2204 case because we need to inspect the compiler database. 2205 https://github.com/mesonbuild/meson/pull/951 2206 ''' 2207 testdir = os.path.join(self.common_test_dir, '5 linkstatic') 2208 self.init(testdir) 2209 compdb = self.get_compdb() 2210 # Rules will get written out in this order 2211 self.assertTrue(compdb[0]['file'].endswith("libfile.c")) 2212 self.assertTrue(compdb[1]['file'].endswith("libfile2.c")) 2213 self.assertTrue(compdb[2]['file'].endswith("libfile3.c")) 2214 self.assertTrue(compdb[3]['file'].endswith("libfile4.c")) 2215 # FIXME: We don't have access to the linker command 2216 2217 def test_run_target_files_path(self): 2218 ''' 2219 Test that run_targets are run from the correct directory 2220 https://github.com/mesonbuild/meson/issues/957 2221 ''' 2222 testdir = os.path.join(self.common_test_dir, '54 run target') 2223 self.init(testdir) 2224 self.run_target('check_exists') 2225 2226 def test_install_introspection(self): 2227 ''' 2228 Tests that the Meson introspection API exposes install filenames correctly 2229 https://github.com/mesonbuild/meson/issues/829 2230 ''' 2231 if self.backend is not Backend.ninja: 2232 raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name)) 2233 testdir = os.path.join(self.common_test_dir, '8 install') 2234 self.init(testdir) 2235 intro = self.introspect('--targets') 2236 if intro[0]['type'] == 'executable': 2237 intro = intro[::-1] 2238 self.assertPathListEqual(intro[0]['install_filename'], ['/usr/lib/libstat.a']) 2239 self.assertPathListEqual(intro[1]['install_filename'], ['/usr/bin/prog' + exe_suffix]) 2240 2241 def test_install_subdir_introspection(self): 2242 ''' 2243 Test that the Meson introspection API also contains subdir install information 2244 https://github.com/mesonbuild/meson/issues/5556 2245 ''' 2246 testdir = os.path.join(self.common_test_dir, '62 install subdir') 2247 self.init(testdir) 2248 intro = self.introspect('--installed') 2249 expected = { 2250 'sub2': 'share/sub2', 2251 'subdir/sub1': 'share/sub1', 2252 'subdir/sub_elided': 'share', 2253 'sub1': 'share/sub1', 2254 'sub/sub1': 'share/sub1', 2255 'sub_elided': 'share', 2256 'nested_elided/sub': 'share', 2257 } 2258 2259 self.assertEqual(len(intro), len(expected)) 2260 2261 # Convert expected to PurePath 2262 expected_converted = {PurePath(os.path.join(testdir, key)): PurePath(os.path.join(self.prefix, val)) for key, val in expected.items()} 2263 intro_converted = {PurePath(key): PurePath(val) for key, val in intro.items()} 2264 2265 for src, dst in expected_converted.items(): 2266 self.assertIn(src, intro_converted) 2267 self.assertEqual(dst, intro_converted[src]) 2268 2269 def test_install_introspection_multiple_outputs(self): 2270 ''' 2271 Tests that the Meson introspection API exposes multiple install filenames correctly without crashing 2272 https://github.com/mesonbuild/meson/pull/4555 2273 2274 Reverted to the first file only because of https://github.com/mesonbuild/meson/pull/4547#discussion_r244173438 2275 TODO Change the format to a list officially in a followup PR 2276 ''' 2277 if self.backend is not Backend.ninja: 2278 raise unittest.SkipTest('{!r} backend can\'t install files'.format(self.backend.name)) 2279 testdir = os.path.join(self.common_test_dir, '144 custom target multiple outputs') 2280 self.init(testdir) 2281 intro = self.introspect('--targets') 2282 if intro[0]['type'] == 'executable': 2283 intro = intro[::-1] 2284 self.assertPathListEqual(intro[0]['install_filename'], ['/usr/include/diff.h', '/usr/bin/diff.sh']) 2285 self.assertPathListEqual(intro[1]['install_filename'], ['/opt/same.h', '/opt/same.sh']) 2286 self.assertPathListEqual(intro[2]['install_filename'], ['/usr/include/first.h', None]) 2287 self.assertPathListEqual(intro[3]['install_filename'], [None, '/usr/bin/second.sh']) 2288 2289 def test_install_log_content(self): 2290 ''' 2291 Tests that the install-log.txt is consistent with the installed files and directories. 2292 Specifically checks that the log file only contains one entry per file/directory. 2293 https://github.com/mesonbuild/meson/issues/4499 2294 ''' 2295 testdir = os.path.join(self.common_test_dir, '62 install subdir') 2296 self.init(testdir) 2297 self.install() 2298 installpath = Path(self.installdir) 2299 # Find installed files and directories 2300 expected = {installpath: 0} 2301 for name in installpath.rglob('*'): 2302 expected[name] = 0 2303 # Find logged files and directories 2304 with Path(self.builddir, 'meson-logs', 'install-log.txt').open() as f: 2305 logged = list(map(lambda l: Path(l.strip()), 2306 filter(lambda l: not l.startswith('#'), 2307 f.readlines()))) 2308 for name in logged: 2309 self.assertTrue(name in expected, 'Log contains extra entry {}'.format(name)) 2310 expected[name] += 1 2311 2312 for name, count in expected.items(): 2313 self.assertGreater(count, 0, 'Log is missing entry for {}'.format(name)) 2314 self.assertLess(count, 2, 'Log has multiple entries for {}'.format(name)) 2315 2316 def test_uninstall(self): 2317 exename = os.path.join(self.installdir, 'usr/bin/prog' + exe_suffix) 2318 testdir = os.path.join(self.common_test_dir, '8 install') 2319 self.init(testdir) 2320 self.assertPathDoesNotExist(exename) 2321 self.install() 2322 self.assertPathExists(exename) 2323 self.uninstall() 2324 self.assertPathDoesNotExist(exename) 2325 2326 def test_forcefallback(self): 2327 testdir = os.path.join(self.unit_test_dir, '31 forcefallback') 2328 self.init(testdir, extra_args=['--wrap-mode=forcefallback']) 2329 self.build() 2330 self.run_tests() 2331 2332 def test_force_fallback_for(self): 2333 testdir = os.path.join(self.unit_test_dir, '31 forcefallback') 2334 self.init(testdir, extra_args=['--force-fallback-for=zlib,foo']) 2335 self.build() 2336 self.run_tests() 2337 2338 def test_env_ops_dont_stack(self): 2339 ''' 2340 Test that env ops prepend/append do not stack, and that this usage issues a warning 2341 ''' 2342 testdir = os.path.join(self.unit_test_dir, '63 test env does not stack') 2343 out = self.init(testdir) 2344 self.assertRegex(out, r'WARNING: Overriding.*TEST_VAR_APPEND') 2345 self.assertRegex(out, r'WARNING: Overriding.*TEST_VAR_PREPEND') 2346 self.assertNotRegex(out, r'WARNING: Overriding.*TEST_VAR_SET') 2347 self.run_tests() 2348 2349 def test_testsetups(self): 2350 if not shutil.which('valgrind'): 2351 raise unittest.SkipTest('Valgrind not installed.') 2352 testdir = os.path.join(self.unit_test_dir, '2 testsetups') 2353 self.init(testdir) 2354 self.build() 2355 # Run tests without setup 2356 self.run_tests() 2357 with open(os.path.join(self.logdir, 'testlog.txt')) as f: 2358 basic_log = f.read() 2359 # Run buggy test with setup that has env that will make it fail 2360 self.assertRaises(subprocess.CalledProcessError, 2361 self._run, self.mtest_command + ['--setup=valgrind']) 2362 with open(os.path.join(self.logdir, 'testlog-valgrind.txt')) as f: 2363 vg_log = f.read() 2364 self.assertFalse('TEST_ENV is set' in basic_log) 2365 self.assertFalse('Memcheck' in basic_log) 2366 self.assertTrue('TEST_ENV is set' in vg_log) 2367 self.assertTrue('Memcheck' in vg_log) 2368 # Run buggy test with setup without env that will pass 2369 self._run(self.mtest_command + ['--setup=wrapper']) 2370 # Setup with no properties works 2371 self._run(self.mtest_command + ['--setup=empty']) 2372 # Setup with only env works 2373 self._run(self.mtest_command + ['--setup=onlyenv']) 2374 self._run(self.mtest_command + ['--setup=onlyenv2']) 2375 self._run(self.mtest_command + ['--setup=onlyenv3']) 2376 # Setup with only a timeout works 2377 self._run(self.mtest_command + ['--setup=timeout']) 2378 2379 def test_testsetup_selection(self): 2380 testdir = os.path.join(self.unit_test_dir, '14 testsetup selection') 2381 self.init(testdir) 2382 self.build() 2383 2384 # Run tests without setup 2385 self.run_tests() 2386 2387 self.assertRaises(subprocess.CalledProcessError, self._run, self.mtest_command + ['--setup=missingfromfoo']) 2388 self._run(self.mtest_command + ['--setup=missingfromfoo', '--no-suite=foo:']) 2389 2390 self._run(self.mtest_command + ['--setup=worksforall']) 2391 self._run(self.mtest_command + ['--setup=main:worksforall']) 2392 2393 self.assertRaises(subprocess.CalledProcessError, self._run, 2394 self.mtest_command + ['--setup=onlyinbar']) 2395 self.assertRaises(subprocess.CalledProcessError, self._run, 2396 self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:']) 2397 self._run(self.mtest_command + ['--setup=onlyinbar', '--no-suite=main:', '--no-suite=foo:']) 2398 self._run(self.mtest_command + ['--setup=bar:onlyinbar']) 2399 self.assertRaises(subprocess.CalledProcessError, self._run, 2400 self.mtest_command + ['--setup=foo:onlyinbar']) 2401 self.assertRaises(subprocess.CalledProcessError, self._run, 2402 self.mtest_command + ['--setup=main:onlyinbar']) 2403 2404 def test_testsetup_default(self): 2405 testdir = os.path.join(self.unit_test_dir, '49 testsetup default') 2406 self.init(testdir) 2407 self.build() 2408 2409 # Run tests without --setup will cause the default setup to be used 2410 self.run_tests() 2411 with open(os.path.join(self.logdir, 'testlog.txt')) as f: 2412 default_log = f.read() 2413 2414 # Run tests with explicitly using the same setup that is set as default 2415 self._run(self.mtest_command + ['--setup=mydefault']) 2416 with open(os.path.join(self.logdir, 'testlog-mydefault.txt')) as f: 2417 mydefault_log = f.read() 2418 2419 # Run tests with another setup 2420 self._run(self.mtest_command + ['--setup=other']) 2421 with open(os.path.join(self.logdir, 'testlog-other.txt')) as f: 2422 other_log = f.read() 2423 2424 self.assertTrue('ENV_A is 1' in default_log) 2425 self.assertTrue('ENV_B is 2' in default_log) 2426 self.assertTrue('ENV_C is 2' in default_log) 2427 2428 self.assertTrue('ENV_A is 1' in mydefault_log) 2429 self.assertTrue('ENV_B is 2' in mydefault_log) 2430 self.assertTrue('ENV_C is 2' in mydefault_log) 2431 2432 self.assertTrue('ENV_A is 1' in other_log) 2433 self.assertTrue('ENV_B is 3' in other_log) 2434 self.assertTrue('ENV_C is 2' in other_log) 2435 2436 def assertFailedTestCount(self, failure_count, command): 2437 try: 2438 self._run(command) 2439 self.assertEqual(0, failure_count, 'Expected %d tests to fail.' % failure_count) 2440 except subprocess.CalledProcessError as e: 2441 self.assertEqual(e.returncode, failure_count) 2442 2443 def test_suite_selection(self): 2444 testdir = os.path.join(self.unit_test_dir, '4 suite selection') 2445 self.init(testdir) 2446 self.build() 2447 2448 self.assertFailedTestCount(4, self.mtest_command) 2449 2450 self.assertFailedTestCount(0, self.mtest_command + ['--suite', ':success']) 2451 self.assertFailedTestCount(3, self.mtest_command + ['--suite', ':fail']) 2452 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', ':success']) 2453 self.assertFailedTestCount(1, self.mtest_command + ['--no-suite', ':fail']) 2454 2455 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj']) 2456 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc']) 2457 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail']) 2458 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix']) 2459 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj']) 2460 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc']) 2461 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail']) 2462 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix']) 2463 2464 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'mainprj:fail']) 2465 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'mainprj:success']) 2466 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'mainprj:fail']) 2467 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'mainprj:success']) 2468 2469 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail:fail']) 2470 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjfail:success']) 2471 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjfail:fail']) 2472 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjfail:success']) 2473 2474 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:fail']) 2475 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjsucc:success']) 2476 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:fail']) 2477 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjsucc:success']) 2478 2479 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjmix:fail']) 2480 self.assertFailedTestCount(0, self.mtest_command + ['--suite', 'subprjmix:success']) 2481 self.assertFailedTestCount(3, self.mtest_command + ['--no-suite', 'subprjmix:fail']) 2482 self.assertFailedTestCount(4, self.mtest_command + ['--no-suite', 'subprjmix:success']) 2483 2484 self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix:fail']) 2485 self.assertFailedTestCount(3, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj']) 2486 self.assertFailedTestCount(2, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail']) 2487 self.assertFailedTestCount(1, self.mtest_command + ['--suite', 'subprjfail', '--suite', 'subprjmix', '--suite', 'mainprj', '--no-suite', 'subprjmix:fail', 'mainprj-failing_test']) 2488 2489 self.assertFailedTestCount(2, self.mtest_command + ['--no-suite', 'subprjfail:fail', '--no-suite', 'subprjmix:fail']) 2490 2491 def test_build_by_default(self): 2492 testdir = os.path.join(self.common_test_dir, '133 build by default') 2493 self.init(testdir) 2494 self.build() 2495 genfile1 = os.path.join(self.builddir, 'generated1.dat') 2496 genfile2 = os.path.join(self.builddir, 'generated2.dat') 2497 exe1 = os.path.join(self.builddir, 'fooprog' + exe_suffix) 2498 exe2 = os.path.join(self.builddir, 'barprog' + exe_suffix) 2499 self.assertPathExists(genfile1) 2500 self.assertPathExists(genfile2) 2501 self.assertPathDoesNotExist(exe1) 2502 self.assertPathDoesNotExist(exe2) 2503 self.build(target=('fooprog' + exe_suffix)) 2504 self.assertPathExists(exe1) 2505 self.build(target=('barprog' + exe_suffix)) 2506 self.assertPathExists(exe2) 2507 2508 def test_internal_include_order(self): 2509 if mesonbuild.environment.detect_msys2_arch() and ('MESON_RSP_THRESHOLD' in os.environ): 2510 raise unittest.SkipTest('Test does not yet support gcc rsp files on msys2') 2511 2512 testdir = os.path.join(self.common_test_dir, '134 include order') 2513 self.init(testdir) 2514 execmd = fxecmd = None 2515 for cmd in self.get_compdb(): 2516 if 'someexe' in cmd['command']: 2517 execmd = cmd['command'] 2518 continue 2519 if 'somefxe' in cmd['command']: 2520 fxecmd = cmd['command'] 2521 continue 2522 if not execmd or not fxecmd: 2523 raise Exception('Could not find someexe and somfxe commands') 2524 # Check include order for 'someexe' 2525 incs = [a for a in split_args(execmd) if a.startswith("-I")] 2526 self.assertEqual(len(incs), 9) 2527 # Need to run the build so the private dir is created. 2528 self.build() 2529 pdirs = glob(os.path.join(self.builddir, 'sub4/someexe*.p')) 2530 self.assertEqual(len(pdirs), 1) 2531 privdir = pdirs[0][len(self.builddir)+1:] 2532 self.assertPathEqual(incs[0], "-I" + privdir) 2533 # target build subdir 2534 self.assertPathEqual(incs[1], "-Isub4") 2535 # target source subdir 2536 self.assertPathBasenameEqual(incs[2], 'sub4') 2537 # include paths added via per-target c_args: ['-I'...] 2538 self.assertPathBasenameEqual(incs[3], 'sub3') 2539 # target include_directories: build dir 2540 self.assertPathEqual(incs[4], "-Isub2") 2541 # target include_directories: source dir 2542 self.assertPathBasenameEqual(incs[5], 'sub2') 2543 # target internal dependency include_directories: build dir 2544 self.assertPathEqual(incs[6], "-Isub1") 2545 # target internal dependency include_directories: source dir 2546 self.assertPathBasenameEqual(incs[7], 'sub1') 2547 # custom target include dir 2548 self.assertPathEqual(incs[8], '-Ictsub') 2549 # Check include order for 'somefxe' 2550 incs = [a for a in split_args(fxecmd) if a.startswith('-I')] 2551 self.assertEqual(len(incs), 9) 2552 # target private dir 2553 pdirs = glob(os.path.join(self.builddir, 'somefxe*.p')) 2554 self.assertEqual(len(pdirs), 1) 2555 privdir = pdirs[0][len(self.builddir)+1:] 2556 self.assertPathEqual(incs[0], '-I' + privdir) 2557 # target build dir 2558 self.assertPathEqual(incs[1], '-I.') 2559 # target source dir 2560 self.assertPathBasenameEqual(incs[2], os.path.basename(testdir)) 2561 # target internal dependency correct include_directories: build dir 2562 self.assertPathEqual(incs[3], "-Isub4") 2563 # target internal dependency correct include_directories: source dir 2564 self.assertPathBasenameEqual(incs[4], 'sub4') 2565 # target internal dependency dep include_directories: build dir 2566 self.assertPathEqual(incs[5], "-Isub1") 2567 # target internal dependency dep include_directories: source dir 2568 self.assertPathBasenameEqual(incs[6], 'sub1') 2569 # target internal dependency wrong include_directories: build dir 2570 self.assertPathEqual(incs[7], "-Isub2") 2571 # target internal dependency wrong include_directories: source dir 2572 self.assertPathBasenameEqual(incs[8], 'sub2') 2573 2574 def test_compiler_detection(self): 2575 ''' 2576 Test that automatic compiler detection and setting from the environment 2577 both work just fine. This is needed because while running project tests 2578 and other unit tests, we always read CC/CXX/etc from the environment. 2579 ''' 2580 gnu = mesonbuild.compilers.GnuCompiler 2581 clang = mesonbuild.compilers.ClangCompiler 2582 intel = mesonbuild.compilers.IntelGnuLikeCompiler 2583 msvc = (mesonbuild.compilers.VisualStudioCCompiler, mesonbuild.compilers.VisualStudioCPPCompiler) 2584 clangcl = (mesonbuild.compilers.ClangClCCompiler, mesonbuild.compilers.ClangClCPPCompiler) 2585 ar = mesonbuild.linkers.ArLinker 2586 lib = mesonbuild.linkers.VisualStudioLinker 2587 langs = [('c', 'CC'), ('cpp', 'CXX')] 2588 if not is_windows() and platform.machine().lower() != 'e2k': 2589 langs += [('objc', 'OBJC'), ('objcpp', 'OBJCXX')] 2590 testdir = os.path.join(self.unit_test_dir, '5 compiler detection') 2591 env = get_fake_env(testdir, self.builddir, self.prefix) 2592 for lang, evar in langs: 2593 # Detect with evar and do sanity checks on that 2594 if evar in os.environ: 2595 ecc = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST) 2596 self.assertTrue(ecc.version) 2597 elinker = env.detect_static_linker(ecc) 2598 # Pop it so we don't use it for the next detection 2599 evalue = os.environ.pop(evar) 2600 # Very rough/strict heuristics. Would never work for actual 2601 # compiler detection, but should be ok for the tests. 2602 ebase = os.path.basename(evalue) 2603 if ebase.startswith('g') or ebase.endswith(('-gcc', '-g++')): 2604 self.assertIsInstance(ecc, gnu) 2605 self.assertIsInstance(elinker, ar) 2606 elif 'clang-cl' in ebase: 2607 self.assertIsInstance(ecc, clangcl) 2608 self.assertIsInstance(elinker, lib) 2609 elif 'clang' in ebase: 2610 self.assertIsInstance(ecc, clang) 2611 self.assertIsInstance(elinker, ar) 2612 elif ebase.startswith('ic'): 2613 self.assertIsInstance(ecc, intel) 2614 self.assertIsInstance(elinker, ar) 2615 elif ebase.startswith('cl'): 2616 self.assertIsInstance(ecc, msvc) 2617 self.assertIsInstance(elinker, lib) 2618 else: 2619 raise AssertionError('Unknown compiler {!r}'.format(evalue)) 2620 # Check that we actually used the evalue correctly as the compiler 2621 self.assertEqual(ecc.get_exelist(), split_args(evalue)) 2622 # Do auto-detection of compiler based on platform, PATH, etc. 2623 cc = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST) 2624 self.assertTrue(cc.version) 2625 linker = env.detect_static_linker(cc) 2626 # Check compiler type 2627 if isinstance(cc, gnu): 2628 self.assertIsInstance(linker, ar) 2629 if is_osx(): 2630 self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) 2631 elif is_sunos(): 2632 self.assertIsInstance(cc.linker, (mesonbuild.linkers.SolarisDynamicLinker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin)) 2633 else: 2634 self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin) 2635 if isinstance(cc, clangcl): 2636 self.assertIsInstance(linker, lib) 2637 self.assertIsInstance(cc.linker, mesonbuild.linkers.ClangClDynamicLinker) 2638 if isinstance(cc, clang): 2639 self.assertIsInstance(linker, ar) 2640 if is_osx(): 2641 self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) 2642 elif is_windows(): 2643 # This is clang, not clang-cl 2644 self.assertIsInstance(cc.linker, mesonbuild.linkers.MSVCDynamicLinker) 2645 else: 2646 self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuLikeDynamicLinkerMixin) 2647 if isinstance(cc, intel): 2648 self.assertIsInstance(linker, ar) 2649 if is_osx(): 2650 self.assertIsInstance(cc.linker, mesonbuild.linkers.AppleDynamicLinker) 2651 elif is_windows(): 2652 self.assertIsInstance(cc.linker, mesonbuild.linkers.XilinkDynamicLinker) 2653 else: 2654 self.assertIsInstance(cc.linker, mesonbuild.linkers.GnuDynamicLinker) 2655 if isinstance(cc, msvc): 2656 self.assertTrue(is_windows()) 2657 self.assertIsInstance(linker, lib) 2658 self.assertEqual(cc.id, 'msvc') 2659 self.assertTrue(hasattr(cc, 'is_64')) 2660 self.assertIsInstance(cc.linker, mesonbuild.linkers.MSVCDynamicLinker) 2661 # If we're on Windows CI, we know what the compiler will be 2662 if 'arch' in os.environ: 2663 if os.environ['arch'] == 'x64': 2664 self.assertTrue(cc.is_64) 2665 else: 2666 self.assertFalse(cc.is_64) 2667 # Set evar ourselves to a wrapper script that just calls the same 2668 # exelist + some argument. This is meant to test that setting 2669 # something like `ccache gcc -pipe` or `distcc ccache gcc` works. 2670 wrapper = os.path.join(testdir, 'compiler wrapper.py') 2671 wrappercc = python_command + [wrapper] + cc.get_exelist() + ['-DSOME_ARG'] 2672 wrappercc_s = '' 2673 for w in wrappercc: 2674 wrappercc_s += quote_arg(w) + ' ' 2675 os.environ[evar] = wrappercc_s 2676 wcc = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST) 2677 # Check static linker too 2678 wrapperlinker = python_command + [wrapper] + linker.get_exelist() + linker.get_always_args() 2679 wrapperlinker_s = '' 2680 for w in wrapperlinker: 2681 wrapperlinker_s += quote_arg(w) + ' ' 2682 os.environ['AR'] = wrapperlinker_s 2683 wlinker = env.detect_static_linker(wcc) 2684 # Pop it so we don't use it for the next detection 2685 evalue = os.environ.pop('AR') 2686 # Must be the same type since it's a wrapper around the same exelist 2687 self.assertIs(type(cc), type(wcc)) 2688 self.assertIs(type(linker), type(wlinker)) 2689 # Ensure that the exelist is correct 2690 self.assertEqual(wcc.get_exelist(), wrappercc) 2691 self.assertEqual(wlinker.get_exelist(), wrapperlinker) 2692 # Ensure that the version detection worked correctly 2693 self.assertEqual(cc.version, wcc.version) 2694 if hasattr(cc, 'is_64'): 2695 self.assertEqual(cc.is_64, wcc.is_64) 2696 2697 def test_always_prefer_c_compiler_for_asm(self): 2698 testdir = os.path.join(self.common_test_dir, '137 c cpp and asm') 2699 # Skip if building with MSVC 2700 env = get_fake_env(testdir, self.builddir, self.prefix) 2701 if env.detect_c_compiler(MachineChoice.HOST).get_id() == 'msvc': 2702 raise unittest.SkipTest('MSVC can\'t compile assembly') 2703 self.init(testdir) 2704 commands = {'c-asm': {}, 'cpp-asm': {}, 'cpp-c-asm': {}, 'c-cpp-asm': {}} 2705 for cmd in self.get_compdb(): 2706 # Get compiler 2707 split = split_args(cmd['command']) 2708 if split[0] == 'ccache': 2709 compiler = split[1] 2710 else: 2711 compiler = split[0] 2712 # Classify commands 2713 if 'Ic-asm' in cmd['command']: 2714 if cmd['file'].endswith('.S'): 2715 commands['c-asm']['asm'] = compiler 2716 elif cmd['file'].endswith('.c'): 2717 commands['c-asm']['c'] = compiler 2718 else: 2719 raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) 2720 elif 'Icpp-asm' in cmd['command']: 2721 if cmd['file'].endswith('.S'): 2722 commands['cpp-asm']['asm'] = compiler 2723 elif cmd['file'].endswith('.cpp'): 2724 commands['cpp-asm']['cpp'] = compiler 2725 else: 2726 raise AssertionError('{!r} found in cpp-asm?'.format(cmd['command'])) 2727 elif 'Ic-cpp-asm' in cmd['command']: 2728 if cmd['file'].endswith('.S'): 2729 commands['c-cpp-asm']['asm'] = compiler 2730 elif cmd['file'].endswith('.c'): 2731 commands['c-cpp-asm']['c'] = compiler 2732 elif cmd['file'].endswith('.cpp'): 2733 commands['c-cpp-asm']['cpp'] = compiler 2734 else: 2735 raise AssertionError('{!r} found in c-cpp-asm?'.format(cmd['command'])) 2736 elif 'Icpp-c-asm' in cmd['command']: 2737 if cmd['file'].endswith('.S'): 2738 commands['cpp-c-asm']['asm'] = compiler 2739 elif cmd['file'].endswith('.c'): 2740 commands['cpp-c-asm']['c'] = compiler 2741 elif cmd['file'].endswith('.cpp'): 2742 commands['cpp-c-asm']['cpp'] = compiler 2743 else: 2744 raise AssertionError('{!r} found in cpp-c-asm?'.format(cmd['command'])) 2745 else: 2746 raise AssertionError('Unknown command {!r} found'.format(cmd['command'])) 2747 # Check that .S files are always built with the C compiler 2748 self.assertEqual(commands['c-asm']['asm'], commands['c-asm']['c']) 2749 self.assertEqual(commands['c-asm']['asm'], commands['cpp-asm']['asm']) 2750 self.assertEqual(commands['cpp-asm']['asm'], commands['c-cpp-asm']['c']) 2751 self.assertEqual(commands['c-cpp-asm']['asm'], commands['c-cpp-asm']['c']) 2752 self.assertEqual(commands['cpp-c-asm']['asm'], commands['cpp-c-asm']['c']) 2753 self.assertNotEqual(commands['cpp-asm']['asm'], commands['cpp-asm']['cpp']) 2754 self.assertNotEqual(commands['c-cpp-asm']['c'], commands['c-cpp-asm']['cpp']) 2755 self.assertNotEqual(commands['cpp-c-asm']['c'], commands['cpp-c-asm']['cpp']) 2756 # Check that the c-asm target is always linked with the C linker 2757 build_ninja = os.path.join(self.builddir, 'build.ninja') 2758 with open(build_ninja, 'r', encoding='utf-8') as f: 2759 contents = f.read() 2760 m = re.search('build c-asm.*: c_LINKER', contents) 2761 self.assertIsNotNone(m, msg=contents) 2762 2763 def test_preprocessor_checks_CPPFLAGS(self): 2764 ''' 2765 Test that preprocessor compiler checks read CPPFLAGS and also CFLAGS but 2766 not LDFLAGS. 2767 ''' 2768 testdir = os.path.join(self.common_test_dir, '136 get define') 2769 define = 'MESON_TEST_DEFINE_VALUE' 2770 # NOTE: this list can't have \n, ' or " 2771 # \n is never substituted by the GNU pre-processor via a -D define 2772 # ' and " confuse split_args() even when they are escaped 2773 # % and # confuse the MSVC preprocessor 2774 # !, ^, *, and < confuse lcc preprocessor 2775 value = 'spaces and fun@$&()-=_+{}[]:;>?,./~`' 2776 for env_var in ['CPPFLAGS', 'CFLAGS']: 2777 env = {} 2778 env[env_var] = '-D{}="{}"'.format(define, value) 2779 env['LDFLAGS'] = '-DMESON_FAIL_VALUE=cflags-read'.format(define) 2780 self.init(testdir, extra_args=['-D{}={}'.format(define, value)], override_envvars=env) 2781 2782 def test_custom_target_exe_data_deterministic(self): 2783 testdir = os.path.join(self.common_test_dir, '113 custom target capture') 2784 self.init(testdir) 2785 meson_exe_dat1 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) 2786 self.wipe() 2787 self.init(testdir) 2788 meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat')) 2789 self.assertListEqual(meson_exe_dat1, meson_exe_dat2) 2790 2791 def test_noop_changes_cause_no_rebuilds(self): 2792 ''' 2793 Test that no-op changes to the build files such as mtime do not cause 2794 a rebuild of anything. 2795 ''' 2796 testdir = os.path.join(self.common_test_dir, '6 linkshared') 2797 self.init(testdir) 2798 self.build() 2799 # Immediately rebuilding should not do anything 2800 self.assertBuildIsNoop() 2801 # Changing mtime of meson.build should not rebuild anything 2802 self.utime(os.path.join(testdir, 'meson.build')) 2803 self.assertReconfiguredBuildIsNoop() 2804 # Changing mtime of libefile.c should rebuild the library, but not relink the executable 2805 self.utime(os.path.join(testdir, 'libfile.c')) 2806 self.assertBuildRelinkedOnlyTarget('mylib') 2807 2808 def test_source_changes_cause_rebuild(self): 2809 ''' 2810 Test that changes to sources and headers cause rebuilds, but not 2811 changes to unused files (as determined by the dependency file) in the 2812 input files list. 2813 ''' 2814 testdir = os.path.join(self.common_test_dir, '20 header in file list') 2815 self.init(testdir) 2816 self.build() 2817 # Immediately rebuilding should not do anything 2818 self.assertBuildIsNoop() 2819 # Changing mtime of header.h should rebuild everything 2820 self.utime(os.path.join(testdir, 'header.h')) 2821 self.assertBuildRelinkedOnlyTarget('prog') 2822 2823 def test_custom_target_changes_cause_rebuild(self): 2824 ''' 2825 Test that in a custom target, changes to the input files, the 2826 ExternalProgram, and any File objects on the command-line cause 2827 a rebuild. 2828 ''' 2829 testdir = os.path.join(self.common_test_dir, '60 custom header generator') 2830 self.init(testdir) 2831 self.build() 2832 # Immediately rebuilding should not do anything 2833 self.assertBuildIsNoop() 2834 # Changing mtime of these should rebuild everything 2835 for f in ('input.def', 'makeheader.py', 'somefile.txt'): 2836 self.utime(os.path.join(testdir, f)) 2837 self.assertBuildRelinkedOnlyTarget('prog') 2838 2839 def test_source_generator_program_cause_rebuild(self): 2840 ''' 2841 Test that changes to generator programs in the source tree cause 2842 a rebuild. 2843 ''' 2844 testdir = os.path.join(self.common_test_dir, '94 gen extra') 2845 self.init(testdir) 2846 self.build() 2847 # Immediately rebuilding should not do anything 2848 self.assertBuildIsNoop() 2849 # Changing mtime of generator should rebuild the executable 2850 self.utime(os.path.join(testdir, 'srcgen.py')) 2851 self.assertRebuiltTarget('basic') 2852 2853 def test_static_library_lto(self): 2854 ''' 2855 Test that static libraries can be built with LTO and linked to 2856 executables. On Linux, this requires the use of gcc-ar. 2857 https://github.com/mesonbuild/meson/issues/1646 2858 ''' 2859 testdir = os.path.join(self.common_test_dir, '5 linkstatic') 2860 2861 env = get_fake_env(testdir, self.builddir, self.prefix) 2862 if env.detect_c_compiler(MachineChoice.HOST).get_id() == 'clang' and is_windows(): 2863 raise unittest.SkipTest('LTO not (yet) supported by windows clang') 2864 2865 self.init(testdir, extra_args='-Db_lto=true') 2866 self.build() 2867 self.run_tests() 2868 2869 def test_dist_git(self): 2870 if not shutil.which('git'): 2871 raise unittest.SkipTest('Git not found') 2872 if self.backend is not Backend.ninja: 2873 raise unittest.SkipTest('Dist is only supported with Ninja') 2874 2875 try: 2876 self.dist_impl(_git_init) 2877 except PermissionError: 2878 # When run under Windows CI, something (virus scanner?) 2879 # holds on to the git files so cleaning up the dir 2880 # fails sometimes. 2881 pass 2882 2883 def has_working_hg(self): 2884 if not shutil.which('hg'): 2885 return False 2886 try: 2887 # This check should not be necessary, but 2888 # CI under macOS passes the above test even 2889 # though Mercurial is not installed. 2890 if subprocess.call(['hg', '--version'], 2891 stdout=subprocess.DEVNULL, 2892 stderr=subprocess.DEVNULL) != 0: 2893 return False 2894 return True 2895 except FileNotFoundError: 2896 return False 2897 2898 2899 def test_dist_hg(self): 2900 if not self.has_working_hg(): 2901 raise unittest.SkipTest('Mercurial not found or broken.') 2902 if self.backend is not Backend.ninja: 2903 raise unittest.SkipTest('Dist is only supported with Ninja') 2904 2905 def hg_init(project_dir): 2906 subprocess.check_call(['hg', 'init'], cwd=project_dir) 2907 with open(os.path.join(project_dir, '.hg', 'hgrc'), 'w') as f: 2908 print('[ui]', file=f) 2909 print('username=Author Person <teh_coderz@example.com>', file=f) 2910 subprocess.check_call(['hg', 'add', 'meson.build', 'distexe.c'], cwd=project_dir) 2911 subprocess.check_call(['hg', 'commit', '-m', 'I am a project'], cwd=project_dir) 2912 2913 try: 2914 self.dist_impl(hg_init, include_subprojects=False) 2915 except PermissionError: 2916 # When run under Windows CI, something (virus scanner?) 2917 # holds on to the hg files so cleaning up the dir 2918 # fails sometimes. 2919 pass 2920 2921 def test_dist_git_script(self): 2922 if not shutil.which('git'): 2923 raise unittest.SkipTest('Git not found') 2924 if self.backend is not Backend.ninja: 2925 raise unittest.SkipTest('Dist is only supported with Ninja') 2926 2927 try: 2928 with tempfile.TemporaryDirectory() as tmpdir: 2929 project_dir = os.path.join(tmpdir, 'a') 2930 shutil.copytree(os.path.join(self.unit_test_dir, '35 dist script'), 2931 project_dir) 2932 _git_init(project_dir) 2933 self.init(project_dir) 2934 self.build('dist') 2935 except PermissionError: 2936 # When run under Windows CI, something (virus scanner?) 2937 # holds on to the git files so cleaning up the dir 2938 # fails sometimes. 2939 pass 2940 2941 def create_dummy_subproject(self, project_dir, name): 2942 path = os.path.join(project_dir, 'subprojects', name) 2943 os.makedirs(path) 2944 with open(os.path.join(path, 'meson.build'), 'w') as ofile: 2945 ofile.write("project('{}')".format(name)) 2946 return path 2947 2948 def dist_impl(self, vcs_init, include_subprojects=True): 2949 # Create this on the fly because having rogue .git directories inside 2950 # the source tree leads to all kinds of trouble. 2951 with tempfile.TemporaryDirectory() as project_dir: 2952 with open(os.path.join(project_dir, 'meson.build'), 'w') as ofile: 2953 ofile.write('''project('disttest', 'c', version : '1.4.3') 2954e = executable('distexe', 'distexe.c') 2955test('dist test', e) 2956subproject('vcssub', required : false) 2957subproject('tarballsub', required : false) 2958''') 2959 with open(os.path.join(project_dir, 'distexe.c'), 'w') as ofile: 2960 ofile.write('''#include<stdio.h> 2961 2962int main(int argc, char **argv) { 2963 printf("I am a distribution test.\\n"); 2964 return 0; 2965} 2966''') 2967 xz_distfile = os.path.join(self.distdir, 'disttest-1.4.3.tar.xz') 2968 xz_checksumfile = xz_distfile + '.sha256sum' 2969 zip_distfile = os.path.join(self.distdir, 'disttest-1.4.3.zip') 2970 zip_checksumfile = zip_distfile + '.sha256sum' 2971 vcs_init(project_dir) 2972 if include_subprojects: 2973 vcs_init(self.create_dummy_subproject(project_dir, 'vcssub')) 2974 self.create_dummy_subproject(project_dir, 'tarballsub') 2975 self.create_dummy_subproject(project_dir, 'unusedsub') 2976 self.init(project_dir) 2977 self.build('dist') 2978 self.assertPathExists(xz_distfile) 2979 self.assertPathExists(xz_checksumfile) 2980 self.assertPathDoesNotExist(zip_distfile) 2981 self.assertPathDoesNotExist(zip_checksumfile) 2982 self._run(self.meson_command + ['dist', '--formats', 'zip'], 2983 workdir=self.builddir) 2984 self.assertPathExists(zip_distfile) 2985 self.assertPathExists(zip_checksumfile) 2986 2987 if include_subprojects: 2988 z = zipfile.ZipFile(zip_distfile) 2989 self.assertEqual(sorted(['disttest-1.4.3/', 2990 'disttest-1.4.3/meson.build', 2991 'disttest-1.4.3/distexe.c']), 2992 sorted(z.namelist())) 2993 2994 self._run(self.meson_command + ['dist', '--formats', 'zip', '--include-subprojects'], 2995 workdir=self.builddir) 2996 z = zipfile.ZipFile(zip_distfile) 2997 self.assertEqual(sorted(['disttest-1.4.3/', 2998 'disttest-1.4.3/subprojects/', 2999 'disttest-1.4.3/meson.build', 3000 'disttest-1.4.3/distexe.c', 3001 'disttest-1.4.3/subprojects/tarballsub/', 3002 'disttest-1.4.3/subprojects/vcssub/', 3003 'disttest-1.4.3/subprojects/tarballsub/meson.build', 3004 'disttest-1.4.3/subprojects/vcssub/meson.build']), 3005 sorted(z.namelist())) 3006 3007 def test_rpath_uses_ORIGIN(self): 3008 ''' 3009 Test that built targets use $ORIGIN in rpath, which ensures that they 3010 are relocatable and ensures that builds are reproducible since the 3011 build directory won't get embedded into the built binaries. 3012 ''' 3013 if is_windows() or is_cygwin(): 3014 raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH') 3015 testdir = os.path.join(self.common_test_dir, '42 library chain') 3016 self.init(testdir) 3017 self.build() 3018 for each in ('prog', 'subdir/liblib1.so', ): 3019 rpath = get_rpath(os.path.join(self.builddir, each)) 3020 self.assertTrue(rpath, 'Rpath could not be determined for {}.'.format(each)) 3021 if is_dragonflybsd(): 3022 # DragonflyBSD will prepend /usr/lib/gccVERSION to the rpath, 3023 # so ignore that. 3024 self.assertTrue(rpath.startswith('/usr/lib/gcc')) 3025 rpaths = rpath.split(':')[1:] 3026 else: 3027 rpaths = rpath.split(':') 3028 for path in rpaths: 3029 self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path)) 3030 # These two don't link to anything else, so they do not need an rpath entry. 3031 for each in ('subdir/subdir2/liblib2.so', 'subdir/subdir3/liblib3.so'): 3032 rpath = get_rpath(os.path.join(self.builddir, each)) 3033 if is_dragonflybsd(): 3034 # The rpath should be equal to /usr/lib/gccVERSION 3035 self.assertTrue(rpath.startswith('/usr/lib/gcc')) 3036 self.assertEqual(len(rpath.split(':')), 1) 3037 else: 3038 self.assertTrue(rpath is None) 3039 3040 def test_dash_d_dedup(self): 3041 testdir = os.path.join(self.unit_test_dir, '9 d dedup') 3042 self.init(testdir) 3043 cmd = self.get_compdb()[0]['command'] 3044 self.assertTrue('-D FOO -D BAR' in cmd or 3045 '"-D" "FOO" "-D" "BAR"' in cmd or 3046 '/D FOO /D BAR' in cmd or 3047 '"/D" "FOO" "/D" "BAR"' in cmd) 3048 3049 def test_all_forbidden_targets_tested(self): 3050 ''' 3051 Test that all forbidden targets are tested in the '154 reserved targets' 3052 test. Needs to be a unit test because it accesses Meson internals. 3053 ''' 3054 testdir = os.path.join(self.common_test_dir, '154 reserved targets') 3055 targets = mesonbuild.coredata.forbidden_target_names 3056 # We don't actually define a target with this name 3057 targets.pop('build.ninja') 3058 # Remove this to avoid multiple entries with the same name 3059 # but different case. 3060 targets.pop('PHONY') 3061 for i in targets: 3062 self.assertPathExists(os.path.join(testdir, i)) 3063 3064 def detect_prebuild_env(self): 3065 env = get_fake_env() 3066 cc = env.detect_c_compiler(MachineChoice.HOST) 3067 stlinker = env.detect_static_linker(cc) 3068 if mesonbuild.mesonlib.is_windows(): 3069 object_suffix = 'obj' 3070 shared_suffix = 'dll' 3071 elif mesonbuild.mesonlib.is_cygwin(): 3072 object_suffix = 'o' 3073 shared_suffix = 'dll' 3074 elif mesonbuild.mesonlib.is_osx(): 3075 object_suffix = 'o' 3076 shared_suffix = 'dylib' 3077 else: 3078 object_suffix = 'o' 3079 shared_suffix = 'so' 3080 return (cc, stlinker, object_suffix, shared_suffix) 3081 3082 def pbcompile(self, compiler, source, objectfile, extra_args=None): 3083 cmd = compiler.get_exelist() 3084 extra_args = extra_args or [] 3085 if compiler.get_argument_syntax() == 'msvc': 3086 cmd += ['/nologo', '/Fo' + objectfile, '/c', source] + extra_args 3087 else: 3088 cmd += ['-c', source, '-o', objectfile] + extra_args 3089 subprocess.check_call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 3090 3091 def test_prebuilt_object(self): 3092 (compiler, _, object_suffix, _) = self.detect_prebuild_env() 3093 tdir = os.path.join(self.unit_test_dir, '15 prebuilt object') 3094 source = os.path.join(tdir, 'source.c') 3095 objectfile = os.path.join(tdir, 'prebuilt.' + object_suffix) 3096 self.pbcompile(compiler, source, objectfile) 3097 try: 3098 self.init(tdir) 3099 self.build() 3100 self.run_tests() 3101 finally: 3102 os.unlink(objectfile) 3103 3104 def build_static_lib(self, compiler, linker, source, objectfile, outfile, extra_args=None): 3105 if extra_args is None: 3106 extra_args = [] 3107 if compiler.get_argument_syntax() == 'msvc': 3108 link_cmd = ['lib', '/NOLOGO', '/OUT:' + outfile, objectfile] 3109 else: 3110 link_cmd = ['ar', 'csr', outfile, objectfile] 3111 link_cmd = linker.get_exelist() 3112 link_cmd += linker.get_always_args() 3113 link_cmd += linker.get_std_link_args() 3114 link_cmd += linker.get_output_args(outfile) 3115 link_cmd += [objectfile] 3116 self.pbcompile(compiler, source, objectfile, extra_args=extra_args) 3117 try: 3118 subprocess.check_call(link_cmd) 3119 finally: 3120 os.unlink(objectfile) 3121 3122 def test_prebuilt_static_lib(self): 3123 (cc, stlinker, object_suffix, _) = self.detect_prebuild_env() 3124 tdir = os.path.join(self.unit_test_dir, '16 prebuilt static') 3125 source = os.path.join(tdir, 'libdir/best.c') 3126 objectfile = os.path.join(tdir, 'libdir/best.' + object_suffix) 3127 stlibfile = os.path.join(tdir, 'libdir/libbest.a') 3128 self.build_static_lib(cc, stlinker, source, objectfile, stlibfile) 3129 # Run the test 3130 try: 3131 self.init(tdir) 3132 self.build() 3133 self.run_tests() 3134 finally: 3135 os.unlink(stlibfile) 3136 3137 def build_shared_lib(self, compiler, source, objectfile, outfile, impfile, extra_args=None): 3138 if extra_args is None: 3139 extra_args = [] 3140 if compiler.get_argument_syntax() == 'msvc': 3141 link_cmd = compiler.get_linker_exelist() + [ 3142 '/NOLOGO', '/DLL', '/DEBUG', '/IMPLIB:' + impfile, 3143 '/OUT:' + outfile, objectfile] 3144 else: 3145 if not (compiler.info.is_windows() or compiler.info.is_cygwin() or compiler.info.is_darwin()): 3146 extra_args += ['-fPIC'] 3147 link_cmd = compiler.get_exelist() + ['-shared', '-o', outfile, objectfile] 3148 if not mesonbuild.mesonlib.is_osx(): 3149 link_cmd += ['-Wl,-soname=' + os.path.basename(outfile)] 3150 self.pbcompile(compiler, source, objectfile, extra_args=extra_args) 3151 try: 3152 subprocess.check_call(link_cmd) 3153 finally: 3154 os.unlink(objectfile) 3155 3156 def test_prebuilt_shared_lib(self): 3157 (cc, _, object_suffix, shared_suffix) = self.detect_prebuild_env() 3158 tdir = os.path.join(self.unit_test_dir, '17 prebuilt shared') 3159 source = os.path.join(tdir, 'alexandria.c') 3160 objectfile = os.path.join(tdir, 'alexandria.' + object_suffix) 3161 impfile = os.path.join(tdir, 'alexandria.lib') 3162 if cc.get_argument_syntax() == 'msvc': 3163 shlibfile = os.path.join(tdir, 'alexandria.' + shared_suffix) 3164 elif is_cygwin(): 3165 shlibfile = os.path.join(tdir, 'cygalexandria.' + shared_suffix) 3166 else: 3167 shlibfile = os.path.join(tdir, 'libalexandria.' + shared_suffix) 3168 self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) 3169 # Run the test 3170 try: 3171 self.init(tdir) 3172 self.build() 3173 self.run_tests() 3174 finally: 3175 os.unlink(shlibfile) 3176 if mesonbuild.mesonlib.is_windows(): 3177 # Clean up all the garbage MSVC writes in the 3178 # source tree. 3179 for fname in glob(os.path.join(tdir, 'alexandria.*')): 3180 if os.path.splitext(fname)[1] not in ['.c', '.h']: 3181 os.unlink(fname) 3182 3183 @skipIfNoPkgconfig 3184 def test_pkgconfig_static(self): 3185 ''' 3186 Test that the we prefer static libraries when `static: true` is 3187 passed to dependency() with pkg-config. Can't be an ordinary test 3188 because we need to build libs and try to find them from meson.build 3189 3190 Also test that it's not a hard error to have unsatisfiable library deps 3191 since system libraries -lm will never be found statically. 3192 https://github.com/mesonbuild/meson/issues/2785 3193 ''' 3194 (cc, stlinker, objext, shext) = self.detect_prebuild_env() 3195 testdir = os.path.join(self.unit_test_dir, '18 pkgconfig static') 3196 source = os.path.join(testdir, 'foo.c') 3197 objectfile = os.path.join(testdir, 'foo.' + objext) 3198 stlibfile = os.path.join(testdir, 'libfoo.a') 3199 impfile = os.path.join(testdir, 'foo.lib') 3200 if cc.get_argument_syntax() == 'msvc': 3201 shlibfile = os.path.join(testdir, 'foo.' + shext) 3202 elif is_cygwin(): 3203 shlibfile = os.path.join(testdir, 'cygfoo.' + shext) 3204 else: 3205 shlibfile = os.path.join(testdir, 'libfoo.' + shext) 3206 # Build libs 3207 self.build_static_lib(cc, stlinker, source, objectfile, stlibfile, extra_args=['-DFOO_STATIC']) 3208 self.build_shared_lib(cc, source, objectfile, shlibfile, impfile) 3209 # Run test 3210 try: 3211 self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': self.builddir}) 3212 self.build() 3213 self.run_tests() 3214 finally: 3215 os.unlink(stlibfile) 3216 os.unlink(shlibfile) 3217 if mesonbuild.mesonlib.is_windows(): 3218 # Clean up all the garbage MSVC writes in the 3219 # source tree. 3220 for fname in glob(os.path.join(testdir, 'foo.*')): 3221 if os.path.splitext(fname)[1] not in ['.c', '.h', '.in']: 3222 os.unlink(fname) 3223 3224 @skipIfNoPkgconfig 3225 def test_pkgconfig_gen_escaping(self): 3226 testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen') 3227 prefix = '/usr/with spaces' 3228 libdir = 'lib' 3229 self.init(testdir, extra_args=['--prefix=' + prefix, 3230 '--libdir=' + libdir]) 3231 # Find foo dependency 3232 os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir 3233 env = get_fake_env(testdir, self.builddir, self.prefix) 3234 kwargs = {'required': True, 'silent': True} 3235 foo_dep = PkgConfigDependency('libfoo', env, kwargs) 3236 # Ensure link_args are properly quoted 3237 libdir = PurePath(prefix) / PurePath(libdir) 3238 link_args = ['-L' + libdir.as_posix(), '-lfoo'] 3239 self.assertEqual(foo_dep.get_link_args(), link_args) 3240 # Ensure include args are properly quoted 3241 incdir = PurePath(prefix) / PurePath('include') 3242 cargs = ['-I' + incdir.as_posix(), '-DLIBFOO'] 3243 # pkg-config and pkgconf does not respect the same order 3244 self.assertEqual(sorted(foo_dep.get_compile_args()), sorted(cargs)) 3245 3246 def test_array_option_change(self): 3247 def get_opt(): 3248 opts = self.introspect('--buildoptions') 3249 for x in opts: 3250 if x.get('name') == 'list': 3251 return x 3252 raise Exception(opts) 3253 3254 expected = { 3255 'name': 'list', 3256 'description': 'list', 3257 'section': 'user', 3258 'type': 'array', 3259 'value': ['foo', 'bar'], 3260 'machine': 'any', 3261 } 3262 tdir = os.path.join(self.unit_test_dir, '19 array option') 3263 self.init(tdir) 3264 original = get_opt() 3265 self.assertDictEqual(original, expected) 3266 3267 expected['value'] = ['oink', 'boink'] 3268 self.setconf('-Dlist=oink,boink') 3269 changed = get_opt() 3270 self.assertEqual(changed, expected) 3271 3272 def test_array_option_bad_change(self): 3273 def get_opt(): 3274 opts = self.introspect('--buildoptions') 3275 for x in opts: 3276 if x.get('name') == 'list': 3277 return x 3278 raise Exception(opts) 3279 3280 expected = { 3281 'name': 'list', 3282 'description': 'list', 3283 'section': 'user', 3284 'type': 'array', 3285 'value': ['foo', 'bar'], 3286 'machine': 'any', 3287 } 3288 tdir = os.path.join(self.unit_test_dir, '19 array option') 3289 self.init(tdir) 3290 original = get_opt() 3291 self.assertDictEqual(original, expected) 3292 with self.assertRaises(subprocess.CalledProcessError): 3293 self.setconf('-Dlist=bad') 3294 changed = get_opt() 3295 self.assertDictEqual(changed, expected) 3296 3297 def test_array_option_empty_equivalents(self): 3298 """Array options treat -Dopt=[] and -Dopt= as equivalent.""" 3299 def get_opt(): 3300 opts = self.introspect('--buildoptions') 3301 for x in opts: 3302 if x.get('name') == 'list': 3303 return x 3304 raise Exception(opts) 3305 3306 expected = { 3307 'name': 'list', 3308 'description': 'list', 3309 'section': 'user', 3310 'type': 'array', 3311 'value': [], 3312 'machine': 'any', 3313 } 3314 tdir = os.path.join(self.unit_test_dir, '19 array option') 3315 self.init(tdir, extra_args='-Dlist=') 3316 original = get_opt() 3317 self.assertDictEqual(original, expected) 3318 3319 def opt_has(self, name, value): 3320 res = self.introspect('--buildoptions') 3321 found = False 3322 for i in res: 3323 if i['name'] == name: 3324 self.assertEqual(i['value'], value) 3325 found = True 3326 break 3327 self.assertTrue(found, "Array option not found in introspect data.") 3328 3329 def test_free_stringarray_setting(self): 3330 testdir = os.path.join(self.common_test_dir, '43 options') 3331 self.init(testdir) 3332 self.opt_has('free_array_opt', []) 3333 self.setconf('-Dfree_array_opt=foo,bar', will_build=False) 3334 self.opt_has('free_array_opt', ['foo', 'bar']) 3335 self.setconf("-Dfree_array_opt=['a,b', 'c,d']", will_build=False) 3336 self.opt_has('free_array_opt', ['a,b', 'c,d']) 3337 3338 def test_subproject_promotion(self): 3339 testdir = os.path.join(self.unit_test_dir, '12 promote') 3340 workdir = os.path.join(self.builddir, 'work') 3341 shutil.copytree(testdir, workdir) 3342 spdir = os.path.join(workdir, 'subprojects') 3343 s3dir = os.path.join(spdir, 's3') 3344 scommondir = os.path.join(spdir, 'scommon') 3345 self.assertFalse(os.path.isdir(s3dir)) 3346 subprocess.check_call(self.wrap_command + ['promote', 's3'], cwd=workdir) 3347 self.assertTrue(os.path.isdir(s3dir)) 3348 self.assertFalse(os.path.isdir(scommondir)) 3349 self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'scommon'], 3350 cwd=workdir, 3351 stdout=subprocess.DEVNULL), 0) 3352 self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'invalid/path/to/scommon'], 3353 cwd=workdir, 3354 stderr=subprocess.DEVNULL), 0) 3355 self.assertFalse(os.path.isdir(scommondir)) 3356 subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/scommon'], cwd=workdir) 3357 self.assertTrue(os.path.isdir(scommondir)) 3358 promoted_wrap = os.path.join(spdir, 'athing.wrap') 3359 self.assertFalse(os.path.isfile(promoted_wrap)) 3360 subprocess.check_call(self.wrap_command + ['promote', 'athing'], cwd=workdir) 3361 self.assertTrue(os.path.isfile(promoted_wrap)) 3362 self.init(workdir) 3363 self.build() 3364 3365 def test_subproject_promotion_wrap(self): 3366 testdir = os.path.join(self.unit_test_dir, '44 promote wrap') 3367 workdir = os.path.join(self.builddir, 'work') 3368 shutil.copytree(testdir, workdir) 3369 spdir = os.path.join(workdir, 'subprojects') 3370 3371 ambiguous_wrap = os.path.join(spdir, 'ambiguous.wrap') 3372 self.assertNotEqual(subprocess.call(self.wrap_command + ['promote', 'ambiguous'], 3373 cwd=workdir, 3374 stdout=subprocess.DEVNULL), 0) 3375 self.assertFalse(os.path.isfile(ambiguous_wrap)) 3376 subprocess.check_call(self.wrap_command + ['promote', 'subprojects/s2/subprojects/ambiguous.wrap'], cwd=workdir) 3377 self.assertTrue(os.path.isfile(ambiguous_wrap)) 3378 3379 def test_warning_location(self): 3380 tdir = os.path.join(self.unit_test_dir, '22 warning location') 3381 out = self.init(tdir) 3382 for expected in [ 3383 r'meson.build:4: WARNING: Keyword argument "link_with" defined multiple times.', 3384 r'sub' + os.path.sep + r'meson.build:3: WARNING: Keyword argument "link_with" defined multiple times.', 3385 r'meson.build:6: WARNING: a warning of some sort', 3386 r'sub' + os.path.sep + r'meson.build:4: WARNING: subdir warning', 3387 r'meson.build:7: WARNING: Module unstable-simd has no backwards or forwards compatibility and might not exist in future releases.', 3388 r"meson.build:11: WARNING: The variable(s) 'MISSING' in the input file 'conf.in' are not present in the given configuration data.", 3389 r'meson.build:1: WARNING: Passed invalid keyword argument "invalid".', 3390 ]: 3391 self.assertRegex(out, re.escape(expected)) 3392 3393 for wd in [ 3394 self.src_root, 3395 self.builddir, 3396 os.getcwd(), 3397 ]: 3398 self.new_builddir() 3399 out = self.init(tdir, workdir=wd) 3400 expected = os.path.join(relpath(tdir, self.src_root), 'meson.build') 3401 relwd = relpath(self.src_root, wd) 3402 if relwd != '.': 3403 expected = os.path.join(relwd, expected) 3404 expected = '\n' + expected + ':' 3405 self.assertIn(expected, out) 3406 3407 def test_error_location_path(self): 3408 '''Test locations in meson errors contain correct paths''' 3409 # this list contains errors from all the different steps in the 3410 # lexer/parser/interpreter we have tests for. 3411 for (t, f) in [ 3412 ('10 out of bounds', 'meson.build'), 3413 ('18 wrong plusassign', 'meson.build'), 3414 ('61 bad option argument', 'meson_options.txt'), 3415 ('102 subdir parse error', os.path.join('subdir', 'meson.build')), 3416 ('103 invalid option file', 'meson_options.txt'), 3417 ]: 3418 tdir = os.path.join(self.src_root, 'test cases', 'failing', t) 3419 3420 for wd in [ 3421 self.src_root, 3422 self.builddir, 3423 os.getcwd(), 3424 ]: 3425 try: 3426 self.init(tdir, workdir=wd) 3427 except subprocess.CalledProcessError as e: 3428 expected = os.path.join('test cases', 'failing', t, f) 3429 relwd = relpath(self.src_root, wd) 3430 if relwd != '.': 3431 expected = os.path.join(relwd, expected) 3432 expected = '\n' + expected + ':' 3433 self.assertIn(expected, e.output) 3434 else: 3435 self.fail('configure unexpectedly succeeded') 3436 3437 def test_permitted_method_kwargs(self): 3438 tdir = os.path.join(self.unit_test_dir, '25 non-permitted kwargs') 3439 out = self.init(tdir) 3440 for expected in [ 3441 r'WARNING: Passed invalid keyword argument "prefixxx".', 3442 r'WARNING: Passed invalid keyword argument "argsxx".', 3443 r'WARNING: Passed invalid keyword argument "invalidxx".', 3444 ]: 3445 self.assertRegex(out, re.escape(expected)) 3446 3447 def test_templates(self): 3448 ninja = detect_ninja() 3449 if ninja is None: 3450 raise unittest.SkipTest('This test currently requires ninja. Fix this once "meson build" works.') 3451 langs = ['c'] 3452 env = get_fake_env() 3453 try: 3454 env.detect_cpp_compiler(MachineChoice.HOST) 3455 langs.append('cpp') 3456 except EnvironmentException: 3457 pass 3458 try: 3459 env.detect_cs_compiler(MachineChoice.HOST) 3460 langs.append('cs') 3461 except EnvironmentException: 3462 pass 3463 try: 3464 env.detect_d_compiler(MachineChoice.HOST) 3465 langs.append('d') 3466 except EnvironmentException: 3467 pass 3468 try: 3469 env.detect_java_compiler(MachineChoice.HOST) 3470 langs.append('java') 3471 except EnvironmentException: 3472 pass 3473 try: 3474 env.detect_cuda_compiler(MachineChoice.HOST) 3475 langs.append('cuda') 3476 except EnvironmentException: 3477 pass 3478 try: 3479 env.detect_fortran_compiler(MachineChoice.HOST) 3480 langs.append('fortran') 3481 except EnvironmentException: 3482 pass 3483 try: 3484 env.detect_objc_compiler(MachineChoice.HOST) 3485 langs.append('objc') 3486 except EnvironmentException: 3487 pass 3488 try: 3489 env.detect_objcpp_compiler(MachineChoice.HOST) 3490 langs.append('objcpp') 3491 except EnvironmentException: 3492 pass 3493 # FIXME: omitting rust as Windows AppVeyor CI finds Rust but doesn't link correctly 3494 if not is_windows(): 3495 try: 3496 env.detect_rust_compiler(MachineChoice.HOST) 3497 langs.append('rust') 3498 except EnvironmentException: 3499 pass 3500 3501 for lang in langs: 3502 for target_type in ('executable', 'library'): 3503 # test empty directory 3504 with tempfile.TemporaryDirectory() as tmpdir: 3505 self._run(self.meson_command + ['init', '--language', lang, '--type', target_type], 3506 workdir=tmpdir) 3507 self._run(self.setup_command + ['--backend=ninja', 'builddir'], 3508 workdir=tmpdir) 3509 self._run(ninja, 3510 workdir=os.path.join(tmpdir, 'builddir')) 3511 # test directory with existing code file 3512 if lang in ('c', 'cpp', 'd'): 3513 with tempfile.TemporaryDirectory() as tmpdir: 3514 with open(os.path.join(tmpdir, 'foo.' + lang), 'w') as f: 3515 f.write('int main(void) {}') 3516 self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) 3517 elif lang in ('java'): 3518 with tempfile.TemporaryDirectory() as tmpdir: 3519 with open(os.path.join(tmpdir, 'Foo.' + lang), 'w') as f: 3520 f.write('public class Foo { public static void main() {} }') 3521 self._run(self.meson_command + ['init', '-b'], workdir=tmpdir) 3522 3523 def test_compiler_run_command(self): 3524 ''' 3525 The test checks that the compiler object can be passed to 3526 run_command(). 3527 ''' 3528 testdir = os.path.join(self.unit_test_dir, '24 compiler run_command') 3529 self.init(testdir) 3530 3531 def test_identical_target_name_in_subproject_flat_layout(self): 3532 ''' 3533 Test that identical targets in different subprojects do not collide 3534 if layout is flat. 3535 ''' 3536 testdir = os.path.join(self.common_test_dir, '177 identical target name in subproject flat layout') 3537 self.init(testdir, extra_args=['--layout=flat']) 3538 self.build() 3539 3540 def test_identical_target_name_in_subdir_flat_layout(self): 3541 ''' 3542 Test that identical targets in different subdirs do not collide 3543 if layout is flat. 3544 ''' 3545 testdir = os.path.join(self.common_test_dir, '186 same target name flat layout') 3546 self.init(testdir, extra_args=['--layout=flat']) 3547 self.build() 3548 3549 def test_flock(self): 3550 exception_raised = False 3551 with tempfile.TemporaryDirectory() as tdir: 3552 os.mkdir(os.path.join(tdir, 'meson-private')) 3553 with BuildDirLock(tdir): 3554 try: 3555 with BuildDirLock(tdir): 3556 pass 3557 except MesonException: 3558 exception_raised = True 3559 self.assertTrue(exception_raised, 'Double locking did not raise exception.') 3560 3561 @unittest.skipIf(is_osx(), 'Test not applicable to OSX') 3562 def test_check_module_linking(self): 3563 """ 3564 Test that link_with: a shared module issues a warning 3565 https://github.com/mesonbuild/meson/issues/2865 3566 (That an error is raised on OSX is exercised by test failing/78) 3567 """ 3568 tdir = os.path.join(self.unit_test_dir, '30 shared_mod linking') 3569 out = self.init(tdir) 3570 msg = ('''WARNING: target links against shared modules. This is not 3571recommended as it is not supported on some platforms''') 3572 self.assertIn(msg, out) 3573 3574 def test_ndebug_if_release_disabled(self): 3575 testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release') 3576 self.init(testdir, extra_args=['--buildtype=release', '-Db_ndebug=if-release']) 3577 self.build() 3578 exe = os.path.join(self.builddir, 'main') 3579 self.assertEqual(b'NDEBUG=1', subprocess.check_output(exe).strip()) 3580 3581 def test_ndebug_if_release_enabled(self): 3582 testdir = os.path.join(self.unit_test_dir, '28 ndebug if-release') 3583 self.init(testdir, extra_args=['--buildtype=debugoptimized', '-Db_ndebug=if-release']) 3584 self.build() 3585 exe = os.path.join(self.builddir, 'main') 3586 self.assertEqual(b'NDEBUG=0', subprocess.check_output(exe).strip()) 3587 3588 def test_guessed_linker_dependencies(self): 3589 ''' 3590 Test that meson adds dependencies for libraries based on the final 3591 linker command line. 3592 ''' 3593 testdirbase = os.path.join(self.unit_test_dir, '29 guessed linker dependencies') 3594 testdirlib = os.path.join(testdirbase, 'lib') 3595 3596 extra_args = None 3597 libdir_flags = ['-L'] 3598 env = get_fake_env(testdirlib, self.builddir, self.prefix) 3599 if env.detect_c_compiler(MachineChoice.HOST).get_id() in {'msvc', 'clang-cl', 'intel-cl'}: 3600 # msvc-like compiler, also test it with msvc-specific flags 3601 libdir_flags += ['/LIBPATH:', '-LIBPATH:'] 3602 else: 3603 # static libraries are not linkable with -l with msvc because meson installs them 3604 # as .a files which unix_args_to_native will not know as it expects libraries to use 3605 # .lib as extension. For a DLL the import library is installed as .lib. Thus for msvc 3606 # this tests needs to use shared libraries to test the path resolving logic in the 3607 # dependency generation code path. 3608 extra_args = ['--default-library', 'static'] 3609 3610 initial_builddir = self.builddir 3611 initial_installdir = self.installdir 3612 3613 for libdir_flag in libdir_flags: 3614 # build library 3615 self.new_builddir() 3616 self.init(testdirlib, extra_args=extra_args) 3617 self.build() 3618 self.install() 3619 libbuilddir = self.builddir 3620 installdir = self.installdir 3621 libdir = os.path.join(self.installdir, self.prefix.lstrip('/').lstrip('\\'), 'lib') 3622 3623 # build user of library 3624 self.new_builddir() 3625 # replace is needed because meson mangles platform paths passed via LDFLAGS 3626 self.init(os.path.join(testdirbase, 'exe'), 3627 override_envvars={"LDFLAGS": '{}{}'.format(libdir_flag, libdir.replace('\\', '/'))}) 3628 self.build() 3629 self.assertBuildIsNoop() 3630 3631 # rebuild library 3632 exebuilddir = self.builddir 3633 self.installdir = installdir 3634 self.builddir = libbuilddir 3635 # Microsoft's compiler is quite smart about touching import libs on changes, 3636 # so ensure that there is actually a change in symbols. 3637 self.setconf('-Dmore_exports=true') 3638 self.build() 3639 self.install() 3640 # no ensure_backend_detects_changes needed because self.setconf did that already 3641 3642 # assert user of library will be rebuild 3643 self.builddir = exebuilddir 3644 self.assertRebuiltTarget('app') 3645 3646 # restore dirs for the next test case 3647 self.installdir = initial_builddir 3648 self.builddir = initial_installdir 3649 3650 def test_conflicting_d_dash_option(self): 3651 testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') 3652 with self.assertRaises(subprocess.CalledProcessError) as e: 3653 self.init(testdir, extra_args=['-Dbindir=foo', '--bindir=bar']) 3654 # Just to ensure that we caught the correct error 3655 self.assertIn('passed as both', e.stderr) 3656 3657 def _test_same_option_twice(self, arg, args): 3658 testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') 3659 self.init(testdir, extra_args=args) 3660 opts = self.introspect('--buildoptions') 3661 for item in opts: 3662 if item['name'] == arg: 3663 self.assertEqual(item['value'], 'bar') 3664 return 3665 raise Exception('Missing {} value?'.format(arg)) 3666 3667 def test_same_dash_option_twice(self): 3668 self._test_same_option_twice('bindir', ['--bindir=foo', '--bindir=bar']) 3669 3670 def test_same_d_option_twice(self): 3671 self._test_same_option_twice('bindir', ['-Dbindir=foo', '-Dbindir=bar']) 3672 3673 def test_same_project_d_option_twice(self): 3674 self._test_same_option_twice('one', ['-Done=foo', '-Done=bar']) 3675 3676 def _test_same_option_twice_configure(self, arg, args): 3677 testdir = os.path.join(self.unit_test_dir, '37 mixed command line args') 3678 self.init(testdir) 3679 self.setconf(args) 3680 opts = self.introspect('--buildoptions') 3681 for item in opts: 3682 if item['name'] == arg: 3683 self.assertEqual(item['value'], 'bar') 3684 return 3685 raise Exception('Missing {} value?'.format(arg)) 3686 3687 def test_same_dash_option_twice_configure(self): 3688 self._test_same_option_twice_configure( 3689 'bindir', ['--bindir=foo', '--bindir=bar']) 3690 3691 def test_same_d_option_twice_configure(self): 3692 self._test_same_option_twice_configure( 3693 'bindir', ['-Dbindir=foo', '-Dbindir=bar']) 3694 3695 def test_same_project_d_option_twice_configure(self): 3696 self._test_same_option_twice_configure( 3697 'one', ['-Done=foo', '-Done=bar']) 3698 3699 def test_command_line(self): 3700 testdir = os.path.join(self.unit_test_dir, '34 command line') 3701 3702 # Verify default values when passing no args that affect the 3703 # configuration, and as a bonus, test that --profile-self works. 3704 self.init(testdir, extra_args=['--profile-self']) 3705 obj = mesonbuild.coredata.load(self.builddir) 3706 self.assertEqual(obj.builtins['default_library'].value, 'static') 3707 self.assertEqual(obj.builtins['warning_level'].value, '1') 3708 self.assertEqual(obj.user_options['set_sub_opt'].value, True) 3709 self.assertEqual(obj.user_options['subp:subp_opt'].value, 'default3') 3710 self.wipe() 3711 3712 # warning_level is special, it's --warnlevel instead of --warning-level 3713 # for historical reasons 3714 self.init(testdir, extra_args=['--warnlevel=2']) 3715 obj = mesonbuild.coredata.load(self.builddir) 3716 self.assertEqual(obj.builtins['warning_level'].value, '2') 3717 self.setconf('--warnlevel=3') 3718 obj = mesonbuild.coredata.load(self.builddir) 3719 self.assertEqual(obj.builtins['warning_level'].value, '3') 3720 self.wipe() 3721 3722 # But when using -D syntax, it should be 'warning_level' 3723 self.init(testdir, extra_args=['-Dwarning_level=2']) 3724 obj = mesonbuild.coredata.load(self.builddir) 3725 self.assertEqual(obj.builtins['warning_level'].value, '2') 3726 self.setconf('-Dwarning_level=3') 3727 obj = mesonbuild.coredata.load(self.builddir) 3728 self.assertEqual(obj.builtins['warning_level'].value, '3') 3729 self.wipe() 3730 3731 # Mixing --option and -Doption is forbidden 3732 with self.assertRaises(subprocess.CalledProcessError) as cm: 3733 self.init(testdir, extra_args=['--warnlevel=1', '-Dwarning_level=3']) 3734 self.assertNotEqual(0, cm.exception.returncode) 3735 self.assertIn('as both', cm.exception.output) 3736 self.init(testdir) 3737 with self.assertRaises(subprocess.CalledProcessError) as cm: 3738 self.setconf(['--warnlevel=1', '-Dwarning_level=3']) 3739 self.assertNotEqual(0, cm.exception.returncode) 3740 self.assertIn('as both', cm.exception.output) 3741 self.wipe() 3742 3743 # --default-library should override default value from project() 3744 self.init(testdir, extra_args=['--default-library=both']) 3745 obj = mesonbuild.coredata.load(self.builddir) 3746 self.assertEqual(obj.builtins['default_library'].value, 'both') 3747 self.setconf('--default-library=shared') 3748 obj = mesonbuild.coredata.load(self.builddir) 3749 self.assertEqual(obj.builtins['default_library'].value, 'shared') 3750 if self.backend is Backend.ninja: 3751 # reconfigure target works only with ninja backend 3752 self.build('reconfigure') 3753 obj = mesonbuild.coredata.load(self.builddir) 3754 self.assertEqual(obj.builtins['default_library'].value, 'shared') 3755 self.wipe() 3756 3757 # Should warn on unknown options 3758 out = self.init(testdir, extra_args=['-Dbad=1', '-Dfoo=2', '-Dwrong_link_args=foo']) 3759 self.assertIn('Unknown options: "bad, foo, wrong_link_args"', out) 3760 self.wipe() 3761 3762 # Should fail on malformed option 3763 with self.assertRaises(subprocess.CalledProcessError) as cm: 3764 self.init(testdir, extra_args=['-Dfoo']) 3765 self.assertNotEqual(0, cm.exception.returncode) 3766 self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output) 3767 self.init(testdir) 3768 with self.assertRaises(subprocess.CalledProcessError) as cm: 3769 self.setconf('-Dfoo') 3770 self.assertNotEqual(0, cm.exception.returncode) 3771 self.assertIn('Option \'foo\' must have a value separated by equals sign.', cm.exception.output) 3772 self.wipe() 3773 3774 # It is not an error to set wrong option for unknown subprojects or 3775 # language because we don't have control on which one will be selected. 3776 self.init(testdir, extra_args=['-Dc_wrong=1', '-Dwrong:bad=1', '-Db_wrong=1']) 3777 self.wipe() 3778 3779 # Test we can set subproject option 3780 self.init(testdir, extra_args=['-Dsubp:subp_opt=foo']) 3781 obj = mesonbuild.coredata.load(self.builddir) 3782 self.assertEqual(obj.user_options['subp:subp_opt'].value, 'foo') 3783 self.wipe() 3784 3785 # c_args value should be parsed with split_args 3786 self.init(testdir, extra_args=['-Dc_args=-Dfoo -Dbar "-Dthird=one two"']) 3787 obj = mesonbuild.coredata.load(self.builddir) 3788 self.assertEqual(obj.compiler_options.host['c']['args'].value, ['-Dfoo', '-Dbar', '-Dthird=one two']) 3789 3790 self.setconf('-Dc_args="foo bar" one two') 3791 obj = mesonbuild.coredata.load(self.builddir) 3792 self.assertEqual(obj.compiler_options.host['c']['args'].value, ['foo bar', 'one', 'two']) 3793 self.wipe() 3794 3795 self.init(testdir, extra_args=['-Dset_percent_opt=myoption%']) 3796 obj = mesonbuild.coredata.load(self.builddir) 3797 self.assertEqual(obj.user_options['set_percent_opt'].value, 'myoption%') 3798 self.wipe() 3799 3800 # Setting a 2nd time the same option should override the first value 3801 try: 3802 self.init(testdir, extra_args=['--bindir=foo', '--bindir=bar', 3803 '-Dbuildtype=plain', '-Dbuildtype=release', 3804 '-Db_sanitize=address', '-Db_sanitize=thread', 3805 '-Dc_args=-Dfoo', '-Dc_args=-Dbar']) 3806 obj = mesonbuild.coredata.load(self.builddir) 3807 self.assertEqual(obj.builtins['bindir'].value, 'bar') 3808 self.assertEqual(obj.builtins['buildtype'].value, 'release') 3809 self.assertEqual(obj.base_options['b_sanitize'].value, 'thread') 3810 self.assertEqual(obj.compiler_options.host['c']['args'].value, ['-Dbar']) 3811 self.setconf(['--bindir=bar', '--bindir=foo', 3812 '-Dbuildtype=release', '-Dbuildtype=plain', 3813 '-Db_sanitize=thread', '-Db_sanitize=address', 3814 '-Dc_args=-Dbar', '-Dc_args=-Dfoo']) 3815 obj = mesonbuild.coredata.load(self.builddir) 3816 self.assertEqual(obj.builtins['bindir'].value, 'foo') 3817 self.assertEqual(obj.builtins['buildtype'].value, 'plain') 3818 self.assertEqual(obj.base_options['b_sanitize'].value, 'address') 3819 self.assertEqual(obj.compiler_options.host['c']['args'].value, ['-Dfoo']) 3820 self.wipe() 3821 except KeyError: 3822 # Ignore KeyError, it happens on CI for compilers that does not 3823 # support b_sanitize. We have to test with a base option because 3824 # they used to fail this test with Meson 0.46 an earlier versions. 3825 pass 3826 3827 def test_warning_level_0(self): 3828 testdir = os.path.join(self.common_test_dir, '214 warning level 0') 3829 3830 # Verify default values when passing no args 3831 self.init(testdir) 3832 obj = mesonbuild.coredata.load(self.builddir) 3833 self.assertEqual(obj.builtins['warning_level'].value, '0') 3834 self.wipe() 3835 3836 # verify we can override w/ --warnlevel 3837 self.init(testdir, extra_args=['--warnlevel=1']) 3838 obj = mesonbuild.coredata.load(self.builddir) 3839 self.assertEqual(obj.builtins['warning_level'].value, '1') 3840 self.setconf('--warnlevel=0') 3841 obj = mesonbuild.coredata.load(self.builddir) 3842 self.assertEqual(obj.builtins['warning_level'].value, '0') 3843 self.wipe() 3844 3845 # verify we can override w/ -Dwarning_level 3846 self.init(testdir, extra_args=['-Dwarning_level=1']) 3847 obj = mesonbuild.coredata.load(self.builddir) 3848 self.assertEqual(obj.builtins['warning_level'].value, '1') 3849 self.setconf('-Dwarning_level=0') 3850 obj = mesonbuild.coredata.load(self.builddir) 3851 self.assertEqual(obj.builtins['warning_level'].value, '0') 3852 self.wipe() 3853 3854 def test_feature_check_usage_subprojects(self): 3855 testdir = os.path.join(self.unit_test_dir, '41 featurenew subprojects') 3856 out = self.init(testdir) 3857 # Parent project warns correctly 3858 self.assertRegex(out, "WARNING: Project targeting '>=0.45'.*'0.47.0': dict") 3859 # Subprojects warn correctly 3860 self.assertRegex(out, r"\|WARNING: Project targeting '>=0.40'.*'0.44.0': disabler") 3861 self.assertRegex(out, r"\|WARNING: Project targeting '!=0.40'.*'0.44.0': disabler") 3862 # Subproject has a new-enough meson_version, no warning 3863 self.assertNotRegex(out, "WARNING: Project targeting.*Python") 3864 # Ensure a summary is printed in the subproject and the outer project 3865 self.assertRegex(out, r"\|WARNING: Project specifies a minimum meson_version '>=0.40'") 3866 self.assertRegex(out, r"\| \* 0.44.0: {'disabler'}") 3867 self.assertRegex(out, "WARNING: Project specifies a minimum meson_version '>=0.45'") 3868 self.assertRegex(out, " * 0.47.0: {'dict'}") 3869 3870 def test_configure_file_warnings(self): 3871 testdir = os.path.join(self.common_test_dir, "14 configure file") 3872 out = self.init(testdir) 3873 self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*") 3874 self.assertRegex(out, "WARNING:.*'FOO_BAR'.*nosubst-nocopy2.txt.in.*not present.*") 3875 self.assertRegex(out, "WARNING:.*'empty'.*config.h.in.*not present.*") 3876 self.assertRegex(out, "WARNING:.*empty configuration_data.*test.py.in") 3877 # Warnings for configuration files that are overwritten. 3878 self.assertRegex(out, "WARNING:.*\"double_output.txt\".*overwrites") 3879 self.assertRegex(out, "WARNING:.*\"subdir.double_output2.txt\".*overwrites") 3880 self.assertNotRegex(out, "WARNING:.*no_write_conflict.txt.*overwrites") 3881 self.assertNotRegex(out, "WARNING:.*@BASENAME@.*overwrites") 3882 self.assertRegex(out, "WARNING:.*\"sameafterbasename\".*overwrites") 3883 # No warnings about empty configuration data objects passed to files with substitutions 3884 self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy1.txt.in") 3885 self.assertNotRegex(out, "WARNING:.*empty configuration_data.*nosubst-nocopy2.txt.in") 3886 with open(os.path.join(self.builddir, 'nosubst-nocopy1.txt'), 'rb') as f: 3887 self.assertEqual(f.read().strip(), b'/* #undef FOO_BAR */') 3888 with open(os.path.join(self.builddir, 'nosubst-nocopy2.txt'), 'rb') as f: 3889 self.assertEqual(f.read().strip(), b'') 3890 self.assertRegex(out, r"DEPRECATION:.*\['array'\] is invalid.*dict") 3891 3892 def test_dirs(self): 3893 with tempfile.TemporaryDirectory() as containing: 3894 with tempfile.TemporaryDirectory(dir=containing) as srcdir: 3895 mfile = os.path.join(srcdir, 'meson.build') 3896 of = open(mfile, 'w') 3897 of.write("project('foobar', 'c')\n") 3898 of.close() 3899 pc = subprocess.run(self.setup_command, 3900 cwd=srcdir, 3901 stdout=subprocess.PIPE, 3902 stderr=subprocess.DEVNULL) 3903 self.assertIn(b'Must specify at least one directory name', pc.stdout) 3904 with tempfile.TemporaryDirectory(dir=srcdir) as builddir: 3905 subprocess.run(self.setup_command, 3906 check=True, 3907 cwd=builddir, 3908 stdout=subprocess.DEVNULL, 3909 stderr=subprocess.DEVNULL) 3910 3911 def get_opts_as_dict(self): 3912 result = {} 3913 for i in self.introspect('--buildoptions'): 3914 result[i['name']] = i['value'] 3915 return result 3916 3917 def test_buildtype_setting(self): 3918 testdir = os.path.join(self.common_test_dir, '1 trivial') 3919 self.init(testdir) 3920 opts = self.get_opts_as_dict() 3921 self.assertEqual(opts['buildtype'], 'debug') 3922 self.assertEqual(opts['debug'], True) 3923 self.setconf('-Ddebug=false') 3924 opts = self.get_opts_as_dict() 3925 self.assertEqual(opts['debug'], False) 3926 self.assertEqual(opts['buildtype'], 'plain') 3927 self.assertEqual(opts['optimization'], '0') 3928 3929 # Setting optimizations to 3 should cause buildtype 3930 # to go to release mode. 3931 self.setconf('-Doptimization=3') 3932 opts = self.get_opts_as_dict() 3933 self.assertEqual(opts['buildtype'], 'release') 3934 self.assertEqual(opts['debug'], False) 3935 self.assertEqual(opts['optimization'], '3') 3936 3937 # Going to debug build type should reset debugging 3938 # and optimization 3939 self.setconf('-Dbuildtype=debug') 3940 opts = self.get_opts_as_dict() 3941 self.assertEqual(opts['buildtype'], 'debug') 3942 self.assertEqual(opts['debug'], True) 3943 self.assertEqual(opts['optimization'], '0') 3944 3945 # Command-line parsing of buildtype settings should be the same as 3946 # setting with `meson configure`. 3947 # 3948 # Setting buildtype should set optimization/debug 3949 self.new_builddir() 3950 self.init(testdir, extra_args=['-Dbuildtype=debugoptimized']) 3951 opts = self.get_opts_as_dict() 3952 self.assertEqual(opts['debug'], True) 3953 self.assertEqual(opts['optimization'], '2') 3954 self.assertEqual(opts['buildtype'], 'debugoptimized') 3955 # Setting optimization/debug should set buildtype 3956 self.new_builddir() 3957 self.init(testdir, extra_args=['-Doptimization=2', '-Ddebug=true']) 3958 opts = self.get_opts_as_dict() 3959 self.assertEqual(opts['debug'], True) 3960 self.assertEqual(opts['optimization'], '2') 3961 self.assertEqual(opts['buildtype'], 'debugoptimized') 3962 # Setting both buildtype and debug on the command-line should work, and 3963 # should warn not to do that. Also test that --debug is parsed as -Ddebug=true 3964 self.new_builddir() 3965 out = self.init(testdir, extra_args=['-Dbuildtype=debugoptimized', '--debug']) 3966 self.assertRegex(out, 'Recommend using either.*buildtype.*debug.*redundant') 3967 opts = self.get_opts_as_dict() 3968 self.assertEqual(opts['debug'], True) 3969 self.assertEqual(opts['optimization'], '2') 3970 self.assertEqual(opts['buildtype'], 'debugoptimized') 3971 3972 @skipIfNoPkgconfig 3973 @unittest.skipIf(is_windows(), 'Help needed with fixing this test on windows') 3974 def test_native_dep_pkgconfig(self): 3975 testdir = os.path.join(self.unit_test_dir, 3976 '46 native dep pkgconfig var') 3977 with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile: 3978 crossfile.write(textwrap.dedent( 3979 '''[binaries] 3980 pkgconfig = '{0}' 3981 3982 [properties] 3983 3984 [host_machine] 3985 system = 'linux' 3986 cpu_family = 'arm' 3987 cpu = 'armv7' 3988 endian = 'little' 3989 '''.format(os.path.join(testdir, 'cross_pkgconfig.py')))) 3990 crossfile.flush() 3991 self.meson_cross_file = crossfile.name 3992 3993 env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir, 3994 'native_pkgconfig')} 3995 self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env) 3996 self.wipe() 3997 self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) 3998 3999 @skipIfNoPkgconfig 4000 @unittest.skipIf(is_windows(), 'Help needed with fixing this test on windows') 4001 def test_pkg_config_libdir(self): 4002 testdir = os.path.join(self.unit_test_dir, 4003 '46 native dep pkgconfig var') 4004 with tempfile.NamedTemporaryFile(mode='w', delete=False) as crossfile: 4005 crossfile.write(textwrap.dedent( 4006 '''[binaries] 4007 pkgconfig = 'pkg-config' 4008 4009 [properties] 4010 pkg_config_libdir = ['{0}'] 4011 4012 [host_machine] 4013 system = 'linux' 4014 cpu_family = 'arm' 4015 cpu = 'armv7' 4016 endian = 'little' 4017 '''.format(os.path.join(testdir, 'cross_pkgconfig')))) 4018 crossfile.flush() 4019 self.meson_cross_file = crossfile.name 4020 4021 env = {'PKG_CONFIG_LIBDIR': os.path.join(testdir, 4022 'native_pkgconfig')} 4023 self.init(testdir, extra_args=['-Dstart_native=false'], override_envvars=env) 4024 self.wipe() 4025 self.init(testdir, extra_args=['-Dstart_native=true'], override_envvars=env) 4026 4027 def __reconfigure(self, change_minor=False): 4028 # Set an older version to force a reconfigure from scratch 4029 filename = os.path.join(self.privatedir, 'coredata.dat') 4030 with open(filename, 'rb') as f: 4031 obj = pickle.load(f) 4032 if change_minor: 4033 v = mesonbuild.coredata.version.split('.') 4034 obj.version = '.'.join(v[0:2] + [str(int(v[2]) + 1)]) 4035 else: 4036 obj.version = '0.47.0' 4037 with open(filename, 'wb') as f: 4038 pickle.dump(obj, f) 4039 4040 def test_reconfigure(self): 4041 testdir = os.path.join(self.unit_test_dir, '48 reconfigure') 4042 self.init(testdir, extra_args=['-Dopt1=val1']) 4043 self.setconf('-Dopt2=val2') 4044 4045 self.__reconfigure() 4046 4047 out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3']) 4048 self.assertRegex(out, 'Regenerating configuration from scratch') 4049 self.assertRegex(out, 'opt1 val1') 4050 self.assertRegex(out, 'opt2 val2') 4051 self.assertRegex(out, 'opt3 val3') 4052 self.assertRegex(out, 'opt4 default4') 4053 self.build() 4054 self.run_tests() 4055 4056 # Create a file in builddir and verify wipe command removes it 4057 filename = os.path.join(self.builddir, 'something') 4058 open(filename, 'w').close() 4059 self.assertTrue(os.path.exists(filename)) 4060 out = self.init(testdir, extra_args=['--wipe', '-Dopt4=val4']) 4061 self.assertFalse(os.path.exists(filename)) 4062 self.assertRegex(out, 'opt1 val1') 4063 self.assertRegex(out, 'opt2 val2') 4064 self.assertRegex(out, 'opt3 val3') 4065 self.assertRegex(out, 'opt4 val4') 4066 self.build() 4067 self.run_tests() 4068 4069 def test_wipe_from_builddir(self): 4070 testdir = os.path.join(self.common_test_dir, '161 custom target subdir depend files') 4071 self.init(testdir) 4072 self.__reconfigure() 4073 4074 with Path(self.builddir): 4075 self.init(testdir, extra_args=['--wipe']) 4076 4077 def test_minor_version_does_not_reconfigure_wipe(self): 4078 testdir = os.path.join(self.unit_test_dir, '48 reconfigure') 4079 self.init(testdir, extra_args=['-Dopt1=val1']) 4080 self.setconf('-Dopt2=val2') 4081 4082 self.__reconfigure(change_minor=True) 4083 4084 out = self.init(testdir, extra_args=['--reconfigure', '-Dopt3=val3']) 4085 self.assertNotRegex(out, 'Regenerating configuration from scratch') 4086 self.assertRegex(out, 'opt1 val1') 4087 self.assertRegex(out, 'opt2 val2') 4088 self.assertRegex(out, 'opt3 val3') 4089 self.assertRegex(out, 'opt4 default4') 4090 self.build() 4091 self.run_tests() 4092 4093 def test_target_construct_id_from_path(self): 4094 # This id is stable but not guessable. 4095 # The test is supposed to prevent unintentional 4096 # changes of target ID generation. 4097 target_id = Target.construct_id_from_path('some/obscure/subdir', 4098 'target-id', '@suffix') 4099 self.assertEqual('5e002d3@@target-id@suffix', target_id) 4100 target_id = Target.construct_id_from_path('subproject/foo/subdir/bar', 4101 'target2-id', '@other') 4102 self.assertEqual('81d46d1@@target2-id@other', target_id) 4103 4104 def test_introspect_projectinfo_without_configured_build(self): 4105 testfile = os.path.join(self.common_test_dir, '35 run program', 'meson.build') 4106 res = self.introspect_directory(testfile, '--projectinfo') 4107 self.assertEqual(set(res['buildsystem_files']), set(['meson.build'])) 4108 self.assertEqual(res['version'], 'undefined') 4109 self.assertEqual(res['descriptive_name'], 'run command') 4110 self.assertEqual(res['subprojects'], []) 4111 4112 testfile = os.path.join(self.common_test_dir, '43 options', 'meson.build') 4113 res = self.introspect_directory(testfile, '--projectinfo') 4114 self.assertEqual(set(res['buildsystem_files']), set(['meson_options.txt', 'meson.build'])) 4115 self.assertEqual(res['version'], 'undefined') 4116 self.assertEqual(res['descriptive_name'], 'options') 4117 self.assertEqual(res['subprojects'], []) 4118 4119 testfile = os.path.join(self.common_test_dir, '46 subproject options', 'meson.build') 4120 res = self.introspect_directory(testfile, '--projectinfo') 4121 self.assertEqual(set(res['buildsystem_files']), set(['meson_options.txt', 'meson.build'])) 4122 self.assertEqual(res['version'], 'undefined') 4123 self.assertEqual(res['descriptive_name'], 'suboptions') 4124 self.assertEqual(len(res['subprojects']), 1) 4125 subproject_files = set(f.replace('\\', '/') for f in res['subprojects'][0]['buildsystem_files']) 4126 self.assertEqual(subproject_files, set(['subprojects/subproject/meson_options.txt', 'subprojects/subproject/meson.build'])) 4127 self.assertEqual(res['subprojects'][0]['name'], 'subproject') 4128 self.assertEqual(res['subprojects'][0]['version'], 'undefined') 4129 self.assertEqual(res['subprojects'][0]['descriptive_name'], 'subproject') 4130 4131 def test_introspect_projectinfo_subprojects(self): 4132 testdir = os.path.join(self.common_test_dir, '102 subproject subdir') 4133 self.init(testdir) 4134 res = self.introspect('--projectinfo') 4135 expected = { 4136 'descriptive_name': 'proj', 4137 'version': 'undefined', 4138 'subproject_dir': 'subprojects', 4139 'subprojects': [ 4140 { 4141 'descriptive_name': 'sub', 4142 'name': 'sub', 4143 'version': '1.0' 4144 }, 4145 { 4146 'descriptive_name': 'sub_implicit', 4147 'name': 'sub_implicit', 4148 'version': '1.0', 4149 }, 4150 { 4151 'descriptive_name': 'sub-novar', 4152 'name': 'sub_novar', 4153 'version': '1.0', 4154 }, 4155 ] 4156 } 4157 res['subprojects'] = sorted(res['subprojects'], key=lambda i: i['name']) 4158 self.assertDictEqual(expected, res) 4159 4160 def test_introspection_target_subproject(self): 4161 testdir = os.path.join(self.common_test_dir, '45 subproject') 4162 self.init(testdir) 4163 res = self.introspect('--targets') 4164 4165 expected = { 4166 'sublib': 'sublib', 4167 'simpletest': 'sublib', 4168 'user': None 4169 } 4170 4171 for entry in res: 4172 name = entry['name'] 4173 self.assertEqual(entry['subproject'], expected[name]) 4174 4175 def test_introspect_projectinfo_subproject_dir(self): 4176 testdir = os.path.join(self.common_test_dir, '78 custom subproject dir') 4177 self.init(testdir) 4178 res = self.introspect('--projectinfo') 4179 4180 self.assertEqual(res['subproject_dir'], 'custom_subproject_dir') 4181 4182 def test_introspect_projectinfo_subproject_dir_from_source(self): 4183 testfile = os.path.join(self.common_test_dir, '78 custom subproject dir', 'meson.build') 4184 res = self.introspect_directory(testfile, '--projectinfo') 4185 4186 self.assertEqual(res['subproject_dir'], 'custom_subproject_dir') 4187 4188 @skipIfNoExecutable('clang-format') 4189 def test_clang_format(self): 4190 if self.backend is not Backend.ninja: 4191 raise unittest.SkipTest('Clang-format is for now only supported on Ninja, not {}'.format(self.backend.name)) 4192 testdir = os.path.join(self.unit_test_dir, '54 clang-format') 4193 testfile = os.path.join(testdir, 'prog.c') 4194 badfile = os.path.join(testdir, 'prog_orig_c') 4195 goodfile = os.path.join(testdir, 'prog_expected_c') 4196 testheader = os.path.join(testdir, 'header.h') 4197 badheader = os.path.join(testdir, 'header_orig_h') 4198 goodheader = os.path.join(testdir, 'header_expected_h') 4199 try: 4200 shutil.copyfile(badfile, testfile) 4201 shutil.copyfile(badheader, testheader) 4202 self.init(testdir) 4203 self.assertNotEqual(Path(testfile).read_text(), 4204 Path(goodfile).read_text()) 4205 self.assertNotEqual(Path(testheader).read_text(), 4206 Path(goodheader).read_text()) 4207 self.run_target('clang-format') 4208 self.assertEqual(Path(testheader).read_text(), 4209 Path(goodheader).read_text()) 4210 finally: 4211 if os.path.exists(testfile): 4212 os.unlink(testfile) 4213 if os.path.exists(testheader): 4214 os.unlink(testheader) 4215 4216 @skipIfNoExecutable('clang-tidy') 4217 def test_clang_tidy(self): 4218 if self.backend is not Backend.ninja: 4219 raise unittest.SkipTest('Clang-tidy is for now only supported on Ninja, not {}'.format(self.backend.name)) 4220 if shutil.which('c++') is None: 4221 raise unittest.SkipTest('Clang-tidy breaks when ccache is used and "c++" not in path.') 4222 if is_osx(): 4223 raise unittest.SkipTest('Apple ships a broken clang-tidy that chokes on -pipe.') 4224 testdir = os.path.join(self.unit_test_dir, '70 clang-tidy') 4225 self.init(testdir, override_envvars={'CXX': 'c++'}) 4226 out = self.run_target('clang-tidy') 4227 self.assertIn('cttest.cpp:4:20', out) 4228 4229 def test_identity_cross(self): 4230 testdir = os.path.join(self.unit_test_dir, '71 cross') 4231 # Do a build to generate a cross file where the host is this target 4232 self.init(testdir, extra_args=['-Dgenerate=true']) 4233 self.meson_cross_file = os.path.join(self.builddir, "crossfile") 4234 self.assertTrue(os.path.exists(self.meson_cross_file)) 4235 # Now verify that this is detected as cross 4236 self.new_builddir() 4237 self.init(testdir) 4238 4239 def test_introspect_buildoptions_without_configured_build(self): 4240 testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions') 4241 testfile = os.path.join(testdir, 'meson.build') 4242 res_nb = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args) 4243 self.init(testdir, default_args=False) 4244 res_wb = self.introspect('--buildoptions') 4245 self.maxDiff = None 4246 self.assertListEqual(res_nb, res_wb) 4247 4248 def test_meson_configure_from_source_does_not_crash(self): 4249 testdir = os.path.join(self.unit_test_dir, '59 introspect buildoptions') 4250 self._run(self.mconf_command + [testdir]) 4251 4252 def test_introspect_json_dump(self): 4253 testdir = os.path.join(self.unit_test_dir, '57 introspection') 4254 self.init(testdir) 4255 infodir = os.path.join(self.builddir, 'meson-info') 4256 self.assertPathExists(infodir) 4257 4258 def assertKeyTypes(key_type_list, obj): 4259 for i in key_type_list: 4260 self.assertIn(i[0], obj) 4261 self.assertIsInstance(obj[i[0]], i[1]) 4262 4263 root_keylist = [ 4264 ('benchmarks', list), 4265 ('buildoptions', list), 4266 ('buildsystem_files', list), 4267 ('dependencies', list), 4268 ('installed', dict), 4269 ('projectinfo', dict), 4270 ('targets', list), 4271 ('tests', list), 4272 ] 4273 4274 test_keylist = [ 4275 ('cmd', list), 4276 ('env', dict), 4277 ('name', str), 4278 ('timeout', int), 4279 ('suite', list), 4280 ('is_parallel', bool), 4281 ('protocol', str), 4282 ] 4283 4284 buildoptions_keylist = [ 4285 ('name', str), 4286 ('section', str), 4287 ('type', str), 4288 ('description', str), 4289 ('machine', str), 4290 ] 4291 4292 buildoptions_typelist = [ 4293 ('combo', str, [('choices', list)]), 4294 ('string', str, []), 4295 ('boolean', bool, []), 4296 ('integer', int, []), 4297 ('array', list, []), 4298 ] 4299 4300 buildoptions_sections = ['core', 'backend', 'base', 'compiler', 'directory', 'user', 'test'] 4301 buildoptions_machines = ['any', 'build', 'host'] 4302 4303 dependencies_typelist = [ 4304 ('name', str), 4305 ('version', str), 4306 ('compile_args', list), 4307 ('link_args', list), 4308 ] 4309 4310 targets_typelist = [ 4311 ('name', str), 4312 ('id', str), 4313 ('type', str), 4314 ('defined_in', str), 4315 ('filename', list), 4316 ('build_by_default', bool), 4317 ('target_sources', list), 4318 ('installed', bool), 4319 ] 4320 4321 targets_sources_typelist = [ 4322 ('language', str), 4323 ('compiler', list), 4324 ('parameters', list), 4325 ('sources', list), 4326 ('generated_sources', list), 4327 ] 4328 4329 # First load all files 4330 res = {} 4331 for i in root_keylist: 4332 curr = os.path.join(infodir, 'intro-{}.json'.format(i[0])) 4333 self.assertPathExists(curr) 4334 with open(curr, 'r') as fp: 4335 res[i[0]] = json.load(fp) 4336 4337 assertKeyTypes(root_keylist, res) 4338 4339 # Check Tests and benchmarks 4340 tests_to_find = ['test case 1', 'test case 2', 'benchmark 1'] 4341 for i in res['benchmarks'] + res['tests']: 4342 assertKeyTypes(test_keylist, i) 4343 if i['name'] in tests_to_find: 4344 tests_to_find.remove(i['name']) 4345 self.assertListEqual(tests_to_find, []) 4346 4347 # Check buildoptions 4348 buildopts_to_find = {'cpp_std': 'c++11'} 4349 for i in res['buildoptions']: 4350 assertKeyTypes(buildoptions_keylist, i) 4351 valid_type = False 4352 for j in buildoptions_typelist: 4353 if i['type'] == j[0]: 4354 self.assertIsInstance(i['value'], j[1]) 4355 assertKeyTypes(j[2], i) 4356 valid_type = True 4357 break 4358 4359 self.assertIn(i['section'], buildoptions_sections) 4360 self.assertIn(i['machine'], buildoptions_machines) 4361 self.assertTrue(valid_type) 4362 if i['name'] in buildopts_to_find: 4363 self.assertEqual(i['value'], buildopts_to_find[i['name']]) 4364 buildopts_to_find.pop(i['name'], None) 4365 self.assertDictEqual(buildopts_to_find, {}) 4366 4367 # Check buildsystem_files 4368 bs_files = ['meson.build', 'meson_options.txt', 'sharedlib/meson.build', 'staticlib/meson.build'] 4369 bs_files = [os.path.join(testdir, x) for x in bs_files] 4370 self.assertPathListEqual(list(sorted(res['buildsystem_files'])), list(sorted(bs_files))) 4371 4372 # Check dependencies 4373 dependencies_to_find = ['threads'] 4374 for i in res['dependencies']: 4375 assertKeyTypes(dependencies_typelist, i) 4376 if i['name'] in dependencies_to_find: 4377 dependencies_to_find.remove(i['name']) 4378 self.assertListEqual(dependencies_to_find, []) 4379 4380 # Check projectinfo 4381 self.assertDictEqual(res['projectinfo'], {'version': '1.2.3', 'descriptive_name': 'introspection', 'subproject_dir': 'subprojects', 'subprojects': []}) 4382 4383 # Check targets 4384 targets_to_find = { 4385 'sharedTestLib': ('shared library', True, False, 'sharedlib/meson.build'), 4386 'staticTestLib': ('static library', True, False, 'staticlib/meson.build'), 4387 'test1': ('executable', True, True, 'meson.build'), 4388 'test2': ('executable', True, False, 'meson.build'), 4389 'test3': ('executable', True, False, 'meson.build'), 4390 } 4391 for i in res['targets']: 4392 assertKeyTypes(targets_typelist, i) 4393 if i['name'] in targets_to_find: 4394 tgt = targets_to_find[i['name']] 4395 self.assertEqual(i['type'], tgt[0]) 4396 self.assertEqual(i['build_by_default'], tgt[1]) 4397 self.assertEqual(i['installed'], tgt[2]) 4398 self.assertPathEqual(i['defined_in'], os.path.join(testdir, tgt[3])) 4399 targets_to_find.pop(i['name'], None) 4400 for j in i['target_sources']: 4401 assertKeyTypes(targets_sources_typelist, j) 4402 self.assertDictEqual(targets_to_find, {}) 4403 4404 def test_introspect_file_dump_equals_all(self): 4405 testdir = os.path.join(self.unit_test_dir, '57 introspection') 4406 self.init(testdir) 4407 res_all = self.introspect('--all') 4408 res_file = {} 4409 4410 root_keylist = [ 4411 'benchmarks', 4412 'buildoptions', 4413 'buildsystem_files', 4414 'dependencies', 4415 'installed', 4416 'projectinfo', 4417 'targets', 4418 'tests', 4419 ] 4420 4421 infodir = os.path.join(self.builddir, 'meson-info') 4422 self.assertPathExists(infodir) 4423 for i in root_keylist: 4424 curr = os.path.join(infodir, 'intro-{}.json'.format(i)) 4425 self.assertPathExists(curr) 4426 with open(curr, 'r') as fp: 4427 res_file[i] = json.load(fp) 4428 4429 self.assertEqual(res_all, res_file) 4430 4431 def test_introspect_meson_info(self): 4432 testdir = os.path.join(self.unit_test_dir, '57 introspection') 4433 introfile = os.path.join(self.builddir, 'meson-info', 'meson-info.json') 4434 self.init(testdir) 4435 self.assertPathExists(introfile) 4436 with open(introfile, 'r') as fp: 4437 res1 = json.load(fp) 4438 4439 for i in ['meson_version', 'directories', 'introspection', 'build_files_updated', 'error']: 4440 self.assertIn(i, res1) 4441 4442 self.assertEqual(res1['error'], False) 4443 self.assertEqual(res1['build_files_updated'], True) 4444 4445 def test_introspect_config_update(self): 4446 testdir = os.path.join(self.unit_test_dir, '57 introspection') 4447 introfile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json') 4448 self.init(testdir) 4449 self.assertPathExists(introfile) 4450 with open(introfile, 'r') as fp: 4451 res1 = json.load(fp) 4452 4453 self.setconf('-Dcpp_std=c++14') 4454 self.setconf('-Dbuildtype=release') 4455 4456 for idx, i in enumerate(res1): 4457 if i['name'] == 'cpp_std': 4458 res1[idx]['value'] = 'c++14' 4459 if i['name'] == 'build.cpp_std': 4460 res1[idx]['value'] = 'c++14' 4461 if i['name'] == 'buildtype': 4462 res1[idx]['value'] = 'release' 4463 if i['name'] == 'optimization': 4464 res1[idx]['value'] = '3' 4465 if i['name'] == 'debug': 4466 res1[idx]['value'] = False 4467 4468 with open(introfile, 'r') as fp: 4469 res2 = json.load(fp) 4470 4471 self.assertListEqual(res1, res2) 4472 4473 def test_introspect_targets_from_source(self): 4474 testdir = os.path.join(self.unit_test_dir, '57 introspection') 4475 testfile = os.path.join(testdir, 'meson.build') 4476 introfile = os.path.join(self.builddir, 'meson-info', 'intro-targets.json') 4477 self.init(testdir) 4478 self.assertPathExists(introfile) 4479 with open(introfile, 'r') as fp: 4480 res_wb = json.load(fp) 4481 4482 res_nb = self.introspect_directory(testfile, ['--targets'] + self.meson_args) 4483 4484 # Account for differences in output 4485 for i in res_wb: 4486 i['filename'] = [os.path.relpath(x, self.builddir) for x in i['filename']] 4487 if 'install_filename' in i: 4488 del i['install_filename'] 4489 4490 sources = [] 4491 for j in i['target_sources']: 4492 sources += j['sources'] 4493 i['target_sources'] = [{ 4494 'language': 'unknown', 4495 'compiler': [], 4496 'parameters': [], 4497 'sources': sources, 4498 'generated_sources': [] 4499 }] 4500 4501 self.maxDiff = None 4502 self.assertListEqual(res_nb, res_wb) 4503 4504 def test_introspect_ast_source(self): 4505 testdir = os.path.join(self.unit_test_dir, '57 introspection') 4506 testfile = os.path.join(testdir, 'meson.build') 4507 res_nb = self.introspect_directory(testfile, ['--ast'] + self.meson_args) 4508 4509 node_counter = {} 4510 4511 def accept_node(json_node): 4512 self.assertIsInstance(json_node, dict) 4513 for i in ['lineno', 'colno', 'end_lineno', 'end_colno']: 4514 self.assertIn(i, json_node) 4515 self.assertIsInstance(json_node[i], int) 4516 self.assertIn('node', json_node) 4517 n = json_node['node'] 4518 self.assertIsInstance(n, str) 4519 self.assertIn(n, nodes) 4520 if n not in node_counter: 4521 node_counter[n] = 0 4522 node_counter[n] = node_counter[n] + 1 4523 for nodeDesc in nodes[n]: 4524 key = nodeDesc[0] 4525 func = nodeDesc[1] 4526 self.assertIn(key, json_node) 4527 if func is None: 4528 tp = nodeDesc[2] 4529 self.assertIsInstance(json_node[key], tp) 4530 continue 4531 func(json_node[key]) 4532 4533 def accept_node_list(node_list): 4534 self.assertIsInstance(node_list, list) 4535 for i in node_list: 4536 accept_node(i) 4537 4538 def accept_kwargs(kwargs): 4539 self.assertIsInstance(kwargs, list) 4540 for i in kwargs: 4541 self.assertIn('key', i) 4542 self.assertIn('val', i) 4543 accept_node(i['key']) 4544 accept_node(i['val']) 4545 4546 nodes = { 4547 'BooleanNode': [('value', None, bool)], 4548 'IdNode': [('value', None, str)], 4549 'NumberNode': [('value', None, int)], 4550 'StringNode': [('value', None, str)], 4551 'ContinueNode': [], 4552 'BreakNode': [], 4553 'ArgumentNode': [('positional', accept_node_list), ('kwargs', accept_kwargs)], 4554 'ArrayNode': [('args', accept_node)], 4555 'DictNode': [('args', accept_node)], 4556 'EmptyNode': [], 4557 'OrNode': [('left', accept_node), ('right', accept_node)], 4558 'AndNode': [('left', accept_node), ('right', accept_node)], 4559 'ComparisonNode': [('left', accept_node), ('right', accept_node), ('ctype', None, str)], 4560 'ArithmeticNode': [('left', accept_node), ('right', accept_node), ('op', None, str)], 4561 'NotNode': [('right', accept_node)], 4562 'CodeBlockNode': [('lines', accept_node_list)], 4563 'IndexNode': [('object', accept_node), ('index', accept_node)], 4564 'MethodNode': [('object', accept_node), ('args', accept_node), ('name', None, str)], 4565 'FunctionNode': [('args', accept_node), ('name', None, str)], 4566 'AssignmentNode': [('value', accept_node), ('var_name', None, str)], 4567 'PlusAssignmentNode': [('value', accept_node), ('var_name', None, str)], 4568 'ForeachClauseNode': [('items', accept_node), ('block', accept_node), ('varnames', None, list)], 4569 'IfClauseNode': [('ifs', accept_node_list), ('else', accept_node)], 4570 'IfNode': [('condition', accept_node), ('block', accept_node)], 4571 'UMinusNode': [('right', accept_node)], 4572 'TernaryNode': [('condition', accept_node), ('true', accept_node), ('false', accept_node)], 4573 } 4574 4575 accept_node(res_nb) 4576 4577 for n, c in [('ContinueNode', 2), ('BreakNode', 1), ('NotNode', 3)]: 4578 self.assertIn(n, node_counter) 4579 self.assertEqual(node_counter[n], c) 4580 4581 def test_introspect_dependencies_from_source(self): 4582 testdir = os.path.join(self.unit_test_dir, '57 introspection') 4583 testfile = os.path.join(testdir, 'meson.build') 4584 res_nb = self.introspect_directory(testfile, ['--scan-dependencies'] + self.meson_args) 4585 expected = [ 4586 { 4587 'name': 'threads', 4588 'required': True, 4589 'version': [], 4590 'has_fallback': False, 4591 'conditional': False 4592 }, 4593 { 4594 'name': 'zlib', 4595 'required': False, 4596 'version': [], 4597 'has_fallback': False, 4598 'conditional': False 4599 }, 4600 { 4601 'name': 'bugDep1', 4602 'required': True, 4603 'version': [], 4604 'has_fallback': False, 4605 'conditional': False 4606 }, 4607 { 4608 'name': 'somethingthatdoesnotexist', 4609 'required': True, 4610 'version': ['>=1.2.3'], 4611 'has_fallback': False, 4612 'conditional': True 4613 }, 4614 { 4615 'name': 'look_i_have_a_fallback', 4616 'required': True, 4617 'version': ['>=1.0.0', '<=99.9.9'], 4618 'has_fallback': True, 4619 'conditional': True 4620 } 4621 ] 4622 self.maxDiff = None 4623 self.assertListEqual(res_nb, expected) 4624 4625 def test_unstable_coredata(self): 4626 testdir = os.path.join(self.common_test_dir, '1 trivial') 4627 self.init(testdir) 4628 # just test that the command does not fail (e.g. because it throws an exception) 4629 self._run([*self.meson_command, 'unstable-coredata', self.builddir]) 4630 4631 @skip_if_no_cmake 4632 def test_cmake_prefix_path(self): 4633 testdir = os.path.join(self.unit_test_dir, '64 cmake_prefix_path') 4634 self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')]) 4635 4636 @skip_if_no_cmake 4637 def test_cmake_parser(self): 4638 testdir = os.path.join(self.unit_test_dir, '65 cmake parser') 4639 self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')]) 4640 4641 def test_alias_target(self): 4642 if self.backend is Backend.vs: 4643 # FIXME: This unit test is broken with vs backend, needs investigation 4644 raise unittest.SkipTest('Skipping alias_target test with {} backend'.format(self.backend.name)) 4645 testdir = os.path.join(self.unit_test_dir, '66 alias target') 4646 self.init(testdir) 4647 self.build() 4648 self.assertPathDoesNotExist(os.path.join(self.builddir, 'prog' + exe_suffix)) 4649 self.assertPathDoesNotExist(os.path.join(self.builddir, 'hello.txt')) 4650 self.run_target('build-all') 4651 self.assertPathExists(os.path.join(self.builddir, 'prog' + exe_suffix)) 4652 self.assertPathExists(os.path.join(self.builddir, 'hello.txt')) 4653 4654 def test_configure(self): 4655 testdir = os.path.join(self.common_test_dir, '2 cpp') 4656 self.init(testdir) 4657 self._run(self.mconf_command + [self.builddir]) 4658 4659 def test_summary(self): 4660 testdir = os.path.join(self.unit_test_dir, '73 summary') 4661 out = self.init(testdir) 4662 expected = textwrap.dedent(r''' 4663 Some Subproject 2.0 4664 4665 string: bar 4666 integer: 1 4667 boolean: True 4668 4669 My Project 1.0 4670 4671 Configuration 4672 Some boolean: False 4673 Another boolean: True 4674 Some string: Hello World 4675 A list: string 4676 1 4677 True 4678 empty list: 4679 A number: 1 4680 yes: YES 4681 no: NO 4682 coma list: a, b, c 4683 4684 Subprojects 4685 sub: YES 4686 sub2: NO Problem encountered: This subproject failed 4687 ''') 4688 expected_lines = expected.split('\n')[1:] 4689 out_start = out.find(expected_lines[0]) 4690 out_lines = out[out_start:].split('\n')[:len(expected_lines)] 4691 if sys.version_info < (3, 7, 0): 4692 # Dictionary order is not stable in Python <3.7, so sort the lines 4693 # while comparing 4694 self.assertEqual(sorted(expected_lines), sorted(out_lines)) 4695 else: 4696 self.assertEqual(expected_lines, out_lines) 4697 4698 def test_meson_compile(self): 4699 """Test the meson compile command.""" 4700 4701 def get_exe_name(basename: str) -> str: 4702 if is_windows(): 4703 return '{}.exe'.format(basename) 4704 else: 4705 return basename 4706 4707 def get_shared_lib_name(basename: str) -> str: 4708 if mesonbuild.environment.detect_msys2_arch(): 4709 return 'lib{}.dll'.format(basename) 4710 elif is_windows(): 4711 return '{}.dll'.format(basename) 4712 elif is_cygwin(): 4713 return 'cyg{}.dll'.format(basename) 4714 elif is_osx(): 4715 return 'lib{}.dylib'.format(basename) 4716 else: 4717 return 'lib{}.so'.format(basename) 4718 4719 def get_static_lib_name(basename: str) -> str: 4720 return 'lib{}.a'.format(basename) 4721 4722 # Base case (no targets or additional arguments) 4723 4724 testdir = os.path.join(self.common_test_dir, '1 trivial') 4725 self.init(testdir) 4726 4727 self._run([*self.meson_command, 'compile', '-C', self.builddir]) 4728 self.assertPathExists(os.path.join(self.builddir, get_exe_name('trivialprog'))) 4729 4730 # `--clean` 4731 4732 self._run([*self.meson_command, 'compile', '-C', self.builddir, '--clean']) 4733 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) 4734 4735 # Target specified in a project with unique names 4736 4737 testdir = os.path.join(self.common_test_dir, '6 linkshared') 4738 self.init(testdir, extra_args=['--wipe']) 4739 # Multiple targets and target type specified 4740 self._run([*self.meson_command, 'compile', '-C', self.builddir, 'mylib', 'mycpplib:shared_library']) 4741 # Check that we have a shared lib, but not an executable, i.e. check that target actually worked 4742 self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mylib'))) 4743 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('prog'))) 4744 self.assertPathExists(os.path.join(self.builddir, get_shared_lib_name('mycpplib'))) 4745 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('cppprog'))) 4746 4747 # Target specified in a project with non unique names 4748 4749 testdir = os.path.join(self.common_test_dir, '190 same target name') 4750 self.init(testdir, extra_args=['--wipe']) 4751 self._run([*self.meson_command, 'compile', '-C', self.builddir, './foo']) 4752 self.assertPathExists(os.path.join(self.builddir, get_static_lib_name('foo'))) 4753 self._run([*self.meson_command, 'compile', '-C', self.builddir, 'sub/foo']) 4754 self.assertPathExists(os.path.join(self.builddir, 'sub', get_static_lib_name('foo'))) 4755 4756 # run_target 4757 4758 testdir = os.path.join(self.common_test_dir, '54 run target') 4759 self.init(testdir, extra_args=['--wipe']) 4760 out = self._run([*self.meson_command, 'compile', '-C', self.builddir, 'py3hi']) 4761 self.assertIn('I am Python3.', out) 4762 4763 # `--$BACKEND-args` 4764 4765 testdir = os.path.join(self.common_test_dir, '1 trivial') 4766 if self.backend is Backend.ninja: 4767 self.init(testdir, extra_args=['--wipe']) 4768 # Dry run - should not create a program 4769 self._run([*self.meson_command, 'compile', '-C', self.builddir, '--ninja-args=-n']) 4770 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) 4771 elif self.backend is Backend.vs: 4772 self.init(testdir, extra_args=['--wipe']) 4773 self._run([*self.meson_command, 'compile', '-C', self.builddir]) 4774 # Explicitly clean the target through msbuild interface 4775 self._run([*self.meson_command, 'compile', '-C', self.builddir, '--vs-args=-t:{}:Clean'.format(re.sub(r'[\%\$\@\;\.\(\)\']', '_', get_exe_name('trivialprog')))]) 4776 self.assertPathDoesNotExist(os.path.join(self.builddir, get_exe_name('trivialprog'))) 4777 4778 def test_spurious_reconfigure_built_dep_file(self): 4779 testdir = os.path.join(self.unit_test_dir, '75 dep files') 4780 4781 # Regression test: Spurious reconfigure was happening when build 4782 # directory is inside source directory. 4783 # See https://gitlab.freedesktop.org/gstreamer/gst-build/-/issues/85. 4784 srcdir = os.path.join(self.builddir, 'srctree') 4785 shutil.copytree(testdir, srcdir) 4786 builddir = os.path.join(srcdir, '_build') 4787 self.change_builddir(builddir) 4788 4789 self.init(srcdir) 4790 self.build() 4791 4792 # During first configure the file did not exist so no dependency should 4793 # have been set. A rebuild should not trigger a reconfigure. 4794 self.clean() 4795 out = self.build() 4796 self.assertNotIn('Project configured', out) 4797 4798 self.init(srcdir, extra_args=['--reconfigure']) 4799 4800 # During the reconfigure the file did exist, but is inside build 4801 # directory, so no dependency should have been set. A rebuild should not 4802 # trigger a reconfigure. 4803 self.clean() 4804 out = self.build() 4805 self.assertNotIn('Project configured', out) 4806 4807 def _test_junit(self, case: str) -> None: 4808 try: 4809 import lxml.etree as et 4810 except ImportError: 4811 raise unittest.SkipTest('lxml required, but not found.') 4812 4813 schema = et.XMLSchema(et.parse(str(Path(__file__).parent / 'data' / 'schema.xsd'))) 4814 4815 self.init(case) 4816 self.run_tests() 4817 4818 junit = et.parse(str(Path(self.builddir) / 'meson-logs' / 'testlog.junit.xml')) 4819 try: 4820 schema.assertValid(junit) 4821 except et.DocumentInvalid as e: 4822 self.fail(e.error_log) 4823 4824 def test_junit_valid_tap(self): 4825 self._test_junit(os.path.join(self.common_test_dir, '213 tap tests')) 4826 4827 def test_junit_valid_exitcode(self): 4828 self._test_junit(os.path.join(self.common_test_dir, '44 test args')) 4829 4830 def test_junit_valid_gtest(self): 4831 self._test_junit(os.path.join(self.framework_test_dir, '2 gtest')) 4832 4833 def test_link_language_linker(self): 4834 # TODO: there should be some way to query how we're linking things 4835 # without resorting to reading the ninja.build file 4836 if self.backend is not Backend.ninja: 4837 raise unittest.SkipTest('This test reads the ninja file') 4838 4839 testdir = os.path.join(self.common_test_dir, '232 link language') 4840 self.init(testdir) 4841 4842 build_ninja = os.path.join(self.builddir, 'build.ninja') 4843 with open(build_ninja, 'r', encoding='utf-8') as f: 4844 contents = f.read() 4845 4846 self.assertRegex(contents, r'build main(\.exe)?.*: c_LINKER') 4847 self.assertRegex(contents, r'build (lib|cyg)?mylib.*: c_LINKER') 4848 4849 def test_commands_documented(self): 4850 ''' 4851 Test that all listed meson commands are documented in Commands.md. 4852 ''' 4853 4854 # The docs directory is not in release tarballs. 4855 if not os.path.isdir('docs'): 4856 raise unittest.SkipTest('Doc directory does not exist.') 4857 doc_path = 'docs/markdown_dynamic/Commands.md' 4858 4859 md = None 4860 with open(doc_path, encoding='utf-8') as f: 4861 md = f.read() 4862 self.assertIsNotNone(md) 4863 4864 ## Get command sections 4865 4866 section_pattern = re.compile(r'^### (.+)$', re.MULTILINE) 4867 md_command_section_matches = [i for i in section_pattern.finditer(md)] 4868 md_command_sections = dict() 4869 for i, s in enumerate(md_command_section_matches): 4870 section_end = len(md) if i == len(md_command_section_matches) - 1 else md_command_section_matches[i + 1].start() 4871 md_command_sections[s.group(1)] = (s.start(), section_end) 4872 4873 ## Validate commands 4874 4875 md_commands = set(k for k,v in md_command_sections.items()) 4876 4877 help_output = self._run(self.meson_command + ['--help']) 4878 help_commands = set(c.strip() for c in re.findall(r'usage:(?:.+)?{((?:[a-z]+,*)+?)}', help_output, re.MULTILINE|re.DOTALL)[0].split(',')) 4879 4880 self.assertEqual(md_commands | {'help'}, help_commands, 'Doc file: `{}`'.format(doc_path)) 4881 4882 ## Validate that each section has proper placeholders 4883 4884 def get_data_pattern(command): 4885 return re.compile( 4886 r'^```[\r\n]' 4887 r'{{ cmd_help\[\'' + command + r'\'\]\[\'usage\'\] }}[\r\n]' 4888 r'^```[\r\n]' 4889 r'.*?' 4890 r'^```[\r\n]' 4891 r'{{ cmd_help\[\'' + command + r'\'\]\[\'arguments\'\] }}[\r\n]' 4892 r'^```', 4893 flags = re.MULTILINE|re.DOTALL) 4894 4895 for command in md_commands: 4896 m = get_data_pattern(command).search(md, pos=md_command_sections[command][0], endpos=md_command_sections[command][1]) 4897 self.assertIsNotNone(m, 'Command `{}` is missing placeholders for dynamic data. Doc file: `{}`'.format(command, doc_path)) 4898 4899 def _check_coverage_files(self, types=('text', 'xml', 'html')): 4900 covdir = Path(self.builddir) / 'meson-logs' 4901 files = [] 4902 if 'text' in types: 4903 files.append('coverage.txt') 4904 if 'xml' in types: 4905 files.append('coverage.xml') 4906 if 'html' in types: 4907 files.append('coveragereport/index.html') 4908 for f in files: 4909 self.assertTrue((covdir / f).is_file(), msg='{} is not a file'.format(f)) 4910 4911 def test_coverage(self): 4912 if mesonbuild.environment.detect_msys2_arch(): 4913 raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') 4914 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 4915 if not gcovr_exe: 4916 raise unittest.SkipTest('gcovr not found, or too old') 4917 testdir = os.path.join(self.common_test_dir, '1 trivial') 4918 env = get_fake_env(testdir, self.builddir, self.prefix) 4919 cc = env.detect_c_compiler(MachineChoice.HOST) 4920 if cc.get_id() == 'clang': 4921 if not mesonbuild.environment.detect_llvm_cov(): 4922 raise unittest.SkipTest('llvm-cov not found') 4923 if cc.get_id() == 'msvc': 4924 raise unittest.SkipTest('Test only applies to non-MSVC compilers') 4925 self.init(testdir, extra_args=['-Db_coverage=true']) 4926 self.build() 4927 self.run_tests() 4928 self.run_target('coverage') 4929 self._check_coverage_files() 4930 4931 def test_coverage_complex(self): 4932 if mesonbuild.environment.detect_msys2_arch(): 4933 raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') 4934 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 4935 if not gcovr_exe: 4936 raise unittest.SkipTest('gcovr not found, or too old') 4937 testdir = os.path.join(self.common_test_dir, '109 generatorcustom') 4938 env = get_fake_env(testdir, self.builddir, self.prefix) 4939 cc = env.detect_c_compiler(MachineChoice.HOST) 4940 if cc.get_id() == 'clang': 4941 if not mesonbuild.environment.detect_llvm_cov(): 4942 raise unittest.SkipTest('llvm-cov not found') 4943 if cc.get_id() == 'msvc': 4944 raise unittest.SkipTest('Test only applies to non-MSVC compilers') 4945 self.init(testdir, extra_args=['-Db_coverage=true']) 4946 self.build() 4947 self.run_tests() 4948 self.run_target('coverage') 4949 self._check_coverage_files() 4950 4951 def test_coverage_html(self): 4952 if mesonbuild.environment.detect_msys2_arch(): 4953 raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') 4954 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 4955 if not gcovr_exe: 4956 raise unittest.SkipTest('gcovr not found, or too old') 4957 testdir = os.path.join(self.common_test_dir, '1 trivial') 4958 env = get_fake_env(testdir, self.builddir, self.prefix) 4959 cc = env.detect_c_compiler(MachineChoice.HOST) 4960 if cc.get_id() == 'clang': 4961 if not mesonbuild.environment.detect_llvm_cov(): 4962 raise unittest.SkipTest('llvm-cov not found') 4963 if cc.get_id() == 'msvc': 4964 raise unittest.SkipTest('Test only applies to non-MSVC compilers') 4965 self.init(testdir, extra_args=['-Db_coverage=true']) 4966 self.build() 4967 self.run_tests() 4968 self.run_target('coverage-html') 4969 self._check_coverage_files(['html']) 4970 4971 def test_coverage_text(self): 4972 if mesonbuild.environment.detect_msys2_arch(): 4973 raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') 4974 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 4975 if not gcovr_exe: 4976 raise unittest.SkipTest('gcovr not found, or too old') 4977 testdir = os.path.join(self.common_test_dir, '1 trivial') 4978 env = get_fake_env(testdir, self.builddir, self.prefix) 4979 cc = env.detect_c_compiler(MachineChoice.HOST) 4980 if cc.get_id() == 'clang': 4981 if not mesonbuild.environment.detect_llvm_cov(): 4982 raise unittest.SkipTest('llvm-cov not found') 4983 if cc.get_id() == 'msvc': 4984 raise unittest.SkipTest('Test only applies to non-MSVC compilers') 4985 self.init(testdir, extra_args=['-Db_coverage=true']) 4986 self.build() 4987 self.run_tests() 4988 self.run_target('coverage-text') 4989 self._check_coverage_files(['text']) 4990 4991 def test_coverage_xml(self): 4992 if mesonbuild.environment.detect_msys2_arch(): 4993 raise unittest.SkipTest('Skipped due to problems with coverage on MSYS2') 4994 gcovr_exe, gcovr_new_rootdir = mesonbuild.environment.detect_gcovr() 4995 if not gcovr_exe: 4996 raise unittest.SkipTest('gcovr not found, or too old') 4997 testdir = os.path.join(self.common_test_dir, '1 trivial') 4998 env = get_fake_env(testdir, self.builddir, self.prefix) 4999 cc = env.detect_c_compiler(MachineChoice.HOST) 5000 if cc.get_id() == 'clang': 5001 if not mesonbuild.environment.detect_llvm_cov(): 5002 raise unittest.SkipTest('llvm-cov not found') 5003 if cc.get_id() == 'msvc': 5004 raise unittest.SkipTest('Test only applies to non-MSVC compilers') 5005 self.init(testdir, extra_args=['-Db_coverage=true']) 5006 self.build() 5007 self.run_tests() 5008 self.run_target('coverage-xml') 5009 self._check_coverage_files(['xml']) 5010 5011 def test_cross_file_constants(self): 5012 with temp_filename() as crossfile1, temp_filename() as crossfile2: 5013 with open(crossfile1, 'w') as f: 5014 f.write(textwrap.dedent( 5015 ''' 5016 [constants] 5017 compiler = 'gcc' 5018 ''')) 5019 with open(crossfile2, 'w') as f: 5020 f.write(textwrap.dedent( 5021 ''' 5022 [constants] 5023 toolchain = '/toolchain/' 5024 common_flags = ['--sysroot=' + toolchain / 'sysroot'] 5025 5026 [properties] 5027 c_args = common_flags + ['-DSOMETHING'] 5028 cpp_args = c_args + ['-DSOMETHING_ELSE'] 5029 5030 [binaries] 5031 c = toolchain / compiler 5032 ''')) 5033 5034 values = mesonbuild.coredata.parse_machine_files([crossfile1, crossfile2]) 5035 self.assertEqual(values['binaries']['c'], '/toolchain/gcc') 5036 self.assertEqual(values['properties']['c_args'], 5037 ['--sysroot=/toolchain/sysroot', '-DSOMETHING']) 5038 self.assertEqual(values['properties']['cpp_args'], 5039 ['--sysroot=/toolchain/sysroot', '-DSOMETHING', '-DSOMETHING_ELSE']) 5040 5041 @unittest.skipIf(is_windows(), 'Directory cleanup fails for some reason') 5042 def test_wrap_git(self): 5043 with tempfile.TemporaryDirectory() as tmpdir: 5044 srcdir = os.path.join(tmpdir, 'src') 5045 shutil.copytree(os.path.join(self.unit_test_dir, '78 wrap-git'), srcdir) 5046 upstream = os.path.join(srcdir, 'subprojects', 'wrap_git_upstream') 5047 upstream_uri = Path(upstream).as_uri() 5048 _git_init(upstream) 5049 with open(os.path.join(srcdir, 'subprojects', 'wrap_git.wrap'), 'w') as f: 5050 f.write(textwrap.dedent(''' 5051 [wrap-git] 5052 url = {} 5053 patch_directory = wrap_git_builddef 5054 revision = master 5055 '''.format(upstream_uri))) 5056 self.init(srcdir) 5057 self.build() 5058 self.run_tests() 5059 5060 def test_multi_output_custom_target_no_warning(self): 5061 testdir = os.path.join(self.common_test_dir, '235 custom_target source') 5062 5063 out = self.init(testdir) 5064 self.assertNotRegex(out, 'WARNING:.*Using the first one.') 5065 self.build() 5066 self.run_tests() 5067 5068class FailureTests(BasePlatformTests): 5069 ''' 5070 Tests that test failure conditions. Build files here should be dynamically 5071 generated and static tests should go into `test cases/failing*`. 5072 This is useful because there can be many ways in which a particular 5073 function can fail, and creating failing tests for all of them is tedious 5074 and slows down testing. 5075 ''' 5076 dnf = "[Dd]ependency.*not found(:.*)?" 5077 nopkg = '[Pp]kg-config.*not found' 5078 5079 def setUp(self): 5080 super().setUp() 5081 self.srcdir = os.path.realpath(tempfile.mkdtemp()) 5082 self.mbuild = os.path.join(self.srcdir, 'meson.build') 5083 self.moptions = os.path.join(self.srcdir, 'meson_options.txt') 5084 5085 def tearDown(self): 5086 super().tearDown() 5087 windows_proof_rmtree(self.srcdir) 5088 5089 def assertMesonRaises(self, contents, match, *, 5090 extra_args=None, 5091 langs=None, 5092 meson_version=None, 5093 options=None, 5094 override_envvars=None): 5095 ''' 5096 Assert that running meson configure on the specified @contents raises 5097 a error message matching regex @match. 5098 ''' 5099 if langs is None: 5100 langs = [] 5101 with open(self.mbuild, 'w') as f: 5102 f.write("project('failure test', 'c', 'cpp'") 5103 if meson_version: 5104 f.write(", meson_version: '{}'".format(meson_version)) 5105 f.write(")\n") 5106 for lang in langs: 5107 f.write("add_languages('{}', required : false)\n".format(lang)) 5108 f.write(contents) 5109 if options is not None: 5110 with open(self.moptions, 'w') as f: 5111 f.write(options) 5112 o = {'MESON_FORCE_BACKTRACE': '1'} 5113 if override_envvars is None: 5114 override_envvars = o 5115 else: 5116 override_envvars.update(o) 5117 # Force tracebacks so we can detect them properly 5118 with self.assertRaisesRegex(MesonException, match, msg=contents): 5119 # Must run in-process or we'll get a generic CalledProcessError 5120 self.init(self.srcdir, extra_args=extra_args, 5121 inprocess=True, 5122 override_envvars = override_envvars) 5123 5124 def obtainMesonOutput(self, contents, match, extra_args, langs, meson_version=None): 5125 if langs is None: 5126 langs = [] 5127 with open(self.mbuild, 'w') as f: 5128 f.write("project('output test', 'c', 'cpp'") 5129 if meson_version: 5130 f.write(", meson_version: '{}'".format(meson_version)) 5131 f.write(")\n") 5132 for lang in langs: 5133 f.write("add_languages('{}', required : false)\n".format(lang)) 5134 f.write(contents) 5135 # Run in-process for speed and consistency with assertMesonRaises 5136 return self.init(self.srcdir, extra_args=extra_args, inprocess=True) 5137 5138 def assertMesonOutputs(self, contents, match, extra_args=None, langs=None, meson_version=None): 5139 ''' 5140 Assert that running meson configure on the specified @contents outputs 5141 something that matches regex @match. 5142 ''' 5143 out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version) 5144 self.assertRegex(out, match) 5145 5146 def assertMesonDoesNotOutput(self, contents, match, extra_args=None, langs=None, meson_version=None): 5147 ''' 5148 Assert that running meson configure on the specified @contents does not output 5149 something that matches regex @match. 5150 ''' 5151 out = self.obtainMesonOutput(contents, match, extra_args, langs, meson_version) 5152 self.assertNotRegex(out, match) 5153 5154 @skipIfNoPkgconfig 5155 def test_dependency(self): 5156 if subprocess.call(['pkg-config', '--exists', 'zlib']) != 0: 5157 raise unittest.SkipTest('zlib not found with pkg-config') 5158 a = (("dependency('zlib', method : 'fail')", "'fail' is invalid"), 5159 ("dependency('zlib', static : '1')", "[Ss]tatic.*boolean"), 5160 ("dependency('zlib', version : 1)", "Item must be a list or one of <class 'str'>"), 5161 ("dependency('zlib', required : 1)", "[Rr]equired.*boolean"), 5162 ("dependency('zlib', method : 1)", "[Mm]ethod.*string"), 5163 ("dependency('zlibfail')", self.dnf),) 5164 for contents, match in a: 5165 self.assertMesonRaises(contents, match) 5166 5167 def test_apple_frameworks_dependency(self): 5168 if not is_osx(): 5169 raise unittest.SkipTest('only run on macOS') 5170 self.assertMesonRaises("dependency('appleframeworks')", 5171 "requires at least one module") 5172 5173 def test_extraframework_dependency_method(self): 5174 code = "dependency('python', method : 'extraframework')" 5175 if not is_osx(): 5176 self.assertMesonRaises(code, self.dnf) 5177 else: 5178 # Python2 framework is always available on macOS 5179 self.assertMesonOutputs(code, '[Dd]ependency.*python.*found.*YES') 5180 5181 def test_sdl2_notfound_dependency(self): 5182 # Want to test failure, so skip if available 5183 if shutil.which('sdl2-config'): 5184 raise unittest.SkipTest('sdl2-config found') 5185 self.assertMesonRaises("dependency('sdl2', method : 'sdlconfig')", self.dnf) 5186 if shutil.which('pkg-config'): 5187 self.assertMesonRaises("dependency('sdl2', method : 'pkg-config')", self.dnf) 5188 with no_pkgconfig(): 5189 # Look for pkg-config, cache it, then 5190 # Use cached pkg-config without erroring out, then 5191 # Use cached pkg-config to error out 5192 code = "dependency('foobarrr', method : 'pkg-config', required : false)\n" \ 5193 "dependency('foobarrr2', method : 'pkg-config', required : false)\n" \ 5194 "dependency('sdl2', method : 'pkg-config')" 5195 self.assertMesonRaises(code, self.nopkg) 5196 5197 def test_gnustep_notfound_dependency(self): 5198 # Want to test failure, so skip if available 5199 if shutil.which('gnustep-config'): 5200 raise unittest.SkipTest('gnustep-config found') 5201 self.assertMesonRaises("dependency('gnustep')", 5202 "(requires a Objc compiler|{})".format(self.dnf), 5203 langs = ['objc']) 5204 5205 def test_wx_notfound_dependency(self): 5206 # Want to test failure, so skip if available 5207 if shutil.which('wx-config-3.0') or shutil.which('wx-config') or shutil.which('wx-config-gtk3'): 5208 raise unittest.SkipTest('wx-config, wx-config-3.0 or wx-config-gtk3 found') 5209 self.assertMesonRaises("dependency('wxwidgets')", self.dnf) 5210 self.assertMesonOutputs("dependency('wxwidgets', required : false)", 5211 "Run-time dependency .*WxWidgets.* found: .*NO.*") 5212 5213 def test_wx_dependency(self): 5214 if not shutil.which('wx-config-3.0') and not shutil.which('wx-config') and not shutil.which('wx-config-gtk3'): 5215 raise unittest.SkipTest('Neither wx-config, wx-config-3.0 nor wx-config-gtk3 found') 5216 self.assertMesonRaises("dependency('wxwidgets', modules : 1)", 5217 "module argument is not a string") 5218 5219 def test_llvm_dependency(self): 5220 self.assertMesonRaises("dependency('llvm', modules : 'fail')", 5221 "(required.*fail|{})".format(self.dnf)) 5222 5223 def test_boost_notfound_dependency(self): 5224 # Can be run even if Boost is found or not 5225 self.assertMesonRaises("dependency('boost', modules : 1)", 5226 "module.*not a string") 5227 self.assertMesonRaises("dependency('boost', modules : 'fail')", 5228 "(fail.*not found|{})".format(self.dnf)) 5229 5230 def test_boost_BOOST_ROOT_dependency(self): 5231 # Test BOOST_ROOT; can be run even if Boost is found or not 5232 self.assertMesonRaises("dependency('boost')", 5233 "(BOOST_ROOT.*absolute|{})".format(self.dnf), 5234 override_envvars = {'BOOST_ROOT': 'relative/path'}) 5235 5236 def test_dependency_invalid_method(self): 5237 code = '''zlib_dep = dependency('zlib', required : false) 5238 zlib_dep.get_configtool_variable('foo') 5239 ''' 5240 self.assertMesonRaises(code, ".* is not a config-tool dependency") 5241 code = '''zlib_dep = dependency('zlib', required : false) 5242 dep = declare_dependency(dependencies : zlib_dep) 5243 dep.get_pkgconfig_variable('foo') 5244 ''' 5245 self.assertMesonRaises(code, "Method.*pkgconfig.*is invalid.*internal") 5246 code = '''zlib_dep = dependency('zlib', required : false) 5247 dep = declare_dependency(dependencies : zlib_dep) 5248 dep.get_configtool_variable('foo') 5249 ''' 5250 self.assertMesonRaises(code, "Method.*configtool.*is invalid.*internal") 5251 5252 def test_objc_cpp_detection(self): 5253 ''' 5254 Test that when we can't detect objc or objcpp, we fail gracefully. 5255 ''' 5256 env = get_fake_env() 5257 try: 5258 env.detect_objc_compiler(MachineChoice.HOST) 5259 env.detect_objcpp_compiler(MachineChoice.HOST) 5260 except EnvironmentException: 5261 code = "add_languages('objc')\nadd_languages('objcpp')" 5262 self.assertMesonRaises(code, "Unknown compiler") 5263 return 5264 raise unittest.SkipTest("objc and objcpp found, can't test detection failure") 5265 5266 def test_subproject_variables(self): 5267 ''' 5268 Test that: 5269 1. The correct message is outputted when a not-required dep is not 5270 found and the fallback subproject is also not found. 5271 2. A not-required fallback dependency is not found because the 5272 subproject failed to parse. 5273 3. A not-found not-required dep with a fallback subproject outputs the 5274 correct message when the fallback subproject is found but the 5275 variable inside it is not. 5276 4. A fallback dependency is found from the subproject parsed in (3) 5277 5. The correct message is outputted when the .wrap file is missing for 5278 a sub-subproject. 5279 ''' 5280 tdir = os.path.join(self.unit_test_dir, '20 subproj dep variables') 5281 out = self.init(tdir, inprocess=True) 5282 self.assertRegex(out, r"Subproject directory not found and .*nosubproj.wrap.* file not found") 5283 self.assertRegex(out, r'Function does not take positional arguments.') 5284 self.assertRegex(out, r'WARNING:.* Dependency .*subsubproject.* not found but it is available in a sub-subproject.') 5285 self.assertRegex(out, r'Subproject directory not found and .*subsubproject.wrap.* file not found') 5286 self.assertRegex(out, r'Dependency .*zlibproxy.* from subproject .*subprojects.*somesubproj.* found: .*YES.*') 5287 5288 def test_exception_exit_status(self): 5289 ''' 5290 Test exit status on python exception 5291 ''' 5292 tdir = os.path.join(self.unit_test_dir, '21 exit status') 5293 with self.assertRaises(subprocess.CalledProcessError) as cm: 5294 self.init(tdir, inprocess=False, override_envvars = {'MESON_UNIT_TEST': '1'}) 5295 self.assertEqual(cm.exception.returncode, 2) 5296 self.wipe() 5297 5298 def test_dict_requires_key_value_pairs(self): 5299 self.assertMesonRaises("dict = {3, 'foo': 'bar'}", 5300 'Only key:value pairs are valid in dict construction.') 5301 self.assertMesonRaises("{'foo': 'bar', 3}", 5302 'Only key:value pairs are valid in dict construction.') 5303 5304 def test_dict_forbids_duplicate_keys(self): 5305 self.assertMesonRaises("dict = {'a': 41, 'a': 42}", 5306 'Duplicate dictionary key: a.*') 5307 5308 def test_dict_forbids_integer_key(self): 5309 self.assertMesonRaises("dict = {3: 'foo'}", 5310 'Key must be a string.*') 5311 5312 def test_using_too_recent_feature(self): 5313 # Here we use a dict, which was introduced in 0.47.0 5314 self.assertMesonOutputs("dict = {}", 5315 ".*WARNING.*Project targeting.*but.*", 5316 meson_version='>= 0.46.0') 5317 5318 def test_using_recent_feature(self): 5319 # Same as above, except the meson version is now appropriate 5320 self.assertMesonDoesNotOutput("dict = {}", 5321 ".*WARNING.*Project targeting.*but.*", 5322 meson_version='>= 0.47') 5323 5324 def test_using_too_recent_feature_dependency(self): 5325 self.assertMesonOutputs("dependency('pcap', required: false)", 5326 ".*WARNING.*Project targeting.*but.*", 5327 meson_version='>= 0.41.0') 5328 5329 def test_vcs_tag_featurenew_build_always_stale(self): 5330 'https://github.com/mesonbuild/meson/issues/3904' 5331 vcs_tag = '''version_data = configuration_data() 5332 version_data.set('PROJVER', '@VCS_TAG@') 5333 vf = configure_file(output : 'version.h.in', configuration: version_data) 5334 f = vcs_tag(input : vf, output : 'version.h') 5335 ''' 5336 msg = '.*WARNING:.*feature.*build_always_stale.*custom_target.*' 5337 self.assertMesonDoesNotOutput(vcs_tag, msg, meson_version='>=0.43') 5338 5339 def test_missing_subproject_not_required_and_required(self): 5340 self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" + 5341 "sub2 = subproject('not-found-subproject', required: true)", 5342 """.*Subproject "subprojects/not-found-subproject" required but not found.*""") 5343 5344 def test_get_variable_on_not_found_project(self): 5345 self.assertMesonRaises("sub1 = subproject('not-found-subproject', required: false)\n" + 5346 "sub1.get_variable('naaa')", 5347 """Subproject "subprojects/not-found-subproject" disabled can't get_variable on it.""") 5348 5349 def test_version_checked_before_parsing_options(self): 5350 ''' 5351 https://github.com/mesonbuild/meson/issues/5281 5352 ''' 5353 options = "option('some-option', type: 'foo', value: '')" 5354 match = 'Meson version is.*but project requires >=2000' 5355 self.assertMesonRaises("", match, meson_version='>=2000', options=options) 5356 5357 def test_assert_default_message(self): 5358 self.assertMesonRaises("k1 = 'a'\n" + 5359 "assert({\n" + 5360 " k1: 1,\n" + 5361 "}['a'] == 2)\n", 5362 r"Assert failed: {k1 : 1}\['a'\] == 2") 5363 5364 def test_wrap_nofallback(self): 5365 self.assertMesonRaises("dependency('notfound', fallback : ['foo', 'foo_dep'])", 5366 r"Dependency \'notfound\' not found and fallback is disabled", 5367 extra_args=['--wrap-mode=nofallback']) 5368 5369 def test_message(self): 5370 self.assertMesonOutputs("message('Array:', ['a', 'b'])", 5371 r"Message:.* Array: \['a', 'b'\]") 5372 5373 def test_warning(self): 5374 self.assertMesonOutputs("warning('Array:', ['a', 'b'])", 5375 r"WARNING:.* Array: \['a', 'b'\]") 5376 5377 def test_override_dependency_twice(self): 5378 self.assertMesonRaises("meson.override_dependency('foo', declare_dependency())\n" + 5379 "meson.override_dependency('foo', declare_dependency())", 5380 """Tried to override dependency 'foo' which has already been resolved or overridden""") 5381 5382 @unittest.skipIf(is_windows(), 'zlib is not available on Windows') 5383 def test_override_resolved_dependency(self): 5384 self.assertMesonRaises("dependency('zlib')\n" + 5385 "meson.override_dependency('zlib', declare_dependency())", 5386 """Tried to override dependency 'zlib' which has already been resolved or overridden""") 5387 5388@unittest.skipUnless(is_windows() or is_cygwin(), "requires Windows (or Windows via Cygwin)") 5389class WindowsTests(BasePlatformTests): 5390 ''' 5391 Tests that should run on Cygwin, MinGW, and MSVC 5392 ''' 5393 5394 def setUp(self): 5395 super().setUp() 5396 self.platform_test_dir = os.path.join(self.src_root, 'test cases/windows') 5397 5398 @unittest.skipIf(is_cygwin(), 'Test only applicable to Windows') 5399 def test_find_program(self): 5400 ''' 5401 Test that Windows-specific edge-cases in find_program are functioning 5402 correctly. Cannot be an ordinary test because it involves manipulating 5403 PATH to point to a directory with Python scripts. 5404 ''' 5405 testdir = os.path.join(self.platform_test_dir, '8 find program') 5406 # Find `cmd` and `cmd.exe` 5407 prog1 = ExternalProgram('cmd') 5408 self.assertTrue(prog1.found(), msg='cmd not found') 5409 prog2 = ExternalProgram('cmd.exe') 5410 self.assertTrue(prog2.found(), msg='cmd.exe not found') 5411 self.assertPathEqual(prog1.get_path(), prog2.get_path()) 5412 # Find cmd.exe with args without searching 5413 prog = ExternalProgram('cmd', command=['cmd', '/C']) 5414 self.assertTrue(prog.found(), msg='cmd not found with args') 5415 self.assertPathEqual(prog.get_command()[0], 'cmd') 5416 # Find cmd with an absolute path that's missing the extension 5417 cmd_path = prog2.get_path()[:-4] 5418 prog = ExternalProgram(cmd_path) 5419 self.assertTrue(prog.found(), msg='{!r} not found'.format(cmd_path)) 5420 # Finding a script with no extension inside a directory works 5421 prog = ExternalProgram(os.path.join(testdir, 'test-script')) 5422 self.assertTrue(prog.found(), msg='test-script not found') 5423 # Finding a script with an extension inside a directory works 5424 prog = ExternalProgram(os.path.join(testdir, 'test-script-ext.py')) 5425 self.assertTrue(prog.found(), msg='test-script-ext.py not found') 5426 # Finding a script in PATH 5427 os.environ['PATH'] += os.pathsep + testdir 5428 # If `.PY` is in PATHEXT, scripts can be found as programs 5429 if '.PY' in [ext.upper() for ext in os.environ['PATHEXT'].split(';')]: 5430 # Finding a script in PATH w/o extension works and adds the interpreter 5431 prog = ExternalProgram('test-script-ext') 5432 self.assertTrue(prog.found(), msg='test-script-ext not found in PATH') 5433 self.assertPathEqual(prog.get_command()[0], python_command[0]) 5434 self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') 5435 # Finding a script in PATH with extension works and adds the interpreter 5436 prog = ExternalProgram('test-script-ext.py') 5437 self.assertTrue(prog.found(), msg='test-script-ext.py not found in PATH') 5438 self.assertPathEqual(prog.get_command()[0], python_command[0]) 5439 self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') 5440 # Using a script with an extension directly via command= works and adds the interpreter 5441 prog = ExternalProgram('test-script-ext.py', command=[os.path.join(testdir, 'test-script-ext.py'), '--help']) 5442 self.assertTrue(prog.found(), msg='test-script-ext.py with full path not picked up via command=') 5443 self.assertPathEqual(prog.get_command()[0], python_command[0]) 5444 self.assertPathEqual(prog.get_command()[2], '--help') 5445 self.assertPathBasenameEqual(prog.get_path(), 'test-script-ext.py') 5446 # Using a script without an extension directly via command= works and adds the interpreter 5447 prog = ExternalProgram('test-script', command=[os.path.join(testdir, 'test-script'), '--help']) 5448 self.assertTrue(prog.found(), msg='test-script with full path not picked up via command=') 5449 self.assertPathEqual(prog.get_command()[0], python_command[0]) 5450 self.assertPathEqual(prog.get_command()[2], '--help') 5451 self.assertPathBasenameEqual(prog.get_path(), 'test-script') 5452 # Ensure that WindowsApps gets removed from PATH 5453 path = os.environ['PATH'] 5454 if 'WindowsApps' not in path: 5455 username = os.environ['USERNAME'] 5456 appstore_dir = r'C:\Users\{}\AppData\Local\Microsoft\WindowsApps'.format(username) 5457 path = os.pathsep + appstore_dir 5458 path = ExternalProgram._windows_sanitize_path(path) 5459 self.assertNotIn('WindowsApps', path) 5460 5461 def test_ignore_libs(self): 5462 ''' 5463 Test that find_library on libs that are to be ignored returns an empty 5464 array of arguments. Must be a unit test because we cannot inspect 5465 ExternalLibraryHolder from build files. 5466 ''' 5467 testdir = os.path.join(self.platform_test_dir, '1 basic') 5468 env = get_fake_env(testdir, self.builddir, self.prefix) 5469 cc = env.detect_c_compiler(MachineChoice.HOST) 5470 if cc.get_argument_syntax() != 'msvc': 5471 raise unittest.SkipTest('Not using MSVC') 5472 # To force people to update this test, and also test 5473 self.assertEqual(set(cc.ignore_libs), {'c', 'm', 'pthread', 'dl', 'rt', 'execinfo'}) 5474 for l in cc.ignore_libs: 5475 self.assertEqual(cc.find_library(l, env, []), []) 5476 5477 def test_rc_depends_files(self): 5478 testdir = os.path.join(self.platform_test_dir, '5 resources') 5479 5480 # resource compiler depfile generation is not yet implemented for msvc 5481 env = get_fake_env(testdir, self.builddir, self.prefix) 5482 depfile_works = env.detect_c_compiler(MachineChoice.HOST).get_id() not in {'msvc', 'clang-cl', 'intel-cl'} 5483 5484 self.init(testdir) 5485 self.build() 5486 # Immediately rebuilding should not do anything 5487 self.assertBuildIsNoop() 5488 # Test compile_resources(depend_file:) 5489 # Changing mtime of sample.ico should rebuild prog 5490 self.utime(os.path.join(testdir, 'res', 'sample.ico')) 5491 self.assertRebuiltTarget('prog') 5492 # Test depfile generation by compile_resources 5493 # Changing mtime of resource.h should rebuild myres.rc and then prog 5494 if depfile_works: 5495 self.utime(os.path.join(testdir, 'inc', 'resource', 'resource.h')) 5496 self.assertRebuiltTarget('prog') 5497 self.wipe() 5498 5499 if depfile_works: 5500 testdir = os.path.join(self.platform_test_dir, '12 resources with custom targets') 5501 self.init(testdir) 5502 self.build() 5503 # Immediately rebuilding should not do anything 5504 self.assertBuildIsNoop() 5505 # Changing mtime of resource.h should rebuild myres_1.rc and then prog_1 5506 self.utime(os.path.join(testdir, 'res', 'resource.h')) 5507 self.assertRebuiltTarget('prog_1') 5508 5509 def test_msvc_cpp17(self): 5510 testdir = os.path.join(self.unit_test_dir, '45 vscpp17') 5511 5512 env = get_fake_env(testdir, self.builddir, self.prefix) 5513 cc = env.detect_c_compiler(MachineChoice.HOST) 5514 if cc.get_argument_syntax() != 'msvc': 5515 raise unittest.SkipTest('Test only applies to MSVC-like compilers') 5516 5517 try: 5518 self.init(testdir) 5519 except subprocess.CalledProcessError: 5520 # According to Python docs, output is only stored when 5521 # using check_output. We don't use it, so we can't check 5522 # that the output is correct (i.e. that it failed due 5523 # to the right reason). 5524 return 5525 self.build() 5526 5527 def test_install_pdb_introspection(self): 5528 testdir = os.path.join(self.platform_test_dir, '1 basic') 5529 5530 env = get_fake_env(testdir, self.builddir, self.prefix) 5531 cc = env.detect_c_compiler(MachineChoice.HOST) 5532 if cc.get_argument_syntax() != 'msvc': 5533 raise unittest.SkipTest('Test only applies to MSVC-like compilers') 5534 5535 self.init(testdir) 5536 installed = self.introspect('--installed') 5537 files = [os.path.basename(path) for path in installed.values()] 5538 5539 self.assertTrue('prog.pdb' in files) 5540 5541 def _check_ld(self, name: str, lang: str, expected: str) -> None: 5542 if not shutil.which(name): 5543 raise unittest.SkipTest('Could not find {}.'.format(name)) 5544 envvars = [mesonbuild.envconfig.BinaryTable.evarMap['{}_ld'.format(lang)]] 5545 5546 # Also test a deprecated variable if there is one. 5547 if envvars[0] in mesonbuild.envconfig.BinaryTable.DEPRECATION_MAP: 5548 envvars.append( 5549 mesonbuild.envconfig.BinaryTable.DEPRECATION_MAP[envvars[0]]) 5550 5551 for envvar in envvars: 5552 with mock.patch.dict(os.environ, {envvar: name}): 5553 env = get_fake_env() 5554 try: 5555 comp = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST) 5556 except EnvironmentException: 5557 raise unittest.SkipTest('Could not find a compiler for {}'.format(lang)) 5558 self.assertEqual(comp.linker.id, expected) 5559 5560 def test_link_environment_variable_lld_link(self): 5561 env = get_fake_env() 5562 comp = getattr(env, 'detect_c_compiler')(MachineChoice.HOST) 5563 if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): 5564 raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') 5565 self._check_ld('lld-link', 'c', 'lld-link') 5566 5567 def test_link_environment_variable_link(self): 5568 env = get_fake_env() 5569 comp = getattr(env, 'detect_c_compiler')(MachineChoice.HOST) 5570 if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): 5571 raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') 5572 self._check_ld('link', 'c', 'link') 5573 5574 def test_link_environment_variable_optlink(self): 5575 env = get_fake_env() 5576 comp = getattr(env, 'detect_c_compiler')(MachineChoice.HOST) 5577 if isinstance(comp, mesonbuild.compilers.GnuLikeCompiler): 5578 raise unittest.SkipTest('GCC cannot be used with link compatible linkers.') 5579 self._check_ld('optlink', 'c', 'optlink') 5580 5581 @skip_if_not_language('rust') 5582 def test_link_environment_variable_rust(self): 5583 self._check_ld('link', 'rust', 'link') 5584 5585 @skip_if_not_language('d') 5586 def test_link_environment_variable_d(self): 5587 env = get_fake_env() 5588 comp = getattr(env, 'detect_d_compiler')(MachineChoice.HOST) 5589 if comp.id == 'dmd': 5590 raise unittest.SkipTest('meson cannot reliably make DMD use a different linker.') 5591 self._check_ld('lld-link', 'd', 'lld-link') 5592 5593 def test_pefile_checksum(self): 5594 try: 5595 import pefile 5596 except ImportError: 5597 if is_ci(): 5598 raise 5599 raise unittest.SkipTest('pefile module not found') 5600 testdir = os.path.join(self.common_test_dir, '6 linkshared') 5601 self.init(testdir, extra_args=['--buildtype=release']) 5602 self.build() 5603 # Test that binaries have a non-zero checksum 5604 env = get_fake_env() 5605 cc = env.detect_c_compiler(MachineChoice.HOST) 5606 cc_id = cc.get_id() 5607 ld_id = cc.get_linker_id() 5608 dll = glob(os.path.join(self.builddir, '*mycpplib.dll'))[0] 5609 exe = os.path.join(self.builddir, 'cppprog.exe') 5610 for f in (dll, exe): 5611 pe = pefile.PE(f) 5612 msg = 'PE file: {!r}, compiler: {!r}, linker: {!r}'.format(f, cc_id, ld_id) 5613 if cc_id == 'clang-cl': 5614 # Latest clang-cl tested (7.0) does not write checksums out 5615 self.assertFalse(pe.verify_checksum(), msg=msg) 5616 else: 5617 # Verify that a valid checksum was written by all other compilers 5618 self.assertTrue(pe.verify_checksum(), msg=msg) 5619 5620 def test_qt5dependency_vscrt(self): 5621 ''' 5622 Test that qt5 dependencies use the debug module suffix when b_vscrt is 5623 set to 'mdd' 5624 ''' 5625 # Verify that the `b_vscrt` option is available 5626 env = get_fake_env() 5627 cc = env.detect_c_compiler(MachineChoice.HOST) 5628 if 'b_vscrt' not in cc.base_options: 5629 raise unittest.SkipTest('Compiler does not support setting the VS CRT') 5630 # Verify that qmake is for Qt5 5631 if not shutil.which('qmake-qt5'): 5632 if not shutil.which('qmake') and not is_ci(): 5633 raise unittest.SkipTest('QMake not found') 5634 output = subprocess.getoutput('qmake --version') 5635 if 'Qt version 5' not in output and not is_ci(): 5636 raise unittest.SkipTest('Qmake found, but it is not for Qt 5.') 5637 # Setup with /MDd 5638 testdir = os.path.join(self.framework_test_dir, '4 qt') 5639 self.init(testdir, extra_args=['-Db_vscrt=mdd']) 5640 # Verify that we're linking to the debug versions of Qt DLLs 5641 build_ninja = os.path.join(self.builddir, 'build.ninja') 5642 with open(build_ninja, 'r', encoding='utf-8') as f: 5643 contents = f.read() 5644 m = re.search('build qt5core.exe: cpp_LINKER.*Qt5Cored.lib', contents) 5645 self.assertIsNotNone(m, msg=contents) 5646 5647 def test_compiler_checks_vscrt(self): 5648 ''' 5649 Test that the correct VS CRT is used when running compiler checks 5650 ''' 5651 # Verify that the `b_vscrt` option is available 5652 env = get_fake_env() 5653 cc = env.detect_c_compiler(MachineChoice.HOST) 5654 if 'b_vscrt' not in cc.base_options: 5655 raise unittest.SkipTest('Compiler does not support setting the VS CRT') 5656 5657 def sanitycheck_vscrt(vscrt): 5658 checks = self.get_meson_log_sanitychecks() 5659 self.assertTrue(len(checks) > 0) 5660 for check in checks: 5661 self.assertIn(vscrt, check) 5662 5663 testdir = os.path.join(self.common_test_dir, '1 trivial') 5664 self.init(testdir) 5665 sanitycheck_vscrt('/MDd') 5666 5667 self.new_builddir() 5668 self.init(testdir, extra_args=['-Dbuildtype=debugoptimized']) 5669 sanitycheck_vscrt('/MD') 5670 5671 self.new_builddir() 5672 self.init(testdir, extra_args=['-Dbuildtype=release']) 5673 sanitycheck_vscrt('/MD') 5674 5675 self.new_builddir() 5676 self.init(testdir, extra_args=['-Db_vscrt=md']) 5677 sanitycheck_vscrt('/MD') 5678 5679 self.new_builddir() 5680 self.init(testdir, extra_args=['-Db_vscrt=mdd']) 5681 sanitycheck_vscrt('/MDd') 5682 5683 self.new_builddir() 5684 self.init(testdir, extra_args=['-Db_vscrt=mt']) 5685 sanitycheck_vscrt('/MT') 5686 5687 self.new_builddir() 5688 self.init(testdir, extra_args=['-Db_vscrt=mtd']) 5689 sanitycheck_vscrt('/MTd') 5690 5691 5692@unittest.skipUnless(is_osx(), "requires Darwin") 5693class DarwinTests(BasePlatformTests): 5694 ''' 5695 Tests that should run on macOS 5696 ''' 5697 5698 def setUp(self): 5699 super().setUp() 5700 self.platform_test_dir = os.path.join(self.src_root, 'test cases/osx') 5701 5702 def test_apple_bitcode(self): 5703 ''' 5704 Test that -fembed-bitcode is correctly added while compiling and 5705 -bitcode_bundle is added while linking when b_bitcode is true and not 5706 when it is false. This can't be an ordinary test case because we need 5707 to inspect the compiler database. 5708 ''' 5709 testdir = os.path.join(self.platform_test_dir, '7 bitcode') 5710 env = get_fake_env(testdir, self.builddir, self.prefix) 5711 cc = env.detect_c_compiler(MachineChoice.HOST) 5712 if cc.id != 'clang': 5713 raise unittest.SkipTest('Not using Clang on OSX') 5714 # Try with bitcode enabled 5715 out = self.init(testdir, extra_args='-Db_bitcode=true') 5716 # Warning was printed 5717 self.assertRegex(out, 'WARNING:.*b_bitcode') 5718 # Compiler options were added 5719 for compdb in self.get_compdb(): 5720 if 'module' in compdb['file']: 5721 self.assertNotIn('-fembed-bitcode', compdb['command']) 5722 else: 5723 self.assertIn('-fembed-bitcode', compdb['command']) 5724 build_ninja = os.path.join(self.builddir, 'build.ninja') 5725 # Linker options were added 5726 with open(build_ninja, 'r', encoding='utf-8') as f: 5727 contents = f.read() 5728 m = re.search('LINK_ARGS =.*-bitcode_bundle', contents) 5729 self.assertIsNotNone(m, msg=contents) 5730 # Try with bitcode disabled 5731 self.setconf('-Db_bitcode=false') 5732 # Regenerate build 5733 self.build() 5734 for compdb in self.get_compdb(): 5735 self.assertNotIn('-fembed-bitcode', compdb['command']) 5736 build_ninja = os.path.join(self.builddir, 'build.ninja') 5737 with open(build_ninja, 'r', encoding='utf-8') as f: 5738 contents = f.read() 5739 m = re.search('LINK_ARGS =.*-bitcode_bundle', contents) 5740 self.assertIsNone(m, msg=contents) 5741 5742 def test_apple_bitcode_modules(self): 5743 ''' 5744 Same as above, just for shared_module() 5745 ''' 5746 testdir = os.path.join(self.common_test_dir, '152 shared module resolving symbol in executable') 5747 # Ensure that it builds even with bitcode enabled 5748 self.init(testdir, extra_args='-Db_bitcode=true') 5749 self.build() 5750 self.run_tests() 5751 5752 def _get_darwin_versions(self, fname): 5753 fname = os.path.join(self.builddir, fname) 5754 out = subprocess.check_output(['otool', '-L', fname], universal_newlines=True) 5755 m = re.match(r'.*version (.*), current version (.*)\)', out.split('\n')[1]) 5756 self.assertIsNotNone(m, msg=out) 5757 return m.groups() 5758 5759 @skipIfNoPkgconfig 5760 def test_library_versioning(self): 5761 ''' 5762 Ensure that compatibility_version and current_version are set correctly 5763 ''' 5764 testdir = os.path.join(self.platform_test_dir, '2 library versions') 5765 self.init(testdir) 5766 self.build() 5767 targets = {} 5768 for t in self.introspect('--targets'): 5769 targets[t['name']] = t['filename'][0] if isinstance(t['filename'], list) else t['filename'] 5770 self.assertEqual(self._get_darwin_versions(targets['some']), ('7.0.0', '7.0.0')) 5771 self.assertEqual(self._get_darwin_versions(targets['noversion']), ('0.0.0', '0.0.0')) 5772 self.assertEqual(self._get_darwin_versions(targets['onlyversion']), ('1.0.0', '1.0.0')) 5773 self.assertEqual(self._get_darwin_versions(targets['onlysoversion']), ('5.0.0', '5.0.0')) 5774 self.assertEqual(self._get_darwin_versions(targets['intver']), ('2.0.0', '2.0.0')) 5775 self.assertEqual(self._get_darwin_versions(targets['stringver']), ('2.3.0', '2.3.0')) 5776 self.assertEqual(self._get_darwin_versions(targets['stringlistver']), ('2.4.0', '2.4.0')) 5777 self.assertEqual(self._get_darwin_versions(targets['intstringver']), ('1111.0.0', '2.5.0')) 5778 self.assertEqual(self._get_darwin_versions(targets['stringlistvers']), ('2.6.0', '2.6.1')) 5779 5780 def test_duplicate_rpath(self): 5781 testdir = os.path.join(self.unit_test_dir, '10 build_rpath') 5782 # We purposely pass a duplicate rpath to Meson, in order 5783 # to ascertain that Meson does not call install_name_tool 5784 # with duplicate -delete_rpath arguments, which would 5785 # lead to erroring out on installation 5786 env = {"LDFLAGS": "-Wl,-rpath,/foo/bar"} 5787 self.init(testdir, override_envvars=env) 5788 self.build() 5789 self.install() 5790 5791 def test_removing_unused_linker_args(self): 5792 testdir = os.path.join(self.common_test_dir, '108 has arg') 5793 env = {'CFLAGS': '-L/tmp -L /var/tmp -headerpad_max_install_names -Wl,-export_dynamic -framework Foundation'} 5794 self.init(testdir, override_envvars=env) 5795 5796 5797@unittest.skipUnless(not is_windows(), "requires something Unix-like") 5798class LinuxlikeTests(BasePlatformTests): 5799 ''' 5800 Tests that should run on Linux, macOS, and *BSD 5801 ''' 5802 5803 def test_basic_soname(self): 5804 ''' 5805 Test that the soname is set correctly for shared libraries. This can't 5806 be an ordinary test case because we need to run `readelf` and actually 5807 check the soname. 5808 https://github.com/mesonbuild/meson/issues/785 5809 ''' 5810 testdir = os.path.join(self.common_test_dir, '4 shared') 5811 self.init(testdir) 5812 self.build() 5813 lib1 = os.path.join(self.builddir, 'libmylib.so') 5814 soname = get_soname(lib1) 5815 self.assertEqual(soname, 'libmylib.so') 5816 5817 def test_custom_soname(self): 5818 ''' 5819 Test that the soname is set correctly for shared libraries when 5820 a custom prefix and/or suffix is used. This can't be an ordinary test 5821 case because we need to run `readelf` and actually check the soname. 5822 https://github.com/mesonbuild/meson/issues/785 5823 ''' 5824 testdir = os.path.join(self.common_test_dir, '25 library versions') 5825 self.init(testdir) 5826 self.build() 5827 lib1 = os.path.join(self.builddir, 'prefixsomelib.suffix') 5828 soname = get_soname(lib1) 5829 self.assertEqual(soname, 'prefixsomelib.suffix') 5830 5831 def test_pic(self): 5832 ''' 5833 Test that -fPIC is correctly added to static libraries when b_staticpic 5834 is true and not when it is false. This can't be an ordinary test case 5835 because we need to inspect the compiler database. 5836 ''' 5837 if is_windows() or is_cygwin() or is_osx(): 5838 raise unittest.SkipTest('PIC not relevant') 5839 5840 testdir = os.path.join(self.common_test_dir, '3 static') 5841 self.init(testdir) 5842 compdb = self.get_compdb() 5843 self.assertIn('-fPIC', compdb[0]['command']) 5844 self.setconf('-Db_staticpic=false') 5845 # Regenerate build 5846 self.build() 5847 compdb = self.get_compdb() 5848 self.assertNotIn('-fPIC', compdb[0]['command']) 5849 5850 def test_pkgconfig_gen(self): 5851 ''' 5852 Test that generated pkg-config files can be found and have the correct 5853 version and link args. This can't be an ordinary test case because we 5854 need to run pkg-config outside of a Meson build file. 5855 https://github.com/mesonbuild/meson/issues/889 5856 ''' 5857 testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen') 5858 self.init(testdir) 5859 env = get_fake_env(testdir, self.builddir, self.prefix) 5860 kwargs = {'required': True, 'silent': True} 5861 os.environ['PKG_CONFIG_LIBDIR'] = self.privatedir 5862 foo_dep = PkgConfigDependency('libfoo', env, kwargs) 5863 self.assertTrue(foo_dep.found()) 5864 self.assertEqual(foo_dep.get_version(), '1.0') 5865 self.assertIn('-lfoo', foo_dep.get_link_args()) 5866 self.assertEqual(foo_dep.get_pkgconfig_variable('foo', {}), 'bar') 5867 self.assertPathEqual(foo_dep.get_pkgconfig_variable('datadir', {}), '/usr/data') 5868 5869 libhello_nolib = PkgConfigDependency('libhello_nolib', env, kwargs) 5870 self.assertTrue(libhello_nolib.found()) 5871 self.assertEqual(libhello_nolib.get_link_args(), []) 5872 self.assertEqual(libhello_nolib.get_compile_args(), []) 5873 5874 def test_pkgconfig_gen_deps(self): 5875 ''' 5876 Test that generated pkg-config files correctly handle dependencies 5877 ''' 5878 testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen') 5879 self.init(testdir) 5880 privatedir1 = self.privatedir 5881 5882 self.new_builddir() 5883 testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen', 'dependencies') 5884 self.init(testdir, override_envvars={'PKG_CONFIG_LIBDIR': privatedir1}) 5885 privatedir2 = self.privatedir 5886 5887 os.environ 5888 env = { 5889 'PKG_CONFIG_LIBDIR': os.pathsep.join([privatedir1, privatedir2]), 5890 'PKG_CONFIG_SYSTEM_LIBRARY_PATH': '/usr/lib', 5891 } 5892 self._run(['pkg-config', 'dependency-test', '--validate'], override_envvars=env) 5893 5894 # pkg-config strips some duplicated flags so we have to parse the 5895 # generated file ourself. 5896 expected = { 5897 'Requires': 'libexposed', 5898 'Requires.private': 'libfoo >= 1.0', 5899 'Libs': '-L${libdir} -llibmain -pthread -lcustom', 5900 'Libs.private': '-lcustom2 -L${libdir} -llibinternal', 5901 'Cflags': '-I${includedir} -pthread -DCUSTOM', 5902 } 5903 if is_osx() or is_haiku(): 5904 expected['Cflags'] = expected['Cflags'].replace('-pthread ', '') 5905 with open(os.path.join(privatedir2, 'dependency-test.pc')) as f: 5906 matched_lines = 0 5907 for line in f: 5908 parts = line.split(':', 1) 5909 if parts[0] in expected: 5910 key = parts[0] 5911 val = parts[1].strip() 5912 expected_val = expected[key] 5913 self.assertEqual(expected_val, val) 5914 matched_lines += 1 5915 self.assertEqual(len(expected), matched_lines) 5916 5917 cmd = ['pkg-config', 'requires-test'] 5918 out = self._run(cmd + ['--print-requires'], override_envvars=env).strip().split('\n') 5919 if not is_openbsd(): 5920 self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) 5921 else: 5922 self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello'])) 5923 5924 cmd = ['pkg-config', 'requires-private-test'] 5925 out = self._run(cmd + ['--print-requires-private'], override_envvars=env).strip().split('\n') 5926 if not is_openbsd(): 5927 self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo >= 1.0', 'libhello'])) 5928 else: 5929 self.assertEqual(sorted(out), sorted(['libexposed', 'libfoo>=1.0', 'libhello'])) 5930 5931 cmd = ['pkg-config', 'pub-lib-order'] 5932 out = self._run(cmd + ['--libs'], override_envvars=env).strip().split() 5933 self.assertEqual(out, ['-llibmain2', '-llibinternal']) 5934 5935 def test_pkgconfig_uninstalled(self): 5936 testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen') 5937 self.init(testdir) 5938 self.build() 5939 5940 os.environ['PKG_CONFIG_LIBDIR'] = os.path.join(self.builddir, 'meson-uninstalled') 5941 if is_cygwin(): 5942 os.environ['PATH'] += os.pathsep + self.builddir 5943 5944 self.new_builddir() 5945 testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen', 'dependencies') 5946 self.init(testdir) 5947 self.build() 5948 self.run_tests() 5949 5950 def test_pkg_unfound(self): 5951 testdir = os.path.join(self.unit_test_dir, '23 unfound pkgconfig') 5952 self.init(testdir) 5953 with open(os.path.join(self.privatedir, 'somename.pc')) as f: 5954 pcfile = f.read() 5955 self.assertFalse('blub_blob_blib' in pcfile) 5956 5957 def test_vala_c_warnings(self): 5958 ''' 5959 Test that no warnings are emitted for C code generated by Vala. This 5960 can't be an ordinary test case because we need to inspect the compiler 5961 database. 5962 https://github.com/mesonbuild/meson/issues/864 5963 ''' 5964 if not shutil.which('valac'): 5965 raise unittest.SkipTest('valac not installed.') 5966 testdir = os.path.join(self.vala_test_dir, '5 target glib') 5967 self.init(testdir) 5968 compdb = self.get_compdb() 5969 vala_command = None 5970 c_command = None 5971 for each in compdb: 5972 if each['file'].endswith('GLib.Thread.c'): 5973 vala_command = each['command'] 5974 elif each['file'].endswith('GLib.Thread.vala'): 5975 continue 5976 elif each['file'].endswith('retcode.c'): 5977 c_command = each['command'] 5978 else: 5979 m = 'Unknown file {!r} in vala_c_warnings test'.format(each['file']) 5980 raise AssertionError(m) 5981 self.assertIsNotNone(vala_command) 5982 self.assertIsNotNone(c_command) 5983 # -w suppresses all warnings, should be there in Vala but not in C 5984 self.assertIn(" -w ", vala_command) 5985 self.assertNotIn(" -w ", c_command) 5986 # -Wall enables all warnings, should be there in C but not in Vala 5987 self.assertNotIn(" -Wall ", vala_command) 5988 self.assertIn(" -Wall ", c_command) 5989 # -Werror converts warnings to errors, should always be there since it's 5990 # injected by an unrelated piece of code and the project has werror=true 5991 self.assertIn(" -Werror ", vala_command) 5992 self.assertIn(" -Werror ", c_command) 5993 5994 @skipIfNoPkgconfig 5995 def test_qtdependency_pkgconfig_detection(self): 5996 ''' 5997 Test that qt4 and qt5 detection with pkgconfig works. 5998 ''' 5999 # Verify Qt4 or Qt5 can be found with pkg-config 6000 qt4 = subprocess.call(['pkg-config', '--exists', 'QtCore']) 6001 qt5 = subprocess.call(['pkg-config', '--exists', 'Qt5Core']) 6002 testdir = os.path.join(self.framework_test_dir, '4 qt') 6003 self.init(testdir, extra_args=['-Dmethod=pkg-config']) 6004 # Confirm that the dependency was found with pkg-config 6005 mesonlog = self.get_meson_log() 6006 if qt4 == 0: 6007 self.assertRegex('\n'.join(mesonlog), 6008 r'Run-time dependency qt4 \(modules: Core\) found: YES 4.* \(pkg-config\)\n') 6009 if qt5 == 0: 6010 self.assertRegex('\n'.join(mesonlog), 6011 r'Run-time dependency qt5 \(modules: Core\) found: YES 5.* \(pkg-config\)\n') 6012 6013 @skip_if_not_base_option('b_sanitize') 6014 def test_generate_gir_with_address_sanitizer(self): 6015 if is_cygwin(): 6016 raise unittest.SkipTest('asan not available on Cygwin') 6017 if is_openbsd(): 6018 raise unittest.SkipTest('-fsanitize=address is not supported on OpenBSD') 6019 6020 testdir = os.path.join(self.framework_test_dir, '7 gnome') 6021 self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false']) 6022 self.build() 6023 6024 def test_qt5dependency_qmake_detection(self): 6025 ''' 6026 Test that qt5 detection with qmake works. This can't be an ordinary 6027 test case because it involves setting the environment. 6028 ''' 6029 # Verify that qmake is for Qt5 6030 if not shutil.which('qmake-qt5'): 6031 if not shutil.which('qmake'): 6032 raise unittest.SkipTest('QMake not found') 6033 output = subprocess.getoutput('qmake --version') 6034 if 'Qt version 5' not in output: 6035 raise unittest.SkipTest('Qmake found, but it is not for Qt 5.') 6036 # Disable pkg-config codepath and force searching with qmake/qmake-qt5 6037 testdir = os.path.join(self.framework_test_dir, '4 qt') 6038 self.init(testdir, extra_args=['-Dmethod=qmake']) 6039 # Confirm that the dependency was found with qmake 6040 mesonlog = self.get_meson_log() 6041 self.assertRegex('\n'.join(mesonlog), 6042 r'Run-time dependency qt5 \(modules: Core\) found: YES .* \((qmake|qmake-qt5)\)\n') 6043 6044 def glob_sofiles_without_privdir(self, g): 6045 files = glob(g) 6046 return [f for f in files if not f.endswith('.p')] 6047 6048 def _test_soname_impl(self, libpath, install): 6049 if is_cygwin() or is_osx(): 6050 raise unittest.SkipTest('Test only applicable to ELF and linuxlike sonames') 6051 6052 testdir = os.path.join(self.unit_test_dir, '1 soname') 6053 self.init(testdir) 6054 self.build() 6055 if install: 6056 self.install() 6057 6058 # File without aliases set. 6059 nover = os.path.join(libpath, 'libnover.so') 6060 self.assertPathExists(nover) 6061 self.assertFalse(os.path.islink(nover)) 6062 self.assertEqual(get_soname(nover), 'libnover.so') 6063 self.assertEqual(len(self.glob_sofiles_without_privdir(nover[:-3] + '*')), 1) 6064 6065 # File with version set 6066 verset = os.path.join(libpath, 'libverset.so') 6067 self.assertPathExists(verset + '.4.5.6') 6068 self.assertEqual(os.readlink(verset), 'libverset.so.4') 6069 self.assertEqual(get_soname(verset), 'libverset.so.4') 6070 self.assertEqual(len(self.glob_sofiles_without_privdir(verset[:-3] + '*')), 3) 6071 6072 # File with soversion set 6073 soverset = os.path.join(libpath, 'libsoverset.so') 6074 self.assertPathExists(soverset + '.1.2.3') 6075 self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3') 6076 self.assertEqual(get_soname(soverset), 'libsoverset.so.1.2.3') 6077 self.assertEqual(len(self.glob_sofiles_without_privdir(soverset[:-3] + '*')), 2) 6078 6079 # File with version and soversion set to same values 6080 settosame = os.path.join(libpath, 'libsettosame.so') 6081 self.assertPathExists(settosame + '.7.8.9') 6082 self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9') 6083 self.assertEqual(get_soname(settosame), 'libsettosame.so.7.8.9') 6084 self.assertEqual(len(self.glob_sofiles_without_privdir(settosame[:-3] + '*')), 2) 6085 6086 # File with version and soversion set to different values 6087 bothset = os.path.join(libpath, 'libbothset.so') 6088 self.assertPathExists(bothset + '.1.2.3') 6089 self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3') 6090 self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6') 6091 self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3') 6092 self.assertEqual(len(self.glob_sofiles_without_privdir(bothset[:-3] + '*')), 3) 6093 6094 def test_soname(self): 6095 self._test_soname_impl(self.builddir, False) 6096 6097 def test_installed_soname(self): 6098 libdir = self.installdir + os.path.join(self.prefix, self.libdir) 6099 self._test_soname_impl(libdir, True) 6100 6101 def test_compiler_check_flags_order(self): 6102 ''' 6103 Test that compiler check flags override all other flags. This can't be 6104 an ordinary test case because it needs the environment to be set. 6105 ''' 6106 testdir = os.path.join(self.common_test_dir, '39 has function') 6107 env = get_fake_env(testdir, self.builddir, self.prefix) 6108 cpp = env.detect_cpp_compiler(MachineChoice.HOST) 6109 Oflag = '-O3' 6110 OflagCPP = Oflag 6111 if cpp.get_id() in ('clang', 'gcc'): 6112 # prevent developers from adding "int main(int argc, char **argv)" 6113 # to small Meson checks unless these parameters are actually used 6114 OflagCPP += ' -Werror=unused-parameter' 6115 env = {'CFLAGS': Oflag, 6116 'CXXFLAGS': OflagCPP} 6117 self.init(testdir, override_envvars=env) 6118 cmds = self.get_meson_log_compiler_checks() 6119 for cmd in cmds: 6120 if cmd[0] == 'ccache': 6121 cmd = cmd[1:] 6122 # Verify that -I flags from the `args` kwarg are first 6123 # This is set in the '39 has function' test case 6124 self.assertEqual(cmd[1], '-I/tmp') 6125 # Verify that -O3 set via the environment is overridden by -O0 6126 Oargs = [arg for arg in cmd if arg.startswith('-O')] 6127 self.assertEqual(Oargs, [Oflag, '-O0']) 6128 6129 def _test_stds_impl(self, testdir, compiler, p: str): 6130 has_cpp17 = (compiler.get_id() not in {'clang', 'gcc'} or 6131 compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=5.0.0', '>=9.1') or 6132 compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=5.0.0')) 6133 has_cpp2a_c17 = (compiler.get_id() not in {'clang', 'gcc'} or 6134 compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=6.0.0', '>=10.0') or 6135 compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0')) 6136 has_c18 = (compiler.get_id() not in {'clang', 'gcc'} or 6137 compiler.get_id() == 'clang' and _clang_at_least(compiler, '>=8.0.0', '>=11.0') or 6138 compiler.get_id() == 'gcc' and version_compare(compiler.version, '>=8.0.0')) 6139 # Check that all the listed -std=xxx options for this compiler work just fine when used 6140 # https://en.wikipedia.org/wiki/Xcode#Latest_versions 6141 # https://www.gnu.org/software/gcc/projects/cxx-status.html 6142 for v in compiler.get_options()['std'].choices: 6143 lang_std = p + '_std' 6144 # we do it like this to handle gnu++17,c++17 and gnu17,c17 cleanly 6145 # thus, C++ first 6146 if '++17' in v and not has_cpp17: 6147 continue 6148 elif '++2a' in v and not has_cpp2a_c17: # https://en.cppreference.com/w/cpp/compiler_support 6149 continue 6150 # now C 6151 elif '17' in v and not has_cpp2a_c17: 6152 continue 6153 elif '18' in v and not has_c18: 6154 continue 6155 std_opt = '{}={}'.format(lang_std, v) 6156 self.init(testdir, extra_args=['-D' + std_opt]) 6157 cmd = self.get_compdb()[0]['command'] 6158 # c++03 and gnu++03 are not understood by ICC, don't try to look for them 6159 skiplist = frozenset([ 6160 ('intel', 'c++03'), 6161 ('intel', 'gnu++03')]) 6162 if v != 'none' and not (compiler.get_id(), v) in skiplist: 6163 cmd_std = " -std={} ".format(v) 6164 self.assertIn(cmd_std, cmd) 6165 try: 6166 self.build() 6167 except Exception: 6168 print('{} was {!r}'.format(lang_std, v)) 6169 raise 6170 self.wipe() 6171 # Check that an invalid std option in CFLAGS/CPPFLAGS fails 6172 # Needed because by default ICC ignores invalid options 6173 cmd_std = '-std=FAIL' 6174 if p == 'c': 6175 env_flag_name = 'CFLAGS' 6176 elif p == 'cpp': 6177 env_flag_name = 'CXXFLAGS' 6178 else: 6179 raise NotImplementedError('Language {} not defined.'.format(p)) 6180 env = {} 6181 env[env_flag_name] = cmd_std 6182 with self.assertRaises((subprocess.CalledProcessError, mesonbuild.mesonlib.EnvironmentException), 6183 msg='C compiler should have failed with -std=FAIL'): 6184 self.init(testdir, override_envvars = env) 6185 # ICC won't fail in the above because additional flags are needed to 6186 # make unknown -std=... options errors. 6187 self.build() 6188 6189 def test_compiler_c_stds(self): 6190 ''' 6191 Test that C stds specified for this compiler can all be used. Can't be 6192 an ordinary test because it requires passing options to meson. 6193 ''' 6194 testdir = os.path.join(self.common_test_dir, '1 trivial') 6195 env = get_fake_env(testdir, self.builddir, self.prefix) 6196 cc = env.detect_c_compiler(MachineChoice.HOST) 6197 self._test_stds_impl(testdir, cc, 'c') 6198 6199 def test_compiler_cpp_stds(self): 6200 ''' 6201 Test that C++ stds specified for this compiler can all be used. Can't 6202 be an ordinary test because it requires passing options to meson. 6203 ''' 6204 testdir = os.path.join(self.common_test_dir, '2 cpp') 6205 env = get_fake_env(testdir, self.builddir, self.prefix) 6206 cpp = env.detect_cpp_compiler(MachineChoice.HOST) 6207 self._test_stds_impl(testdir, cpp, 'cpp') 6208 6209 def test_unity_subproj(self): 6210 testdir = os.path.join(self.common_test_dir, '45 subproject') 6211 self.init(testdir, extra_args='--unity=subprojects') 6212 pdirs = glob(os.path.join(self.builddir, 'subprojects/sublib/simpletest*.p')) 6213 self.assertEqual(len(pdirs), 1) 6214 self.assertPathExists(os.path.join(pdirs[0], 'simpletest-unity0.c')) 6215 sdirs = glob(os.path.join(self.builddir, 'subprojects/sublib/*sublib*.p')) 6216 self.assertEqual(len(sdirs), 1) 6217 self.assertPathExists(os.path.join(sdirs[0], 'sublib-unity0.c')) 6218 self.assertPathDoesNotExist(os.path.join(self.builddir, 'user@exe/user-unity.c')) 6219 self.build() 6220 6221 def test_installed_modes(self): 6222 ''' 6223 Test that files installed by these tests have the correct permissions. 6224 Can't be an ordinary test because our installed_files.txt is very basic. 6225 ''' 6226 # Test file modes 6227 testdir = os.path.join(self.common_test_dir, '12 data') 6228 self.init(testdir) 6229 self.install() 6230 6231 f = os.path.join(self.installdir, 'etc', 'etcfile.dat') 6232 found_mode = stat.filemode(os.stat(f).st_mode) 6233 want_mode = 'rw------T' 6234 self.assertEqual(want_mode, found_mode[1:]) 6235 6236 f = os.path.join(self.installdir, 'usr', 'bin', 'runscript.sh') 6237 statf = os.stat(f) 6238 found_mode = stat.filemode(statf.st_mode) 6239 want_mode = 'rwxr-sr-x' 6240 self.assertEqual(want_mode, found_mode[1:]) 6241 if os.getuid() == 0: 6242 # The chown failed nonfatally if we're not root 6243 self.assertEqual(0, statf.st_uid) 6244 self.assertEqual(0, statf.st_gid) 6245 6246 f = os.path.join(self.installdir, 'usr', 'share', 'progname', 6247 'fileobject_datafile.dat') 6248 orig = os.path.join(testdir, 'fileobject_datafile.dat') 6249 statf = os.stat(f) 6250 statorig = os.stat(orig) 6251 found_mode = stat.filemode(statf.st_mode) 6252 orig_mode = stat.filemode(statorig.st_mode) 6253 self.assertEqual(orig_mode[1:], found_mode[1:]) 6254 self.assertEqual(os.getuid(), statf.st_uid) 6255 if os.getuid() == 0: 6256 # The chown failed nonfatally if we're not root 6257 self.assertEqual(0, statf.st_gid) 6258 6259 self.wipe() 6260 # Test directory modes 6261 testdir = os.path.join(self.common_test_dir, '62 install subdir') 6262 self.init(testdir) 6263 self.install() 6264 6265 f = os.path.join(self.installdir, 'usr', 'share', 'sub1', 'second.dat') 6266 statf = os.stat(f) 6267 found_mode = stat.filemode(statf.st_mode) 6268 want_mode = 'rwxr-x--t' 6269 self.assertEqual(want_mode, found_mode[1:]) 6270 if os.getuid() == 0: 6271 # The chown failed nonfatally if we're not root 6272 self.assertEqual(0, statf.st_uid) 6273 6274 def test_installed_modes_extended(self): 6275 ''' 6276 Test that files are installed with correct permissions using install_mode. 6277 ''' 6278 testdir = os.path.join(self.common_test_dir, '195 install_mode') 6279 self.init(testdir) 6280 self.build() 6281 self.install() 6282 6283 for fsobj, want_mode in [ 6284 ('bin', 'drwxr-x---'), 6285 ('bin/runscript.sh', '-rwxr-sr-x'), 6286 ('bin/trivialprog', '-rwxr-sr-x'), 6287 ('include', 'drwxr-x---'), 6288 ('include/config.h', '-rw-rwSr--'), 6289 ('include/rootdir.h', '-r--r--r-T'), 6290 ('lib', 'drwxr-x---'), 6291 ('lib/libstat.a', '-rw---Sr--'), 6292 ('share', 'drwxr-x---'), 6293 ('share/man', 'drwxr-x---'), 6294 ('share/man/man1', 'drwxr-x---'), 6295 ('share/man/man1/foo.1', '-r--r--r-T'), 6296 ('share/sub1', 'drwxr-x---'), 6297 ('share/sub1/second.dat', '-rwxr-x--t'), 6298 ('subdir', 'drwxr-x---'), 6299 ('subdir/data.dat', '-rw-rwSr--'), 6300 ]: 6301 f = os.path.join(self.installdir, 'usr', *fsobj.split('/')) 6302 found_mode = stat.filemode(os.stat(f).st_mode) 6303 self.assertEqual(want_mode, found_mode, 6304 msg=('Expected file %s to have mode %s but found %s instead.' % 6305 (fsobj, want_mode, found_mode))) 6306 # Ensure that introspect --installed works on all types of files 6307 # FIXME: also verify the files list 6308 self.introspect('--installed') 6309 6310 def test_install_umask(self): 6311 ''' 6312 Test that files are installed with correct permissions using default 6313 install umask of 022, regardless of the umask at time the worktree 6314 was checked out or the build was executed. 6315 ''' 6316 # Copy source tree to a temporary directory and change permissions 6317 # there to simulate a checkout with umask 002. 6318 orig_testdir = os.path.join(self.unit_test_dir, '26 install umask') 6319 # Create a new testdir under tmpdir. 6320 tmpdir = os.path.realpath(tempfile.mkdtemp()) 6321 self.addCleanup(windows_proof_rmtree, tmpdir) 6322 testdir = os.path.join(tmpdir, '26 install umask') 6323 # Copy the tree using shutil.copyfile, which will use the current umask 6324 # instead of preserving permissions of the old tree. 6325 save_umask = os.umask(0o002) 6326 self.addCleanup(os.umask, save_umask) 6327 shutil.copytree(orig_testdir, testdir, copy_function=shutil.copyfile) 6328 # Preserve the executable status of subdir/sayhello though. 6329 os.chmod(os.path.join(testdir, 'subdir', 'sayhello'), 0o775) 6330 self.init(testdir) 6331 # Run the build under a 027 umask now. 6332 os.umask(0o027) 6333 self.build() 6334 # And keep umask 027 for the install step too. 6335 self.install() 6336 6337 for executable in [ 6338 'bin/prog', 6339 'share/subdir/sayhello', 6340 ]: 6341 f = os.path.join(self.installdir, 'usr', *executable.split('/')) 6342 found_mode = stat.filemode(os.stat(f).st_mode) 6343 want_mode = '-rwxr-xr-x' 6344 self.assertEqual(want_mode, found_mode, 6345 msg=('Expected file %s to have mode %s but found %s instead.' % 6346 (executable, want_mode, found_mode))) 6347 6348 for directory in [ 6349 'usr', 6350 'usr/bin', 6351 'usr/include', 6352 'usr/share', 6353 'usr/share/man', 6354 'usr/share/man/man1', 6355 'usr/share/subdir', 6356 ]: 6357 f = os.path.join(self.installdir, *directory.split('/')) 6358 found_mode = stat.filemode(os.stat(f).st_mode) 6359 want_mode = 'drwxr-xr-x' 6360 self.assertEqual(want_mode, found_mode, 6361 msg=('Expected directory %s to have mode %s but found %s instead.' % 6362 (directory, want_mode, found_mode))) 6363 6364 for datafile in [ 6365 'include/sample.h', 6366 'share/datafile.cat', 6367 'share/file.dat', 6368 'share/man/man1/prog.1', 6369 'share/subdir/datafile.dog', 6370 ]: 6371 f = os.path.join(self.installdir, 'usr', *datafile.split('/')) 6372 found_mode = stat.filemode(os.stat(f).st_mode) 6373 want_mode = '-rw-r--r--' 6374 self.assertEqual(want_mode, found_mode, 6375 msg=('Expected file %s to have mode %s but found %s instead.' % 6376 (datafile, want_mode, found_mode))) 6377 6378 def test_cpp_std_override(self): 6379 testdir = os.path.join(self.unit_test_dir, '6 std override') 6380 self.init(testdir) 6381 compdb = self.get_compdb() 6382 # Don't try to use -std=c++03 as a check for the 6383 # presence of a compiler flag, as ICC does not 6384 # support it. 6385 for i in compdb: 6386 if 'prog98' in i['file']: 6387 c98_comp = i['command'] 6388 if 'prog11' in i['file']: 6389 c11_comp = i['command'] 6390 if 'progp' in i['file']: 6391 plain_comp = i['command'] 6392 self.assertNotEqual(len(plain_comp), 0) 6393 self.assertIn('-std=c++98', c98_comp) 6394 self.assertNotIn('-std=c++11', c98_comp) 6395 self.assertIn('-std=c++11', c11_comp) 6396 self.assertNotIn('-std=c++98', c11_comp) 6397 self.assertNotIn('-std=c++98', plain_comp) 6398 self.assertNotIn('-std=c++11', plain_comp) 6399 # Now werror 6400 self.assertIn('-Werror', plain_comp) 6401 self.assertNotIn('-Werror', c98_comp) 6402 6403 def test_run_installed(self): 6404 if is_cygwin() or is_osx(): 6405 raise unittest.SkipTest('LD_LIBRARY_PATH and RPATH not applicable') 6406 6407 testdir = os.path.join(self.unit_test_dir, '7 run installed') 6408 self.init(testdir) 6409 self.build() 6410 self.install() 6411 installed_exe = os.path.join(self.installdir, 'usr/bin/prog') 6412 installed_libdir = os.path.join(self.installdir, 'usr/foo') 6413 installed_lib = os.path.join(installed_libdir, 'libfoo.so') 6414 self.assertTrue(os.path.isfile(installed_exe)) 6415 self.assertTrue(os.path.isdir(installed_libdir)) 6416 self.assertTrue(os.path.isfile(installed_lib)) 6417 # Must fail when run without LD_LIBRARY_PATH to ensure that 6418 # rpath has been properly stripped rather than pointing to the builddir. 6419 self.assertNotEqual(subprocess.call(installed_exe, stderr=subprocess.DEVNULL), 0) 6420 # When LD_LIBRARY_PATH is set it should start working. 6421 # For some reason setting LD_LIBRARY_PATH in os.environ fails 6422 # when all tests are run (but works when only this test is run), 6423 # but doing this explicitly works. 6424 env = os.environ.copy() 6425 env['LD_LIBRARY_PATH'] = ':'.join([installed_libdir, env.get('LD_LIBRARY_PATH', '')]) 6426 self.assertEqual(subprocess.call(installed_exe, env=env), 0) 6427 # Ensure that introspect --installed works 6428 installed = self.introspect('--installed') 6429 for v in installed.values(): 6430 self.assertTrue('prog' in v or 'foo' in v) 6431 6432 @skipIfNoPkgconfig 6433 def test_order_of_l_arguments(self): 6434 testdir = os.path.join(self.unit_test_dir, '8 -L -l order') 6435 self.init(testdir, override_envvars={'PKG_CONFIG_PATH': testdir}) 6436 # NOTE: .pc file has -Lfoo -lfoo -Lbar -lbar but pkg-config reorders 6437 # the flags before returning them to -Lfoo -Lbar -lfoo -lbar 6438 # but pkgconf seems to not do that. Sigh. Support both. 6439 expected_order = [('-L/me/first', '-lfoo1'), 6440 ('-L/me/second', '-lfoo2'), 6441 ('-L/me/first', '-L/me/second'), 6442 ('-lfoo1', '-lfoo2'), 6443 ('-L/me/second', '-L/me/third'), 6444 ('-L/me/third', '-L/me/fourth',), 6445 ('-L/me/third', '-lfoo3'), 6446 ('-L/me/fourth', '-lfoo4'), 6447 ('-lfoo3', '-lfoo4'), 6448 ] 6449 with open(os.path.join(self.builddir, 'build.ninja')) as ifile: 6450 for line in ifile: 6451 if expected_order[0][0] in line: 6452 for first, second in expected_order: 6453 self.assertLess(line.index(first), line.index(second)) 6454 return 6455 raise RuntimeError('Linker entries not found in the Ninja file.') 6456 6457 def test_introspect_dependencies(self): 6458 ''' 6459 Tests that mesonintrospect --dependencies returns expected output. 6460 ''' 6461 testdir = os.path.join(self.framework_test_dir, '7 gnome') 6462 self.init(testdir) 6463 glib_found = False 6464 gobject_found = False 6465 deps = self.introspect('--dependencies') 6466 self.assertIsInstance(deps, list) 6467 for dep in deps: 6468 self.assertIsInstance(dep, dict) 6469 self.assertIn('name', dep) 6470 self.assertIn('compile_args', dep) 6471 self.assertIn('link_args', dep) 6472 if dep['name'] == 'glib-2.0': 6473 glib_found = True 6474 elif dep['name'] == 'gobject-2.0': 6475 gobject_found = True 6476 self.assertTrue(glib_found) 6477 self.assertTrue(gobject_found) 6478 if subprocess.call(['pkg-config', '--exists', 'glib-2.0 >= 2.56.2']) != 0: 6479 raise unittest.SkipTest('glib >= 2.56.2 needed for the rest') 6480 targets = self.introspect('--targets') 6481 docbook_target = None 6482 for t in targets: 6483 if t['name'] == 'generated-gdbus-docbook': 6484 docbook_target = t 6485 break 6486 self.assertIsInstance(docbook_target, dict) 6487 self.assertEqual(os.path.basename(t['filename'][0]), 'generated-gdbus-doc-' + os.path.basename(t['target_sources'][0]['sources'][0])) 6488 6489 def test_introspect_installed(self): 6490 testdir = os.path.join(self.linuxlike_test_dir, '7 library versions') 6491 self.init(testdir) 6492 6493 install = self.introspect('--installed') 6494 install = {os.path.basename(k): v for k, v in install.items()} 6495 print(install) 6496 if is_osx(): 6497 the_truth = { 6498 'libmodule.dylib': '/usr/lib/libmodule.dylib', 6499 'libnoversion.dylib': '/usr/lib/libnoversion.dylib', 6500 'libonlysoversion.5.dylib': '/usr/lib/libonlysoversion.5.dylib', 6501 'libonlysoversion.dylib': '/usr/lib/libonlysoversion.dylib', 6502 'libonlyversion.1.dylib': '/usr/lib/libonlyversion.1.dylib', 6503 'libonlyversion.dylib': '/usr/lib/libonlyversion.dylib', 6504 'libsome.0.dylib': '/usr/lib/libsome.0.dylib', 6505 'libsome.dylib': '/usr/lib/libsome.dylib', 6506 } 6507 the_truth_2 = {'/usr/lib/libsome.dylib', 6508 '/usr/lib/libsome.0.dylib', 6509 } 6510 else: 6511 the_truth = { 6512 'libmodule.so': '/usr/lib/libmodule.so', 6513 'libnoversion.so': '/usr/lib/libnoversion.so', 6514 'libonlysoversion.so': '/usr/lib/libonlysoversion.so', 6515 'libonlysoversion.so.5': '/usr/lib/libonlysoversion.so.5', 6516 'libonlyversion.so': '/usr/lib/libonlyversion.so', 6517 'libonlyversion.so.1': '/usr/lib/libonlyversion.so.1', 6518 'libonlyversion.so.1.4.5': '/usr/lib/libonlyversion.so.1.4.5', 6519 'libsome.so': '/usr/lib/libsome.so', 6520 'libsome.so.0': '/usr/lib/libsome.so.0', 6521 'libsome.so.1.2.3': '/usr/lib/libsome.so.1.2.3', 6522 } 6523 the_truth_2 = {'/usr/lib/libsome.so', 6524 '/usr/lib/libsome.so.0', 6525 '/usr/lib/libsome.so.1.2.3'} 6526 self.assertDictEqual(install, the_truth) 6527 6528 targets = self.introspect('--targets') 6529 for t in targets: 6530 if t['name'] != 'some': 6531 continue 6532 self.assertSetEqual(the_truth_2, set(t['install_filename'])) 6533 6534 def test_build_rpath(self): 6535 if is_cygwin(): 6536 raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH') 6537 testdir = os.path.join(self.unit_test_dir, '10 build_rpath') 6538 self.init(testdir) 6539 self.build() 6540 # C program RPATH 6541 build_rpath = get_rpath(os.path.join(self.builddir, 'prog')) 6542 self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar') 6543 self.install() 6544 install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/prog')) 6545 self.assertEqual(install_rpath, '/baz') 6546 # C++ program RPATH 6547 build_rpath = get_rpath(os.path.join(self.builddir, 'progcxx')) 6548 self.assertEqual(build_rpath, '$ORIGIN/sub:/foo/bar') 6549 self.install() 6550 install_rpath = get_rpath(os.path.join(self.installdir, 'usr/bin/progcxx')) 6551 self.assertEqual(install_rpath, 'baz') 6552 6553 def test_global_rpath(self): 6554 if is_cygwin(): 6555 raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH') 6556 if is_osx(): 6557 raise unittest.SkipTest('Global RPATHs via LDFLAGS not yet supported on MacOS (does anybody need it?)') 6558 6559 testdir = os.path.join(self.unit_test_dir, '77 global-rpath') 6560 oldinstalldir = self.installdir 6561 6562 # Build and install an external library without DESTDIR. 6563 # The external library generates a .pc file without an rpath. 6564 yonder_dir = os.path.join(testdir, 'yonder') 6565 yonder_prefix = os.path.join(oldinstalldir, 'yonder') 6566 yonder_libdir = os.path.join(yonder_prefix, self.libdir) 6567 self.prefix = yonder_prefix 6568 self.installdir = yonder_prefix 6569 self.init(yonder_dir) 6570 self.build() 6571 self.install(use_destdir=False) 6572 6573 # Since rpath has multiple valid formats we need to 6574 # test that they are all properly used. 6575 rpath_formats = [ 6576 ('-Wl,-rpath=', False), 6577 ('-Wl,-rpath,', False), 6578 ('-Wl,--just-symbols=', True), 6579 ('-Wl,--just-symbols,', True), 6580 ('-Wl,-R', False), 6581 ('-Wl,-R,', False) 6582 ] 6583 for rpath_format, exception in rpath_formats: 6584 # Build an app that uses that installed library. 6585 # Supply the rpath to the installed library via LDFLAGS 6586 # (as systems like buildroot and guix are wont to do) 6587 # and verify install preserves that rpath. 6588 self.new_builddir() 6589 env = {'LDFLAGS': rpath_format + yonder_libdir, 6590 'PKG_CONFIG_PATH': os.path.join(yonder_libdir, 'pkgconfig')} 6591 if exception: 6592 with self.assertRaises(subprocess.CalledProcessError): 6593 self.init(testdir, override_envvars=env) 6594 break 6595 self.init(testdir, override_envvars=env) 6596 self.build() 6597 self.install(use_destdir=False) 6598 got_rpath = get_rpath(os.path.join(yonder_prefix, 'bin/rpathified')) 6599 self.assertEqual(got_rpath, yonder_libdir, rpath_format) 6600 6601 @skip_if_not_base_option('b_sanitize') 6602 def test_pch_with_address_sanitizer(self): 6603 if is_cygwin(): 6604 raise unittest.SkipTest('asan not available on Cygwin') 6605 if is_openbsd(): 6606 raise unittest.SkipTest('-fsanitize=address is not supported on OpenBSD') 6607 6608 testdir = os.path.join(self.common_test_dir, '13 pch') 6609 self.init(testdir, extra_args=['-Db_sanitize=address', '-Db_lundef=false']) 6610 self.build() 6611 compdb = self.get_compdb() 6612 for i in compdb: 6613 self.assertIn("-fsanitize=address", i["command"]) 6614 6615 def test_cross_find_program(self): 6616 testdir = os.path.join(self.unit_test_dir, '11 cross prog') 6617 crossfile = tempfile.NamedTemporaryFile(mode='w') 6618 print(os.path.join(testdir, 'some_cross_tool.py')) 6619 crossfile.write(textwrap.dedent('''\ 6620 [binaries] 6621 c = '/usr/bin/{1}' 6622 ar = '/usr/bin/ar' 6623 strip = '/usr/bin/ar' 6624 sometool.py = ['{0}'] 6625 someothertool.py = '{0}' 6626 6627 [properties] 6628 6629 [host_machine] 6630 system = 'linux' 6631 cpu_family = 'arm' 6632 cpu = 'armv7' # Not sure if correct. 6633 endian = 'little' 6634 ''').format(os.path.join(testdir, 'some_cross_tool.py'), 6635 'gcc' if is_sunos() else 'cc')) 6636 crossfile.flush() 6637 self.meson_cross_file = crossfile.name 6638 self.init(testdir) 6639 6640 def test_reconfigure(self): 6641 testdir = os.path.join(self.unit_test_dir, '13 reconfigure') 6642 self.init(testdir, extra_args=['-Db_coverage=true'], default_args=False) 6643 self.build('reconfigure') 6644 6645 def test_vala_generated_source_buildir_inside_source_tree(self): 6646 ''' 6647 Test that valac outputs generated C files in the expected location when 6648 the builddir is a subdir of the source tree. 6649 ''' 6650 if not shutil.which('valac'): 6651 raise unittest.SkipTest('valac not installed.') 6652 6653 testdir = os.path.join(self.vala_test_dir, '8 generated sources') 6654 newdir = os.path.join(self.builddir, 'srctree') 6655 shutil.copytree(testdir, newdir) 6656 testdir = newdir 6657 # New builddir 6658 builddir = os.path.join(testdir, 'subdir/_build') 6659 os.makedirs(builddir, exist_ok=True) 6660 self.change_builddir(builddir) 6661 self.init(testdir) 6662 self.build() 6663 6664 def test_old_gnome_module_codepaths(self): 6665 ''' 6666 A lot of code in the GNOME module is conditional on the version of the 6667 glib tools that are installed, and breakages in the old code can slip 6668 by once the CI has a newer glib version. So we force the GNOME module 6669 to pretend that it's running on an ancient glib so the fallback code is 6670 also tested. 6671 ''' 6672 testdir = os.path.join(self.framework_test_dir, '7 gnome') 6673 mesonbuild.modules.gnome.native_glib_version = '2.20' 6674 env = {'MESON_UNIT_TEST_PRETEND_GLIB_OLD': "1"} 6675 try: 6676 self.init(testdir, 6677 inprocess=True, 6678 override_envvars=env) 6679 self.build(override_envvars=env) 6680 finally: 6681 mesonbuild.modules.gnome.native_glib_version = None 6682 6683 @skipIfNoPkgconfig 6684 def test_pkgconfig_usage(self): 6685 testdir1 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependency') 6686 testdir2 = os.path.join(self.unit_test_dir, '27 pkgconfig usage/dependee') 6687 if subprocess.call(['pkg-config', '--cflags', 'glib-2.0'], 6688 stdout=subprocess.DEVNULL, 6689 stderr=subprocess.DEVNULL) != 0: 6690 raise unittest.SkipTest('Glib 2.0 dependency not available.') 6691 with tempfile.TemporaryDirectory() as tempdirname: 6692 self.init(testdir1, extra_args=['--prefix=' + tempdirname, '--libdir=lib'], default_args=False) 6693 self.install(use_destdir=False) 6694 shutil.rmtree(self.builddir) 6695 os.mkdir(self.builddir) 6696 pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') 6697 self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'libpkgdep.pc'))) 6698 lib_dir = os.path.join(tempdirname, 'lib') 6699 myenv = os.environ.copy() 6700 myenv['PKG_CONFIG_PATH'] = pkg_dir 6701 # Private internal libraries must not leak out. 6702 pkg_out = subprocess.check_output(['pkg-config', '--static', '--libs', 'libpkgdep'], env=myenv) 6703 self.assertFalse(b'libpkgdep-int' in pkg_out, 'Internal library leaked out.') 6704 # Dependencies must not leak to cflags when building only a shared library. 6705 pkg_out = subprocess.check_output(['pkg-config', '--cflags', 'libpkgdep'], env=myenv) 6706 self.assertFalse(b'glib' in pkg_out, 'Internal dependency leaked to headers.') 6707 # Test that the result is usable. 6708 self.init(testdir2, override_envvars=myenv) 6709 self.build(override_envvars=myenv) 6710 myenv = os.environ.copy() 6711 myenv['LD_LIBRARY_PATH'] = ':'.join([lib_dir, myenv.get('LD_LIBRARY_PATH', '')]) 6712 if is_cygwin(): 6713 bin_dir = os.path.join(tempdirname, 'bin') 6714 myenv['PATH'] = bin_dir + os.pathsep + myenv['PATH'] 6715 self.assertTrue(os.path.isdir(lib_dir)) 6716 test_exe = os.path.join(self.builddir, 'pkguser') 6717 self.assertTrue(os.path.isfile(test_exe)) 6718 subprocess.check_call(test_exe, env=myenv) 6719 6720 @skipIfNoPkgconfig 6721 def test_pkgconfig_relative_paths(self): 6722 testdir = os.path.join(self.unit_test_dir, '62 pkgconfig relative paths') 6723 pkg_dir = os.path.join(testdir, 'pkgconfig') 6724 self.assertTrue(os.path.exists(os.path.join(pkg_dir, 'librelativepath.pc'))) 6725 6726 env = get_fake_env(testdir, self.builddir, self.prefix) 6727 env.coredata.set_options({'pkg_config_path': pkg_dir}, subproject='') 6728 kwargs = {'required': True, 'silent': True} 6729 relative_path_dep = PkgConfigDependency('librelativepath', env, kwargs) 6730 self.assertTrue(relative_path_dep.found()) 6731 6732 # Ensure link_args are properly quoted 6733 libpath = Path(self.builddir) / '../relativepath/lib' 6734 link_args = ['-L' + libpath.as_posix(), '-lrelativepath'] 6735 self.assertEqual(relative_path_dep.get_link_args(), link_args) 6736 6737 @skipIfNoPkgconfig 6738 def test_pkgconfig_internal_libraries(self): 6739 ''' 6740 ''' 6741 with tempfile.TemporaryDirectory() as tempdirname: 6742 # build library 6743 testdirbase = os.path.join(self.unit_test_dir, '32 pkgconfig use libraries') 6744 testdirlib = os.path.join(testdirbase, 'lib') 6745 self.init(testdirlib, extra_args=['--prefix=' + tempdirname, 6746 '--libdir=lib', 6747 '--default-library=static'], default_args=False) 6748 self.build() 6749 self.install(use_destdir=False) 6750 6751 # build user of library 6752 pkg_dir = os.path.join(tempdirname, 'lib/pkgconfig') 6753 self.new_builddir() 6754 self.init(os.path.join(testdirbase, 'app'), 6755 override_envvars={'PKG_CONFIG_PATH': pkg_dir}) 6756 self.build() 6757 6758 @skipIfNoPkgconfig 6759 def test_static_archive_stripping(self): 6760 ''' 6761 Check that Meson produces valid static archives with --strip enabled 6762 ''' 6763 with tempfile.TemporaryDirectory() as tempdirname: 6764 testdirbase = os.path.join(self.unit_test_dir, '67 static archive stripping') 6765 6766 # build lib 6767 self.new_builddir() 6768 testdirlib = os.path.join(testdirbase, 'lib') 6769 testlibprefix = os.path.join(tempdirname, 'libprefix') 6770 self.init(testdirlib, extra_args=['--prefix=' + testlibprefix, 6771 '--libdir=lib', 6772 '--default-library=static', 6773 '--buildtype=debug', 6774 '--strip'], default_args=False) 6775 self.build() 6776 self.install(use_destdir=False) 6777 6778 # build executable (uses lib, fails if static archive has been stripped incorrectly) 6779 pkg_dir = os.path.join(testlibprefix, 'lib/pkgconfig') 6780 self.new_builddir() 6781 self.init(os.path.join(testdirbase, 'app'), 6782 override_envvars={'PKG_CONFIG_PATH': pkg_dir}) 6783 self.build() 6784 6785 @skipIfNoPkgconfig 6786 def test_pkgconfig_formatting(self): 6787 testdir = os.path.join(self.unit_test_dir, '38 pkgconfig format') 6788 self.init(testdir) 6789 myenv = os.environ.copy() 6790 myenv['PKG_CONFIG_PATH'] = self.privatedir 6791 stdo = subprocess.check_output(['pkg-config', '--libs-only-l', 'libsomething'], env=myenv) 6792 deps = [b'-lgobject-2.0', b'-lgio-2.0', b'-lglib-2.0', b'-lsomething'] 6793 if is_windows() or is_cygwin() or is_osx() or is_openbsd(): 6794 # On Windows, libintl is a separate library 6795 deps.append(b'-lintl') 6796 self.assertEqual(set(deps), set(stdo.split())) 6797 6798 @skipIfNoPkgconfig 6799 @skip_if_not_language('cs') 6800 def test_pkgconfig_csharp_library(self): 6801 testdir = os.path.join(self.unit_test_dir, '50 pkgconfig csharp library') 6802 self.init(testdir) 6803 myenv = os.environ.copy() 6804 myenv['PKG_CONFIG_PATH'] = self.privatedir 6805 stdo = subprocess.check_output(['pkg-config', '--libs', 'libsomething'], env=myenv) 6806 6807 self.assertEqual("-r/usr/lib/libsomething.dll", str(stdo.decode('ascii')).strip()) 6808 6809 @skipIfNoPkgconfig 6810 def test_pkgconfig_link_order(self): 6811 ''' 6812 Test that libraries are listed before their dependencies. 6813 ''' 6814 testdir = os.path.join(self.unit_test_dir, '53 pkgconfig static link order') 6815 self.init(testdir) 6816 myenv = os.environ.copy() 6817 myenv['PKG_CONFIG_PATH'] = self.privatedir 6818 stdo = subprocess.check_output(['pkg-config', '--libs', 'libsomething'], env=myenv) 6819 deps = stdo.split() 6820 self.assertTrue(deps.index(b'-lsomething') < deps.index(b'-ldependency')) 6821 6822 def test_deterministic_dep_order(self): 6823 ''' 6824 Test that the dependencies are always listed in a deterministic order. 6825 ''' 6826 testdir = os.path.join(self.unit_test_dir, '43 dep order') 6827 self.init(testdir) 6828 with open(os.path.join(self.builddir, 'build.ninja')) as bfile: 6829 for line in bfile: 6830 if 'build myexe:' in line or 'build myexe.exe:' in line: 6831 self.assertIn('liblib1.a liblib2.a', line) 6832 return 6833 raise RuntimeError('Could not find the build rule') 6834 6835 def test_deterministic_rpath_order(self): 6836 ''' 6837 Test that the rpaths are always listed in a deterministic order. 6838 ''' 6839 if is_cygwin(): 6840 raise unittest.SkipTest('rpath are not used on Cygwin') 6841 testdir = os.path.join(self.unit_test_dir, '42 rpath order') 6842 self.init(testdir) 6843 if is_osx(): 6844 rpathre = re.compile(r'-rpath,.*/subprojects/sub1.*-rpath,.*/subprojects/sub2') 6845 else: 6846 rpathre = re.compile(r'-rpath,\$\$ORIGIN/subprojects/sub1:\$\$ORIGIN/subprojects/sub2') 6847 with open(os.path.join(self.builddir, 'build.ninja')) as bfile: 6848 for line in bfile: 6849 if '-rpath' in line: 6850 self.assertRegex(line, rpathre) 6851 return 6852 raise RuntimeError('Could not find the rpath') 6853 6854 def test_override_with_exe_dep(self): 6855 ''' 6856 Test that we produce the correct dependencies when a program is overridden with an executable. 6857 ''' 6858 testdir = os.path.join(self.common_test_dir, '201 override with exe') 6859 self.init(testdir) 6860 with open(os.path.join(self.builddir, 'build.ninja')) as bfile: 6861 for line in bfile: 6862 if 'main1.c:' in line or 'main2.c:' in line: 6863 self.assertIn('| subprojects/sub/foobar', line) 6864 6865 @skipIfNoPkgconfig 6866 def test_usage_external_library(self): 6867 ''' 6868 Test that uninstalled usage of an external library (from the system or 6869 PkgConfigDependency) works. On macOS, this workflow works out of the 6870 box. On Linux, BSDs, Windows, etc, you need to set extra arguments such 6871 as LD_LIBRARY_PATH, etc, so this test is skipped. 6872 6873 The system library is found with cc.find_library() and pkg-config deps. 6874 ''' 6875 oldprefix = self.prefix 6876 # Install external library so we can find it 6877 testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'external library') 6878 # install into installdir without using DESTDIR 6879 installdir = self.installdir 6880 self.prefix = installdir 6881 self.init(testdir) 6882 self.prefix = oldprefix 6883 self.build() 6884 self.install(use_destdir=False) 6885 ## New builddir for the consumer 6886 self.new_builddir() 6887 env = {'LIBRARY_PATH': os.path.join(installdir, self.libdir), 6888 'PKG_CONFIG_PATH': os.path.join(installdir, self.libdir, 'pkgconfig')} 6889 testdir = os.path.join(self.unit_test_dir, '40 external, internal library rpath', 'built library') 6890 # install into installdir without using DESTDIR 6891 self.prefix = self.installdir 6892 self.init(testdir, override_envvars=env) 6893 self.prefix = oldprefix 6894 self.build(override_envvars=env) 6895 # test uninstalled 6896 self.run_tests(override_envvars=env) 6897 if not (is_osx() or is_linux()): 6898 return 6899 # test running after installation 6900 self.install(use_destdir=False) 6901 prog = os.path.join(self.installdir, 'bin', 'prog') 6902 self._run([prog]) 6903 if not is_osx(): 6904 # Rest of the workflow only works on macOS 6905 return 6906 out = self._run(['otool', '-L', prog]) 6907 self.assertNotIn('@rpath', out) 6908 ## New builddir for testing that DESTDIR is not added to install_name 6909 self.new_builddir() 6910 # install into installdir with DESTDIR 6911 self.init(testdir, override_envvars=env) 6912 self.build(override_envvars=env) 6913 # test running after installation 6914 self.install(override_envvars=env) 6915 prog = self.installdir + os.path.join(self.prefix, 'bin', 'prog') 6916 lib = self.installdir + os.path.join(self.prefix, 'lib', 'libbar_built.dylib') 6917 for f in prog, lib: 6918 out = self._run(['otool', '-L', f]) 6919 # Ensure that the otool output does not contain self.installdir 6920 self.assertNotRegex(out, self.installdir + '.*dylib ') 6921 6922 @skipIfNoPkgconfig 6923 def test_usage_pkgconfig_prefixes(self): 6924 ''' 6925 Build and install two external libraries, to different prefixes, 6926 then build and install a client program that finds them via pkgconfig, 6927 and verify the installed client program runs. 6928 ''' 6929 oldinstalldir = self.installdir 6930 6931 # Build and install both external libraries without DESTDIR 6932 val1dir = os.path.join(self.unit_test_dir, '76 pkgconfig prefixes', 'val1') 6933 val1prefix = os.path.join(oldinstalldir, 'val1') 6934 self.prefix = val1prefix 6935 self.installdir = val1prefix 6936 self.init(val1dir) 6937 self.build() 6938 self.install(use_destdir=False) 6939 self.new_builddir() 6940 6941 env1 = {} 6942 env1['PKG_CONFIG_PATH'] = os.path.join(val1prefix, self.libdir, 'pkgconfig') 6943 val2dir = os.path.join(self.unit_test_dir, '76 pkgconfig prefixes', 'val2') 6944 val2prefix = os.path.join(oldinstalldir, 'val2') 6945 self.prefix = val2prefix 6946 self.installdir = val2prefix 6947 self.init(val2dir, override_envvars=env1) 6948 self.build() 6949 self.install(use_destdir=False) 6950 self.new_builddir() 6951 6952 # Build, install, and run the client program 6953 env2 = {} 6954 env2['PKG_CONFIG_PATH'] = os.path.join(val2prefix, self.libdir, 'pkgconfig') 6955 testdir = os.path.join(self.unit_test_dir, '76 pkgconfig prefixes', 'client') 6956 testprefix = os.path.join(oldinstalldir, 'client') 6957 self.prefix = testprefix 6958 self.installdir = testprefix 6959 self.init(testdir, override_envvars=env2) 6960 self.build() 6961 self.install(use_destdir=False) 6962 prog = os.path.join(self.installdir, 'bin', 'client') 6963 env3 = {} 6964 if is_cygwin(): 6965 env3['PATH'] = os.path.join(val1prefix, 'bin') + \ 6966 os.pathsep + \ 6967 os.path.join(val2prefix, 'bin') + \ 6968 os.pathsep + os.environ['PATH'] 6969 out = self._run([prog], override_envvars=env3).strip() 6970 # Expected output is val1 + val2 = 3 6971 self.assertEqual(out, '3') 6972 6973 def install_subdir_invalid_symlinks(self, testdir, subdir_path): 6974 ''' 6975 Test that installation of broken symlinks works fine. 6976 https://github.com/mesonbuild/meson/issues/3914 6977 ''' 6978 testdir = os.path.join(self.common_test_dir, testdir) 6979 subdir = os.path.join(testdir, subdir_path) 6980 with chdir(subdir): 6981 # Can't distribute broken symlinks in the source tree because it breaks 6982 # the creation of zipapps. Create it dynamically and run the test by 6983 # hand. 6984 src = '../../nonexistent.txt' 6985 os.symlink(src, 'invalid-symlink.txt') 6986 try: 6987 self.init(testdir) 6988 self.build() 6989 self.install() 6990 install_path = subdir_path.split(os.path.sep)[-1] 6991 link = os.path.join(self.installdir, 'usr', 'share', install_path, 'invalid-symlink.txt') 6992 self.assertTrue(os.path.islink(link), msg=link) 6993 self.assertEqual(src, os.readlink(link)) 6994 self.assertFalse(os.path.isfile(link), msg=link) 6995 finally: 6996 os.remove(os.path.join(subdir, 'invalid-symlink.txt')) 6997 6998 def test_install_subdir_symlinks(self): 6999 self.install_subdir_invalid_symlinks('62 install subdir', os.path.join('sub', 'sub1')) 7000 7001 def test_install_subdir_symlinks_with_default_umask(self): 7002 self.install_subdir_invalid_symlinks('195 install_mode', 'sub2') 7003 7004 def test_install_subdir_symlinks_with_default_umask_and_mode(self): 7005 self.install_subdir_invalid_symlinks('195 install_mode', 'sub1') 7006 7007 @skipIfNoPkgconfigDep('gmodule-2.0') 7008 def test_ldflag_dedup(self): 7009 testdir = os.path.join(self.unit_test_dir, '52 ldflagdedup') 7010 if is_cygwin() or is_osx(): 7011 raise unittest.SkipTest('Not applicable on Cygwin or OSX.') 7012 env = get_fake_env() 7013 cc = env.detect_c_compiler(MachineChoice.HOST) 7014 linker = cc.linker 7015 if not linker.export_dynamic_args(env): 7016 raise unittest.SkipTest('Not applicable for linkers without --export-dynamic') 7017 self.init(testdir) 7018 build_ninja = os.path.join(self.builddir, 'build.ninja') 7019 max_count = 0 7020 search_term = '-Wl,--export-dynamic' 7021 with open(build_ninja, 'r', encoding='utf-8') as f: 7022 for line in f: 7023 max_count = max(max_count, line.count(search_term)) 7024 self.assertEqual(max_count, 1, 'Export dynamic incorrectly deduplicated.') 7025 7026 def test_compiler_libs_static_dedup(self): 7027 testdir = os.path.join(self.unit_test_dir, '56 dedup compiler libs') 7028 self.init(testdir) 7029 build_ninja = os.path.join(self.builddir, 'build.ninja') 7030 with open(build_ninja, 'r', encoding='utf-8') as f: 7031 lines = f.readlines() 7032 for lib in ('-ldl', '-lm', '-lc', '-lrt'): 7033 for line in lines: 7034 if lib not in line: 7035 continue 7036 # Assert that 7037 self.assertEqual(len(line.split(lib)), 2, msg=(lib, line)) 7038 7039 @skipIfNoPkgconfig 7040 def test_noncross_options(self): 7041 # C_std defined in project options must be in effect also when native compiling. 7042 testdir = os.path.join(self.unit_test_dir, '51 noncross options') 7043 self.init(testdir, extra_args=['-Dpkg_config_path=' + testdir]) 7044 compdb = self.get_compdb() 7045 self.assertEqual(len(compdb), 2) 7046 self.assertRegex(compdb[0]['command'], '-std=c99') 7047 self.assertRegex(compdb[1]['command'], '-std=c99') 7048 self.build() 7049 7050 def test_identity_cross(self): 7051 testdir = os.path.join(self.unit_test_dir, '61 identity cross') 7052 7053 nativefile = tempfile.NamedTemporaryFile(mode='w') 7054 nativefile.write('''[binaries] 7055c = ['{0}'] 7056'''.format(os.path.join(testdir, 'build_wrapper.py'))) 7057 nativefile.flush() 7058 self.meson_native_file = nativefile.name 7059 7060 crossfile = tempfile.NamedTemporaryFile(mode='w') 7061 crossfile.write('''[binaries] 7062c = ['{0}'] 7063'''.format(os.path.join(testdir, 'host_wrapper.py'))) 7064 crossfile.flush() 7065 self.meson_cross_file = crossfile.name 7066 7067 # TODO should someday be explicit about build platform only here 7068 self.init(testdir) 7069 7070 def test_identity_cross_env(self): 7071 testdir = os.path.join(self.unit_test_dir, '61 identity cross') 7072 env = { 7073 'CC_FOR_BUILD': '"' + os.path.join(testdir, 'build_wrapper.py') + '"', 7074 } 7075 crossfile = tempfile.NamedTemporaryFile(mode='w') 7076 crossfile.write('''[binaries] 7077c = ['{0}'] 7078'''.format(os.path.join(testdir, 'host_wrapper.py'))) 7079 crossfile.flush() 7080 self.meson_cross_file = crossfile.name 7081 # TODO should someday be explicit about build platform only here 7082 self.init(testdir, override_envvars=env) 7083 7084 @skipIfNoPkgconfig 7085 def test_static_link(self): 7086 if is_cygwin(): 7087 raise unittest.SkipTest("Cygwin doesn't support LD_LIBRARY_PATH.") 7088 7089 # Build some libraries and install them 7090 testdir = os.path.join(self.unit_test_dir, '68 static link/lib') 7091 libdir = os.path.join(self.installdir, self.libdir) 7092 oldprefix = self.prefix 7093 self.prefix = self.installdir 7094 self.init(testdir) 7095 self.install(use_destdir=False) 7096 7097 # Test that installed libraries works 7098 self.new_builddir() 7099 self.prefix = oldprefix 7100 meson_args = ['-Dc_link_args=-L{}'.format(libdir), 7101 '--fatal-meson-warnings'] 7102 testdir = os.path.join(self.unit_test_dir, '68 static link') 7103 env = {'PKG_CONFIG_LIBDIR': os.path.join(libdir, 'pkgconfig')} 7104 self.init(testdir, extra_args=meson_args, override_envvars=env) 7105 self.build() 7106 self.run_tests() 7107 7108 def _check_ld(self, check: str, name: str, lang: str, expected: str) -> None: 7109 if is_sunos(): 7110 raise unittest.SkipTest('Solaris currently cannot override the linker.') 7111 if not shutil.which(check): 7112 raise unittest.SkipTest('Could not find {}.'.format(check)) 7113 envvars = [mesonbuild.envconfig.BinaryTable.evarMap['{}_ld'.format(lang)]] 7114 7115 # Also test a deprecated variable if there is one. 7116 if envvars[0] in mesonbuild.envconfig.BinaryTable.DEPRECATION_MAP: 7117 envvars.append( 7118 mesonbuild.envconfig.BinaryTable.DEPRECATION_MAP[envvars[0]]) 7119 7120 for envvar in envvars: 7121 with mock.patch.dict(os.environ, {envvar: name}): 7122 env = get_fake_env() 7123 comp = getattr(env, 'detect_{}_compiler'.format(lang))(MachineChoice.HOST) 7124 if lang != 'rust' and comp.use_linker_args('bfd') == []: 7125 raise unittest.SkipTest( 7126 'Compiler {} does not support using alternative linkers'.format(comp.id)) 7127 self.assertEqual(comp.linker.id, expected) 7128 7129 def test_ld_environment_variable_bfd(self): 7130 self._check_ld('ld.bfd', 'bfd', 'c', 'ld.bfd') 7131 7132 def test_ld_environment_variable_gold(self): 7133 self._check_ld('ld.gold', 'gold', 'c', 'ld.gold') 7134 7135 def test_ld_environment_variable_lld(self): 7136 self._check_ld('ld.lld', 'lld', 'c', 'ld.lld') 7137 7138 @skip_if_not_language('rust') 7139 def test_ld_environment_variable_rust(self): 7140 self._check_ld('ld.gold', 'gold', 'rust', 'ld.gold') 7141 7142 def test_ld_environment_variable_cpp(self): 7143 self._check_ld('ld.gold', 'gold', 'cpp', 'ld.gold') 7144 7145 @skip_if_not_language('objc') 7146 def test_ld_environment_variable_objc(self): 7147 self._check_ld('ld.gold', 'gold', 'objc', 'ld.gold') 7148 7149 @skip_if_not_language('objcpp') 7150 def test_ld_environment_variable_objcpp(self): 7151 self._check_ld('ld.gold', 'gold', 'objcpp', 'ld.gold') 7152 7153 @skip_if_not_language('fortran') 7154 def test_ld_environment_variable_fortran(self): 7155 self._check_ld('ld.gold', 'gold', 'fortran', 'ld.gold') 7156 7157 @skip_if_not_language('d') 7158 def test_ld_environment_variable_d(self): 7159 # At least for me, ldc defaults to gold, and gdc defaults to bfd, so 7160 # let's pick lld, which isn't the default for either (currently) 7161 self._check_ld('ld.lld', 'lld', 'd', 'ld.lld') 7162 7163 def compute_sha256(self, filename): 7164 with open(filename, 'rb') as f: 7165 return hashlib.sha256(f.read()).hexdigest() 7166 7167 def test_wrap_with_file_url(self): 7168 testdir = os.path.join(self.unit_test_dir, '74 wrap file url') 7169 source_filename = os.path.join(testdir, 'subprojects', 'foo.tar.xz') 7170 patch_filename = os.path.join(testdir, 'subprojects', 'foo-patch.tar.xz') 7171 wrap_filename = os.path.join(testdir, 'subprojects', 'foo.wrap') 7172 source_hash = self.compute_sha256(source_filename) 7173 patch_hash = self.compute_sha256(patch_filename) 7174 wrap = textwrap.dedent("""\ 7175 [wrap-file] 7176 directory = foo 7177 7178 source_url = http://server.invalid/foo 7179 source_fallback_url = file://{} 7180 source_filename = foo.tar.xz 7181 source_hash = {} 7182 7183 patch_url = http://server.invalid/foo 7184 patch_fallback_url = file://{} 7185 patch_filename = foo-patch.tar.xz 7186 patch_hash = {} 7187 """.format(source_filename, source_hash, patch_filename, patch_hash)) 7188 with open(wrap_filename, 'w') as f: 7189 f.write(wrap) 7190 self.init(testdir) 7191 self.build() 7192 self.run_tests() 7193 7194 windows_proof_rmtree(os.path.join(testdir, 'subprojects', 'packagecache')) 7195 windows_proof_rmtree(os.path.join(testdir, 'subprojects', 'foo')) 7196 os.unlink(wrap_filename) 7197 7198 def test_no_rpath_for_static(self): 7199 testdir = os.path.join(self.common_test_dir, '5 linkstatic') 7200 self.init(testdir) 7201 self.build() 7202 build_rpath = get_rpath(os.path.join(self.builddir, 'prog')) 7203 self.assertIsNone(build_rpath) 7204 7205 def test_lookup_system_after_broken_fallback(self): 7206 # Just to generate libfoo.pc so we can test system dependency lookup. 7207 testdir = os.path.join(self.common_test_dir, '47 pkgconfig-gen') 7208 self.init(testdir) 7209 privatedir = self.privatedir 7210 7211 # Write test project where the first dependency() returns not-found 7212 # because 'broken' subproject does not exit, but that should not prevent 7213 # the 2nd dependency() to lookup on system. 7214 self.new_builddir() 7215 with tempfile.TemporaryDirectory() as d: 7216 with open(os.path.join(d, 'meson.build'), 'w') as f: 7217 f.write(textwrap.dedent('''\ 7218 project('test') 7219 dependency('notfound', fallback: 'broken', required: false) 7220 dependency('libfoo', fallback: 'broken', required: true) 7221 ''')) 7222 self.init(d, override_envvars={'PKG_CONFIG_LIBDIR': privatedir}) 7223 7224class BaseLinuxCrossTests(BasePlatformTests): 7225 # Don't pass --libdir when cross-compiling. We have tests that 7226 # check whether meson auto-detects it correctly. 7227 libdir = None 7228 7229 7230def should_run_cross_arm_tests(): 7231 return shutil.which('arm-linux-gnueabihf-gcc') and not platform.machine().lower().startswith('arm') 7232 7233@unittest.skipUnless(not is_windows() and should_run_cross_arm_tests(), "requires ability to cross compile to ARM") 7234class LinuxCrossArmTests(BaseLinuxCrossTests): 7235 ''' 7236 Tests that cross-compilation to Linux/ARM works 7237 ''' 7238 7239 def setUp(self): 7240 super().setUp() 7241 src_root = os.path.dirname(__file__) 7242 self.meson_cross_file = os.path.join(src_root, 'cross', 'ubuntu-armhf.txt') 7243 7244 def test_cflags_cross_environment_pollution(self): 7245 ''' 7246 Test that the CFLAGS environment variable does not pollute the cross 7247 environment. This can't be an ordinary test case because we need to 7248 inspect the compiler database. 7249 ''' 7250 testdir = os.path.join(self.common_test_dir, '3 static') 7251 self.init(testdir, override_envvars={'CFLAGS': '-DBUILD_ENVIRONMENT_ONLY'}) 7252 compdb = self.get_compdb() 7253 self.assertNotIn('-DBUILD_ENVIRONMENT_ONLY', compdb[0]['command']) 7254 7255 def test_cross_file_overrides_always_args(self): 7256 ''' 7257 Test that $lang_args in cross files always override get_always_args(). 7258 Needed for overriding the default -D_FILE_OFFSET_BITS=64 on some 7259 architectures such as some Android versions and Raspbian. 7260 https://github.com/mesonbuild/meson/issues/3049 7261 https://github.com/mesonbuild/meson/issues/3089 7262 ''' 7263 testdir = os.path.join(self.unit_test_dir, '33 cross file overrides always args') 7264 self.meson_cross_file = os.path.join(testdir, 'ubuntu-armhf-overrides.txt') 7265 self.init(testdir) 7266 compdb = self.get_compdb() 7267 self.assertRegex(compdb[0]['command'], '-D_FILE_OFFSET_BITS=64.*-U_FILE_OFFSET_BITS') 7268 self.build() 7269 7270 def test_cross_libdir(self): 7271 # When cross compiling "libdir" should default to "lib" 7272 # rather than "lib/x86_64-linux-gnu" or something like that. 7273 testdir = os.path.join(self.common_test_dir, '1 trivial') 7274 self.init(testdir) 7275 for i in self.introspect('--buildoptions'): 7276 if i['name'] == 'libdir': 7277 self.assertEqual(i['value'], 'lib') 7278 return 7279 self.assertTrue(False, 'Option libdir not in introspect data.') 7280 7281 def test_cross_libdir_subproject(self): 7282 # Guard against a regression where calling "subproject" 7283 # would reset the value of libdir to its default value. 7284 testdir = os.path.join(self.unit_test_dir, '76 subdir libdir') 7285 self.init(testdir, extra_args=['--libdir=fuf']) 7286 for i in self.introspect('--buildoptions'): 7287 if i['name'] == 'libdir': 7288 self.assertEqual(i['value'], 'fuf') 7289 return 7290 self.assertTrue(False, 'Libdir specified on command line gets reset.') 7291 7292 def test_std_remains(self): 7293 # C_std defined in project options must be in effect also when cross compiling. 7294 testdir = os.path.join(self.unit_test_dir, '51 noncross options') 7295 self.init(testdir) 7296 compdb = self.get_compdb() 7297 self.assertRegex(compdb[0]['command'], '-std=c99') 7298 self.build() 7299 7300 @skipIfNoPkgconfig 7301 def test_pkg_config_option(self): 7302 if not shutil.which('arm-linux-gnueabihf-pkg-config'): 7303 raise unittest.SkipTest('Cross-pkgconfig not found.') 7304 testdir = os.path.join(self.unit_test_dir, '58 pkg_config_path option') 7305 self.init(testdir, extra_args=[ 7306 '-Dbuild.pkg_config_path=' + os.path.join(testdir, 'build_extra_path'), 7307 '-Dpkg_config_path=' + os.path.join(testdir, 'host_extra_path'), 7308 ]) 7309 7310 7311def should_run_cross_mingw_tests(): 7312 return shutil.which('x86_64-w64-mingw32-gcc') and not (is_windows() or is_cygwin()) 7313 7314@unittest.skipUnless(not is_windows() and should_run_cross_mingw_tests(), "requires ability to cross compile with MinGW") 7315class LinuxCrossMingwTests(BaseLinuxCrossTests): 7316 ''' 7317 Tests that cross-compilation to Windows/MinGW works 7318 ''' 7319 7320 def setUp(self): 7321 super().setUp() 7322 src_root = os.path.dirname(__file__) 7323 self.meson_cross_file = os.path.join(src_root, 'cross', 'linux-mingw-w64-64bit.txt') 7324 7325 def test_exe_wrapper_behaviour(self): 7326 ''' 7327 Test that an exe wrapper that isn't found doesn't cause compiler sanity 7328 checks and compiler checks to fail, but causes configure to fail if it 7329 requires running a cross-built executable (custom_target or run_target) 7330 and causes the tests to be skipped if they are run. 7331 ''' 7332 testdir = os.path.join(self.unit_test_dir, '36 exe_wrapper behaviour') 7333 # Configures, builds, and tests fine by default 7334 self.init(testdir) 7335 self.build() 7336 self.run_tests() 7337 self.wipe() 7338 os.mkdir(self.builddir) 7339 # Change cross file to use a non-existing exe_wrapper and it should fail 7340 self.meson_cross_file = os.path.join(testdir, 'broken-cross.txt') 7341 # Force tracebacks so we can detect them properly 7342 env = {'MESON_FORCE_BACKTRACE': '1'} 7343 with self.assertRaisesRegex(MesonException, 'exe_wrapper.*target.*use-exe-wrapper'): 7344 # Must run in-process or we'll get a generic CalledProcessError 7345 self.init(testdir, extra_args='-Drun-target=false', 7346 inprocess=True, 7347 override_envvars=env) 7348 with self.assertRaisesRegex(MesonException, 'exe_wrapper.*run target.*run-prog'): 7349 # Must run in-process or we'll get a generic CalledProcessError 7350 self.init(testdir, extra_args='-Dcustom-target=false', 7351 inprocess=True, 7352 override_envvars=env) 7353 self.init(testdir, extra_args=['-Dcustom-target=false', '-Drun-target=false'], 7354 override_envvars=env) 7355 self.build() 7356 with self.assertRaisesRegex(MesonException, 'exe_wrapper.*PATH'): 7357 # Must run in-process or we'll get a generic CalledProcessError 7358 self.run_tests(inprocess=True, override_envvars=env) 7359 7360 @skipIfNoPkgconfig 7361 def test_cross_pkg_config_option(self): 7362 testdir = os.path.join(self.unit_test_dir, '58 pkg_config_path option') 7363 self.init(testdir, extra_args=[ 7364 '-Dbuild.pkg_config_path=' + os.path.join(testdir, 'build_extra_path'), 7365 '-Dpkg_config_path=' + os.path.join(testdir, 'host_extra_path'), 7366 ]) 7367 7368 7369class PythonTests(BasePlatformTests): 7370 ''' 7371 Tests that verify compilation of python extension modules 7372 ''' 7373 7374 def test_versions(self): 7375 if self.backend is not Backend.ninja: 7376 raise unittest.SkipTest('Skipping python tests with {} backend'.format(self.backend.name)) 7377 7378 testdir = os.path.join(self.src_root, 'test cases', 'unit', '39 python extmodule') 7379 7380 # No python version specified, this will use meson's python 7381 self.init(testdir) 7382 self.build() 7383 self.run_tests() 7384 self.wipe() 7385 7386 # When specifying a known name, (python2 / python3) the module 7387 # will also try 'python' as a fallback and use it if the major 7388 # version matches 7389 try: 7390 self.init(testdir, extra_args=['-Dpython=python2']) 7391 self.build() 7392 self.run_tests() 7393 except unittest.SkipTest: 7394 # python2 is not necessarily installed on the test machine, 7395 # if it is not, or the python headers can't be found, the test 7396 # will raise MESON_SKIP_TEST, we could check beforehand what version 7397 # of python is available, but it's a bit of a chicken and egg situation, 7398 # as that is the job of the module, so we just ask for forgiveness rather 7399 # than permission. 7400 pass 7401 7402 self.wipe() 7403 7404 for py in ('pypy', 'pypy3'): 7405 try: 7406 self.init(testdir, extra_args=['-Dpython=%s' % py]) 7407 except unittest.SkipTest: 7408 # Same as above, pypy2 and pypy3 are not expected to be present 7409 # on the test system, the test project only raises in these cases 7410 continue 7411 7412 # We have a pypy, this is expected to work 7413 self.build() 7414 self.run_tests() 7415 self.wipe() 7416 7417 # The test is configured to error out with MESON_SKIP_TEST 7418 # in case it could not find python 7419 with self.assertRaises(unittest.SkipTest): 7420 self.init(testdir, extra_args=['-Dpython=not-python']) 7421 self.wipe() 7422 7423 # While dir is an external command on both Windows and Linux, 7424 # it certainly isn't python 7425 with self.assertRaises(unittest.SkipTest): 7426 self.init(testdir, extra_args=['-Dpython=dir']) 7427 self.wipe() 7428 7429 7430class RewriterTests(BasePlatformTests): 7431 def setUp(self): 7432 super().setUp() 7433 self.maxDiff = None 7434 7435 def prime(self, dirname): 7436 copy_tree(os.path.join(self.rewrite_test_dir, dirname), self.builddir) 7437 7438 def rewrite_raw(self, directory, args): 7439 if isinstance(args, str): 7440 args = [args] 7441 command = self.rewrite_command + ['--verbose', '--skip', '--sourcedir', directory] + args 7442 p = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 7443 universal_newlines=True, timeout=60) 7444 print('STDOUT:') 7445 print(p.stdout) 7446 print('STDERR:') 7447 print(p.stderr) 7448 if p.returncode != 0: 7449 if 'MESON_SKIP_TEST' in p.stdout: 7450 raise unittest.SkipTest('Project requested skipping.') 7451 raise subprocess.CalledProcessError(p.returncode, command, output=p.stdout) 7452 if not p.stderr: 7453 return {} 7454 return json.loads(p.stderr) 7455 7456 def rewrite(self, directory, args): 7457 if isinstance(args, str): 7458 args = [args] 7459 return self.rewrite_raw(directory, ['command'] + args) 7460 7461 def test_target_source_list(self): 7462 self.prime('1 basic') 7463 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7464 expected = { 7465 'target': { 7466 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp']}, 7467 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp']}, 7468 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']}, 7469 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']}, 7470 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']}, 7471 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']}, 7472 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']}, 7473 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']}, 7474 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']}, 7475 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp']}, 7476 } 7477 } 7478 self.assertDictEqual(out, expected) 7479 7480 def test_target_add_sources(self): 7481 self.prime('1 basic') 7482 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) 7483 expected = { 7484 'target': { 7485 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp']}, 7486 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}, 7487 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['a7.cpp', 'fileB.cpp', 'fileC.cpp']}, 7488 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['a5.cpp', 'fileA.cpp', 'main.cpp']}, 7489 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['a5.cpp', 'main.cpp', 'fileA.cpp']}, 7490 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['a3.cpp', 'main.cpp', 'a7.cpp', 'fileB.cpp', 'fileC.cpp']}, 7491 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp', 'a4.cpp']}, 7492 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}, 7493 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}, 7494 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}, 7495 } 7496 } 7497 self.assertDictEqual(out, expected) 7498 7499 # Check the written file 7500 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7501 self.assertDictEqual(out, expected) 7502 7503 def test_target_add_sources_abs(self): 7504 self.prime('1 basic') 7505 abs_src = [os.path.join(self.builddir, x) for x in ['a1.cpp', 'a2.cpp', 'a6.cpp']] 7506 add = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "src_add", "sources": abs_src}]) 7507 inf = json.dumps([{"type": "target", "target": "trivialprog1", "operation": "info"}]) 7508 self.rewrite(self.builddir, add) 7509 out = self.rewrite(self.builddir, inf) 7510 expected = {'target': {'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['a1.cpp', 'a2.cpp', 'a6.cpp', 'fileA.cpp', 'main.cpp']}}} 7511 self.assertDictEqual(out, expected) 7512 7513 def test_target_remove_sources(self): 7514 self.prime('1 basic') 7515 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'rmSrc.json')) 7516 expected = { 7517 'target': { 7518 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileC.cpp']}, 7519 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp']}, 7520 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileC.cpp']}, 7521 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp']}, 7522 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp']}, 7523 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileC.cpp']}, 7524 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp']}, 7525 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileC.cpp', 'main.cpp']}, 7526 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp']}, 7527 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp']}, 7528 } 7529 } 7530 self.assertDictEqual(out, expected) 7531 7532 # Check the written file 7533 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7534 self.assertDictEqual(out, expected) 7535 7536 def test_target_subdir(self): 7537 self.prime('2 subdirs') 7538 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) 7539 expected = {'name': 'something', 'sources': ['first.c', 'second.c', 'third.c']} 7540 self.assertDictEqual(list(out['target'].values())[0], expected) 7541 7542 # Check the written file 7543 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7544 self.assertDictEqual(list(out['target'].values())[0], expected) 7545 7546 def test_target_remove(self): 7547 self.prime('1 basic') 7548 self.rewrite(self.builddir, os.path.join(self.builddir, 'rmTgt.json')) 7549 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7550 7551 expected = { 7552 'target': { 7553 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']}, 7554 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']}, 7555 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']}, 7556 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']}, 7557 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']}, 7558 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']}, 7559 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']}, 7560 } 7561 } 7562 self.assertDictEqual(out, expected) 7563 7564 def test_tatrget_add(self): 7565 self.prime('1 basic') 7566 self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json')) 7567 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7568 7569 expected = { 7570 'target': { 7571 'trivialprog0@exe': {'name': 'trivialprog0', 'sources': ['main.cpp', 'fileA.cpp', 'fileB.cpp', 'fileC.cpp']}, 7572 'trivialprog1@exe': {'name': 'trivialprog1', 'sources': ['main.cpp', 'fileA.cpp']}, 7573 'trivialprog2@exe': {'name': 'trivialprog2', 'sources': ['fileB.cpp', 'fileC.cpp']}, 7574 'trivialprog3@exe': {'name': 'trivialprog3', 'sources': ['main.cpp', 'fileA.cpp']}, 7575 'trivialprog4@exe': {'name': 'trivialprog4', 'sources': ['main.cpp', 'fileA.cpp']}, 7576 'trivialprog5@exe': {'name': 'trivialprog5', 'sources': ['main.cpp', 'fileB.cpp', 'fileC.cpp']}, 7577 'trivialprog6@exe': {'name': 'trivialprog6', 'sources': ['main.cpp', 'fileA.cpp']}, 7578 'trivialprog7@exe': {'name': 'trivialprog7', 'sources': ['fileB.cpp', 'fileC.cpp', 'main.cpp', 'fileA.cpp']}, 7579 'trivialprog8@exe': {'name': 'trivialprog8', 'sources': ['main.cpp', 'fileA.cpp']}, 7580 'trivialprog9@exe': {'name': 'trivialprog9', 'sources': ['main.cpp', 'fileA.cpp']}, 7581 'trivialprog10@sha': {'name': 'trivialprog10', 'sources': ['new1.cpp', 'new2.cpp']}, 7582 } 7583 } 7584 self.assertDictEqual(out, expected) 7585 7586 def test_target_remove_subdir(self): 7587 self.prime('2 subdirs') 7588 self.rewrite(self.builddir, os.path.join(self.builddir, 'rmTgt.json')) 7589 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7590 self.assertDictEqual(out, {}) 7591 7592 def test_target_add_subdir(self): 7593 self.prime('2 subdirs') 7594 self.rewrite(self.builddir, os.path.join(self.builddir, 'addTgt.json')) 7595 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7596 expected = {'name': 'something', 'sources': ['first.c', 'second.c']} 7597 self.assertDictEqual(out['target']['94b671c@@something@exe'], expected) 7598 7599 def test_target_source_sorting(self): 7600 self.prime('5 sorting') 7601 add_json = json.dumps([{'type': 'target', 'target': 'exe1', 'operation': 'src_add', 'sources': ['a666.c']}]) 7602 inf_json = json.dumps([{'type': 'target', 'target': 'exe1', 'operation': 'info'}]) 7603 out = self.rewrite(self.builddir, add_json) 7604 out = self.rewrite(self.builddir, inf_json) 7605 expected = { 7606 'target': { 7607 'exe1@exe': { 7608 'name': 'exe1', 7609 'sources': [ 7610 'aaa/a/a1.c', 7611 'aaa/b/b1.c', 7612 'aaa/b/b2.c', 7613 'aaa/f1.c', 7614 'aaa/f2.c', 7615 'aaa/f3.c', 7616 'bbb/a/b1.c', 7617 'bbb/b/b2.c', 7618 'bbb/c1/b5.c', 7619 'bbb/c2/b7.c', 7620 'bbb/c10/b6.c', 7621 'bbb/a4.c', 7622 'bbb/b3.c', 7623 'bbb/b4.c', 7624 'bbb/b5.c', 7625 'a1.c', 7626 'a2.c', 7627 'a3.c', 7628 'a10.c', 7629 'a20.c', 7630 'a30.c', 7631 'a100.c', 7632 'a101.c', 7633 'a110.c', 7634 'a210.c', 7635 'a666.c', 7636 'b1.c', 7637 'c2.c' 7638 ] 7639 } 7640 } 7641 } 7642 self.assertDictEqual(out, expected) 7643 7644 def test_target_same_name_skip(self): 7645 self.prime('4 same name targets') 7646 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'addSrc.json')) 7647 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7648 expected = {'name': 'myExe', 'sources': ['main.cpp']} 7649 self.assertEqual(len(out['target']), 2) 7650 for val in out['target'].values(): 7651 self.assertDictEqual(expected, val) 7652 7653 def test_kwargs_info(self): 7654 self.prime('3 kwargs') 7655 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7656 expected = { 7657 'kwargs': { 7658 'project#/': {'version': '0.0.1'}, 7659 'target#tgt1': {'build_by_default': True}, 7660 'dependency#dep1': {'required': False} 7661 } 7662 } 7663 self.assertDictEqual(out, expected) 7664 7665 def test_kwargs_set(self): 7666 self.prime('3 kwargs') 7667 self.rewrite(self.builddir, os.path.join(self.builddir, 'set.json')) 7668 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7669 expected = { 7670 'kwargs': { 7671 'project#/': {'version': '0.0.2', 'meson_version': '0.50.0', 'license': ['GPL', 'MIT']}, 7672 'target#tgt1': {'build_by_default': False, 'build_rpath': '/usr/local', 'dependencies': 'dep1'}, 7673 'dependency#dep1': {'required': True, 'method': 'cmake'} 7674 } 7675 } 7676 self.assertDictEqual(out, expected) 7677 7678 def test_kwargs_add(self): 7679 self.prime('3 kwargs') 7680 self.rewrite(self.builddir, os.path.join(self.builddir, 'add.json')) 7681 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7682 expected = { 7683 'kwargs': { 7684 'project#/': {'version': '0.0.1', 'license': ['GPL', 'MIT', 'BSD']}, 7685 'target#tgt1': {'build_by_default': True}, 7686 'dependency#dep1': {'required': False} 7687 } 7688 } 7689 self.assertDictEqual(out, expected) 7690 7691 def test_kwargs_remove(self): 7692 self.prime('3 kwargs') 7693 self.rewrite(self.builddir, os.path.join(self.builddir, 'remove.json')) 7694 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7695 expected = { 7696 'kwargs': { 7697 'project#/': {'version': '0.0.1', 'license': 'GPL'}, 7698 'target#tgt1': {'build_by_default': True}, 7699 'dependency#dep1': {'required': False} 7700 } 7701 } 7702 self.assertDictEqual(out, expected) 7703 7704 def test_kwargs_remove_regex(self): 7705 self.prime('3 kwargs') 7706 self.rewrite(self.builddir, os.path.join(self.builddir, 'remove_regex.json')) 7707 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7708 expected = { 7709 'kwargs': { 7710 'project#/': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=true']}, 7711 'target#tgt1': {'build_by_default': True}, 7712 'dependency#dep1': {'required': False} 7713 } 7714 } 7715 self.assertDictEqual(out, expected) 7716 7717 def test_kwargs_delete(self): 7718 self.prime('3 kwargs') 7719 self.rewrite(self.builddir, os.path.join(self.builddir, 'delete.json')) 7720 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7721 expected = { 7722 'kwargs': { 7723 'project#/': {}, 7724 'target#tgt1': {}, 7725 'dependency#dep1': {'required': False} 7726 } 7727 } 7728 self.assertDictEqual(out, expected) 7729 7730 def test_default_options_set(self): 7731 self.prime('3 kwargs') 7732 self.rewrite(self.builddir, os.path.join(self.builddir, 'defopts_set.json')) 7733 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7734 expected = { 7735 'kwargs': { 7736 'project#/': {'version': '0.0.1', 'default_options': ['buildtype=release', 'debug=True', 'cpp_std=c++11']}, 7737 'target#tgt1': {'build_by_default': True}, 7738 'dependency#dep1': {'required': False} 7739 } 7740 } 7741 self.assertDictEqual(out, expected) 7742 7743 def test_default_options_delete(self): 7744 self.prime('3 kwargs') 7745 self.rewrite(self.builddir, os.path.join(self.builddir, 'defopts_delete.json')) 7746 out = self.rewrite(self.builddir, os.path.join(self.builddir, 'info.json')) 7747 expected = { 7748 'kwargs': { 7749 'project#/': {'version': '0.0.1', 'default_options': ['cpp_std=c++14', 'debug=true']}, 7750 'target#tgt1': {'build_by_default': True}, 7751 'dependency#dep1': {'required': False} 7752 } 7753 } 7754 self.assertDictEqual(out, expected) 7755 7756class NativeFileTests(BasePlatformTests): 7757 7758 def setUp(self): 7759 super().setUp() 7760 self.testcase = os.path.join(self.unit_test_dir, '47 native file binary') 7761 self.current_config = 0 7762 self.current_wrapper = 0 7763 7764 def helper_create_native_file(self, values): 7765 """Create a config file as a temporary file. 7766 7767 values should be a nested dictionary structure of {section: {key: 7768 value}} 7769 """ 7770 filename = os.path.join(self.builddir, 'generated{}.config'.format(self.current_config)) 7771 self.current_config += 1 7772 with open(filename, 'wt') as f: 7773 for section, entries in values.items(): 7774 f.write('[{}]\n'.format(section)) 7775 for k, v in entries.items(): 7776 f.write("{}='{}'\n".format(k, v)) 7777 return filename 7778 7779 def helper_create_binary_wrapper(self, binary, dir_=None, extra_args=None, **kwargs): 7780 """Creates a wrapper around a binary that overrides specific values.""" 7781 filename = os.path.join(dir_ or self.builddir, 'binary_wrapper{}.py'.format(self.current_wrapper)) 7782 extra_args = extra_args or {} 7783 self.current_wrapper += 1 7784 if is_haiku(): 7785 chbang = '#!/bin/env python3' 7786 else: 7787 chbang = '#!/usr/bin/env python3' 7788 7789 with open(filename, 'wt') as f: 7790 f.write(textwrap.dedent('''\ 7791 {} 7792 import argparse 7793 import subprocess 7794 import sys 7795 7796 def main(): 7797 parser = argparse.ArgumentParser() 7798 '''.format(chbang))) 7799 for name in chain(extra_args, kwargs): 7800 f.write(' parser.add_argument("-{0}", "--{0}", action="store_true")\n'.format(name)) 7801 f.write(' args, extra_args = parser.parse_known_args()\n') 7802 for name, value in chain(extra_args.items(), kwargs.items()): 7803 f.write(' if args.{}:\n'.format(name)) 7804 f.write(' print("{}", file=sys.{})\n'.format(value, kwargs.get('outfile', 'stdout'))) 7805 f.write(' sys.exit(0)\n') 7806 f.write(textwrap.dedent(''' 7807 ret = subprocess.run( 7808 ["{}"] + extra_args, 7809 stdout=subprocess.PIPE, 7810 stderr=subprocess.PIPE) 7811 print(ret.stdout.decode('utf-8')) 7812 print(ret.stderr.decode('utf-8'), file=sys.stderr) 7813 sys.exit(ret.returncode) 7814 7815 if __name__ == '__main__': 7816 main() 7817 '''.format(binary))) 7818 7819 if not is_windows(): 7820 os.chmod(filename, 0o755) 7821 return filename 7822 7823 # On windows we need yet another level of indirection, as cmd cannot 7824 # invoke python files itself, so instead we generate a .bat file, which 7825 # invokes our python wrapper 7826 batfile = os.path.join(self.builddir, 'binary_wrapper{}.bat'.format(self.current_wrapper)) 7827 with open(batfile, 'wt') as f: 7828 f.write(r'@{} {} %*'.format(sys.executable, filename)) 7829 return batfile 7830 7831 def helper_for_compiler(self, lang, cb, for_machine = MachineChoice.HOST): 7832 """Helper for generating tests for overriding compilers for langaugages 7833 with more than one implementation, such as C, C++, ObjC, ObjC++, and D. 7834 """ 7835 env = get_fake_env() 7836 getter = getattr(env, 'detect_{}_compiler'.format(lang)) 7837 getter = functools.partial(getter, for_machine) 7838 cc = getter() 7839 binary, newid = cb(cc) 7840 env.binaries[for_machine].binaries[lang] = binary 7841 compiler = getter() 7842 self.assertEqual(compiler.id, newid) 7843 7844 def test_multiple_native_files_override(self): 7845 wrapper = self.helper_create_binary_wrapper('bash', version='foo') 7846 config = self.helper_create_native_file({'binaries': {'bash': wrapper}}) 7847 wrapper = self.helper_create_binary_wrapper('bash', version='12345') 7848 config2 = self.helper_create_native_file({'binaries': {'bash': wrapper}}) 7849 self.init(self.testcase, extra_args=[ 7850 '--native-file', config, '--native-file', config2, 7851 '-Dcase=find_program']) 7852 7853 # This test hangs on cygwin. 7854 @unittest.skipIf(os.name != 'posix' or is_cygwin(), 'Uses fifos, which are not available on non Unix OSes.') 7855 def test_native_file_is_pipe(self): 7856 fifo = os.path.join(self.builddir, 'native.file') 7857 os.mkfifo(fifo) 7858 with tempfile.TemporaryDirectory() as d: 7859 wrapper = self.helper_create_binary_wrapper('bash', d, version='12345') 7860 7861 def filler(): 7862 with open(fifo, 'w') as f: 7863 f.write('[binaries]\n') 7864 f.write("bash = '{}'\n".format(wrapper)) 7865 7866 thread = threading.Thread(target=filler) 7867 thread.start() 7868 7869 self.init(self.testcase, extra_args=['--native-file', fifo, '-Dcase=find_program']) 7870 7871 thread.join() 7872 os.unlink(fifo) 7873 7874 self.init(self.testcase, extra_args=['--wipe']) 7875 7876 def test_multiple_native_files(self): 7877 wrapper = self.helper_create_binary_wrapper('bash', version='12345') 7878 config = self.helper_create_native_file({'binaries': {'bash': wrapper}}) 7879 wrapper = self.helper_create_binary_wrapper('python') 7880 config2 = self.helper_create_native_file({'binaries': {'python': wrapper}}) 7881 self.init(self.testcase, extra_args=[ 7882 '--native-file', config, '--native-file', config2, 7883 '-Dcase=find_program']) 7884 7885 def _simple_test(self, case, binary, entry=None): 7886 wrapper = self.helper_create_binary_wrapper(binary, version='12345') 7887 config = self.helper_create_native_file({'binaries': {entry or binary: wrapper}}) 7888 self.init(self.testcase, extra_args=['--native-file', config, '-Dcase={}'.format(case)]) 7889 7890 def test_find_program(self): 7891 self._simple_test('find_program', 'bash') 7892 7893 def test_config_tool_dep(self): 7894 # Do the skip at this level to avoid screwing up the cache 7895 if mesonbuild.environment.detect_msys2_arch(): 7896 raise unittest.SkipTest('Skipped due to problems with LLVM on MSYS2') 7897 if not shutil.which('llvm-config'): 7898 raise unittest.SkipTest('No llvm-installed, cannot test') 7899 self._simple_test('config_dep', 'llvm-config') 7900 7901 def test_python3_module(self): 7902 self._simple_test('python3', 'python3') 7903 7904 def test_python_module(self): 7905 if is_windows(): 7906 # Bat adds extra crap to stdout, so the version check logic in the 7907 # python module breaks. This is fine on other OSes because they 7908 # don't need the extra indirection. 7909 raise unittest.SkipTest('bat indirection breaks internal sanity checks.') 7910 elif is_osx(): 7911 binary = 'python' 7912 else: 7913 binary = 'python2' 7914 7915 # We not have python2, check for it 7916 for v in ['2', '2.7', '-2.7']: 7917 rc = subprocess.call(['pkg-config', '--cflags', 'python{}'.format(v)], 7918 stdout=subprocess.DEVNULL, 7919 stderr=subprocess.DEVNULL) 7920 if rc == 0: 7921 break 7922 else: 7923 raise unittest.SkipTest('Not running Python 2 tests because dev packages not installed.') 7924 self._simple_test('python', binary, entry='python') 7925 7926 @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard') 7927 @skip_if_env_set('CC') 7928 def test_c_compiler(self): 7929 def cb(comp): 7930 if comp.id == 'gcc': 7931 if not shutil.which('clang'): 7932 raise unittest.SkipTest('Only one compiler found, cannot test.') 7933 return 'clang', 'clang' 7934 if not is_real_gnu_compiler(shutil.which('gcc')): 7935 raise unittest.SkipTest('Only one compiler found, cannot test.') 7936 return 'gcc', 'gcc' 7937 self.helper_for_compiler('c', cb) 7938 7939 @unittest.skipIf(is_windows(), 'Setting up multiple compilers on windows is hard') 7940 @skip_if_env_set('CXX') 7941 def test_cpp_compiler(self): 7942 def cb(comp): 7943 if comp.id == 'gcc': 7944 if not shutil.which('clang++'): 7945 raise unittest.SkipTest('Only one compiler found, cannot test.') 7946 return 'clang++', 'clang' 7947 if not is_real_gnu_compiler(shutil.which('g++')): 7948 raise unittest.SkipTest('Only one compiler found, cannot test.') 7949 return 'g++', 'gcc' 7950 self.helper_for_compiler('cpp', cb) 7951 7952 @skip_if_not_language('objc') 7953 @skip_if_env_set('OBJC') 7954 def test_objc_compiler(self): 7955 def cb(comp): 7956 if comp.id == 'gcc': 7957 if not shutil.which('clang'): 7958 raise unittest.SkipTest('Only one compiler found, cannot test.') 7959 return 'clang', 'clang' 7960 if not is_real_gnu_compiler(shutil.which('gcc')): 7961 raise unittest.SkipTest('Only one compiler found, cannot test.') 7962 return 'gcc', 'gcc' 7963 self.helper_for_compiler('objc', cb) 7964 7965 @skip_if_not_language('objcpp') 7966 @skip_if_env_set('OBJCXX') 7967 def test_objcpp_compiler(self): 7968 def cb(comp): 7969 if comp.id == 'gcc': 7970 if not shutil.which('clang++'): 7971 raise unittest.SkipTest('Only one compiler found, cannot test.') 7972 return 'clang++', 'clang' 7973 if not is_real_gnu_compiler(shutil.which('g++')): 7974 raise unittest.SkipTest('Only one compiler found, cannot test.') 7975 return 'g++', 'gcc' 7976 self.helper_for_compiler('objcpp', cb) 7977 7978 @skip_if_not_language('d') 7979 @skip_if_env_set('DC') 7980 def test_d_compiler(self): 7981 def cb(comp): 7982 if comp.id == 'dmd': 7983 if shutil.which('ldc'): 7984 return 'ldc', 'ldc' 7985 elif shutil.which('gdc'): 7986 return 'gdc', 'gdc' 7987 else: 7988 raise unittest.SkipTest('No alternative dlang compiler found.') 7989 if shutil.which('dmd'): 7990 return 'dmd', 'dmd' 7991 raise unittest.SkipTest('No alternative dlang compiler found.') 7992 self.helper_for_compiler('d', cb) 7993 7994 @skip_if_not_language('cs') 7995 @skip_if_env_set('CSC') 7996 def test_cs_compiler(self): 7997 def cb(comp): 7998 if comp.id == 'csc': 7999 if not shutil.which('mcs'): 8000 raise unittest.SkipTest('No alternate C# implementation.') 8001 return 'mcs', 'mcs' 8002 if not shutil.which('csc'): 8003 raise unittest.SkipTest('No alternate C# implementation.') 8004 return 'csc', 'csc' 8005 self.helper_for_compiler('cs', cb) 8006 8007 @skip_if_not_language('fortran') 8008 @skip_if_env_set('FC') 8009 def test_fortran_compiler(self): 8010 def cb(comp): 8011 if comp.id == 'lcc': 8012 if shutil.which('lfortran'): 8013 return 'lfortran', 'lcc' 8014 raise unittest.SkipTest('No alternate Fortran implementation.') 8015 elif comp.id == 'gcc': 8016 if shutil.which('ifort'): 8017 # There is an ICC for windows (windows build, linux host), 8018 # but we don't support that ATM so lets not worry about it. 8019 if is_windows(): 8020 return 'ifort', 'intel-cl' 8021 return 'ifort', 'intel' 8022 elif shutil.which('flang'): 8023 return 'flang', 'flang' 8024 elif shutil.which('pgfortran'): 8025 return 'pgfortran', 'pgi' 8026 # XXX: there are several other fortran compilers meson 8027 # supports, but I don't have any of them to test with 8028 raise unittest.SkipTest('No alternate Fortran implementation.') 8029 if not shutil.which('gfortran'): 8030 raise unittest.SkipTest('No alternate Fortran implementation.') 8031 return 'gfortran', 'gcc' 8032 self.helper_for_compiler('fortran', cb) 8033 8034 def _single_implementation_compiler(self, lang, binary, version_str, version): 8035 """Helper for languages with a single (supported) implementation. 8036 8037 Builds a wrapper around the compiler to override the version. 8038 """ 8039 wrapper = self.helper_create_binary_wrapper(binary, version=version_str) 8040 env = get_fake_env() 8041 getter = getattr(env, 'detect_{}_compiler'.format(lang)) 8042 getter = functools.partial(getter, MachineChoice.HOST) 8043 env.binaries.host.binaries[lang] = wrapper 8044 compiler = getter() 8045 self.assertEqual(compiler.version, version) 8046 8047 @skip_if_not_language('vala') 8048 @skip_if_env_set('VALAC') 8049 def test_vala_compiler(self): 8050 self._single_implementation_compiler( 8051 'vala', 'valac', 'Vala 1.2345', '1.2345') 8052 8053 @skip_if_not_language('rust') 8054 @skip_if_env_set('RUSTC') 8055 def test_rust_compiler(self): 8056 self._single_implementation_compiler( 8057 'rust', 'rustc', 'rustc 1.2345', '1.2345') 8058 8059 @skip_if_not_language('java') 8060 def test_java_compiler(self): 8061 self._single_implementation_compiler( 8062 'java', 'javac', 'javac 9.99.77', '9.99.77') 8063 8064 @skip_if_not_language('swift') 8065 def test_swift_compiler(self): 8066 wrapper = self.helper_create_binary_wrapper( 8067 'swiftc', version='Swift 1.2345', outfile='stderr', 8068 extra_args={'Xlinker': 'macosx_version. PROJECT:ld - 1.2.3'}) 8069 env = get_fake_env() 8070 env.binaries.host.binaries['swift'] = wrapper 8071 compiler = env.detect_swift_compiler(MachineChoice.HOST) 8072 self.assertEqual(compiler.version, '1.2345') 8073 8074 def test_native_file_dirs(self): 8075 testcase = os.path.join(self.unit_test_dir, '60 native file override') 8076 self.init(testcase, default_args=False, 8077 extra_args=['--native-file', os.path.join(testcase, 'nativefile')]) 8078 8079 def test_native_file_dirs_overriden(self): 8080 testcase = os.path.join(self.unit_test_dir, '60 native file override') 8081 self.init(testcase, default_args=False, 8082 extra_args=['--native-file', os.path.join(testcase, 'nativefile'), 8083 '-Ddef_libdir=liblib', '-Dlibdir=liblib']) 8084 8085 def test_compile_sys_path(self): 8086 """Compiling with a native file stored in a system path works. 8087 8088 There was a bug which caused the paths to be stored incorrectly and 8089 would result in ninja invoking meson in an infinite loop. This tests 8090 for that by actually invoking ninja. 8091 """ 8092 testcase = os.path.join(self.common_test_dir, '1 trivial') 8093 8094 # It really doesn't matter what's in the native file, just that it exists 8095 config = self.helper_create_native_file({'binaries': {'bash': 'false'}}) 8096 8097 self.init(testcase, extra_args=['--native-file', config]) 8098 self.build() 8099 8100 8101class CrossFileTests(BasePlatformTests): 8102 8103 """Tests for cross file functionality not directly related to 8104 cross compiling. 8105 8106 This is mainly aimed to testing overrides from cross files. 8107 """ 8108 8109 def _cross_file_generator(self, *, needs_exe_wrapper: bool = False, 8110 exe_wrapper: T.Optional[T.List[str]] = None) -> str: 8111 if is_windows(): 8112 raise unittest.SkipTest('Cannot run this test on non-mingw/non-cygwin windows') 8113 if is_sunos(): 8114 cc = 'gcc' 8115 else: 8116 cc = 'cc' 8117 8118 return textwrap.dedent("""\ 8119 [binaries] 8120 c = '/usr/bin/{}' 8121 ar = '/usr/bin/ar' 8122 strip = '/usr/bin/ar' 8123 {} 8124 8125 [properties] 8126 needs_exe_wrapper = {} 8127 8128 [host_machine] 8129 system = 'linux' 8130 cpu_family = 'x86' 8131 cpu = 'i686' 8132 endian = 'little' 8133 """.format(cc, 8134 'exe_wrapper = {}'.format(str(exe_wrapper)) if exe_wrapper is not None else '', 8135 needs_exe_wrapper)) 8136 8137 def _stub_exe_wrapper(self) -> str: 8138 return textwrap.dedent('''\ 8139 #!/usr/bin/env python3 8140 import subprocess 8141 import sys 8142 8143 sys.exit(subprocess.run(sys.argv[1:]).returncode) 8144 ''') 8145 8146 def test_needs_exe_wrapper_true(self): 8147 testdir = os.path.join(self.unit_test_dir, '72 cross test passed') 8148 with tempfile.TemporaryDirectory() as d: 8149 p = Path(d) / 'crossfile' 8150 with p.open('wt') as f: 8151 f.write(self._cross_file_generator(needs_exe_wrapper=True)) 8152 self.init(testdir, extra_args=['--cross-file=' + str(p)]) 8153 out = self.run_target('test') 8154 self.assertRegex(out, r'Skipped:\s*1\s*\n') 8155 8156 def test_needs_exe_wrapper_false(self): 8157 testdir = os.path.join(self.unit_test_dir, '72 cross test passed') 8158 with tempfile.TemporaryDirectory() as d: 8159 p = Path(d) / 'crossfile' 8160 with p.open('wt') as f: 8161 f.write(self._cross_file_generator(needs_exe_wrapper=False)) 8162 self.init(testdir, extra_args=['--cross-file=' + str(p)]) 8163 out = self.run_target('test') 8164 self.assertNotRegex(out, r'Skipped:\s*1\n') 8165 8166 def test_needs_exe_wrapper_true_wrapper(self): 8167 testdir = os.path.join(self.unit_test_dir, '72 cross test passed') 8168 with tempfile.TemporaryDirectory() as d: 8169 s = Path(d) / 'wrapper.py' 8170 with s.open('wt') as f: 8171 f.write(self._stub_exe_wrapper()) 8172 s.chmod(0o774) 8173 p = Path(d) / 'crossfile' 8174 with p.open('wt') as f: 8175 f.write(self._cross_file_generator( 8176 needs_exe_wrapper=True, 8177 exe_wrapper=[str(s)])) 8178 8179 self.init(testdir, extra_args=['--cross-file=' + str(p), '-Dexpect=true']) 8180 out = self.run_target('test') 8181 self.assertRegex(out, r'Ok:\s*3\s*\n') 8182 8183 def test_cross_exe_passed_no_wrapper(self): 8184 testdir = os.path.join(self.unit_test_dir, '72 cross test passed') 8185 with tempfile.TemporaryDirectory() as d: 8186 p = Path(d) / 'crossfile' 8187 with p.open('wt') as f: 8188 f.write(self._cross_file_generator(needs_exe_wrapper=True)) 8189 8190 self.init(testdir, extra_args=['--cross-file=' + str(p)]) 8191 self.build() 8192 out = self.run_target('test') 8193 self.assertRegex(out, r'Skipped:\s*1\s*\n') 8194 8195 # The test uses mocking and thus requires that the current process is the 8196 # one to run the Meson steps. If we are using an external test executable 8197 # (most commonly in Debian autopkgtests) then the mocking won't work. 8198 @unittest.skipIf('MESON_EXE' in os.environ, 'MESON_EXE is defined, can not use mocking.') 8199 def test_cross_file_system_paths(self): 8200 if is_windows(): 8201 raise unittest.SkipTest('system crossfile paths not defined for Windows (yet)') 8202 8203 testdir = os.path.join(self.common_test_dir, '1 trivial') 8204 cross_content = self._cross_file_generator() 8205 with tempfile.TemporaryDirectory() as d: 8206 dir_ = os.path.join(d, 'meson', 'cross') 8207 os.makedirs(dir_) 8208 with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f: 8209 f.write(cross_content) 8210 name = os.path.basename(f.name) 8211 8212 with mock.patch.dict(os.environ, {'XDG_DATA_HOME': d}): 8213 self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) 8214 self.wipe() 8215 8216 with mock.patch.dict(os.environ, {'XDG_DATA_DIRS': d}): 8217 os.environ.pop('XDG_DATA_HOME', None) 8218 self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) 8219 self.wipe() 8220 8221 with tempfile.TemporaryDirectory() as d: 8222 dir_ = os.path.join(d, '.local', 'share', 'meson', 'cross') 8223 os.makedirs(dir_) 8224 with tempfile.NamedTemporaryFile('w', dir=dir_, delete=False) as f: 8225 f.write(cross_content) 8226 name = os.path.basename(f.name) 8227 8228 # If XDG_DATA_HOME is set in the environment running the 8229 # tests this test will fail, os mock the environment, pop 8230 # it, then test 8231 with mock.patch.dict(os.environ): 8232 os.environ.pop('XDG_DATA_HOME', None) 8233 with mock.patch('mesonbuild.coredata.os.path.expanduser', lambda x: x.replace('~', d)): 8234 self.init(testdir, extra_args=['--cross-file=' + name], inprocess=True) 8235 self.wipe() 8236 8237 def test_cross_file_dirs(self): 8238 testcase = os.path.join(self.unit_test_dir, '60 native file override') 8239 self.init(testcase, default_args=False, 8240 extra_args=['--native-file', os.path.join(testcase, 'nativefile'), 8241 '--cross-file', os.path.join(testcase, 'crossfile'), 8242 '-Ddef_bindir=binbar', 8243 '-Ddef_datadir=databar', 8244 '-Ddef_includedir=includebar', 8245 '-Ddef_infodir=infobar', 8246 '-Ddef_libdir=libbar', 8247 '-Ddef_libexecdir=libexecbar', 8248 '-Ddef_localedir=localebar', 8249 '-Ddef_localstatedir=localstatebar', 8250 '-Ddef_mandir=manbar', 8251 '-Ddef_sbindir=sbinbar', 8252 '-Ddef_sharedstatedir=sharedstatebar', 8253 '-Ddef_sysconfdir=sysconfbar']) 8254 8255 def test_cross_file_dirs_overriden(self): 8256 testcase = os.path.join(self.unit_test_dir, '60 native file override') 8257 self.init(testcase, default_args=False, 8258 extra_args=['--native-file', os.path.join(testcase, 'nativefile'), 8259 '--cross-file', os.path.join(testcase, 'crossfile'), 8260 '-Ddef_libdir=liblib', '-Dlibdir=liblib', 8261 '-Ddef_bindir=binbar', 8262 '-Ddef_datadir=databar', 8263 '-Ddef_includedir=includebar', 8264 '-Ddef_infodir=infobar', 8265 '-Ddef_libexecdir=libexecbar', 8266 '-Ddef_localedir=localebar', 8267 '-Ddef_localstatedir=localstatebar', 8268 '-Ddef_mandir=manbar', 8269 '-Ddef_sbindir=sbinbar', 8270 '-Ddef_sharedstatedir=sharedstatebar', 8271 '-Ddef_sysconfdir=sysconfbar']) 8272 8273 def test_cross_file_dirs_chain(self): 8274 # crossfile2 overrides crossfile overrides nativefile 8275 testcase = os.path.join(self.unit_test_dir, '60 native file override') 8276 self.init(testcase, default_args=False, 8277 extra_args=['--native-file', os.path.join(testcase, 'nativefile'), 8278 '--cross-file', os.path.join(testcase, 'crossfile'), 8279 '--cross-file', os.path.join(testcase, 'crossfile2'), 8280 '-Ddef_bindir=binbar2', 8281 '-Ddef_datadir=databar', 8282 '-Ddef_includedir=includebar', 8283 '-Ddef_infodir=infobar', 8284 '-Ddef_libdir=libbar', 8285 '-Ddef_libexecdir=libexecbar', 8286 '-Ddef_localedir=localebar', 8287 '-Ddef_localstatedir=localstatebar', 8288 '-Ddef_mandir=manbar', 8289 '-Ddef_sbindir=sbinbar', 8290 '-Ddef_sharedstatedir=sharedstatebar', 8291 '-Ddef_sysconfdir=sysconfbar']) 8292 8293class TAPParserTests(unittest.TestCase): 8294 def assert_test(self, events, **kwargs): 8295 if 'explanation' not in kwargs: 8296 kwargs['explanation'] = None 8297 self.assertEqual(next(events), TAPParser.Test(**kwargs)) 8298 8299 def assert_plan(self, events, **kwargs): 8300 if 'skipped' not in kwargs: 8301 kwargs['skipped'] = False 8302 if 'explanation' not in kwargs: 8303 kwargs['explanation'] = None 8304 self.assertEqual(next(events), TAPParser.Plan(**kwargs)) 8305 8306 def assert_version(self, events, **kwargs): 8307 self.assertEqual(next(events), TAPParser.Version(**kwargs)) 8308 8309 def assert_error(self, events): 8310 self.assertEqual(type(next(events)), TAPParser.Error) 8311 8312 def assert_bailout(self, events, **kwargs): 8313 self.assertEqual(next(events), TAPParser.Bailout(**kwargs)) 8314 8315 def assert_last(self, events): 8316 with self.assertRaises(StopIteration): 8317 next(events) 8318 8319 def parse_tap(self, s): 8320 parser = TAPParser(io.StringIO(s)) 8321 return iter(parser.parse()) 8322 8323 def parse_tap_v13(self, s): 8324 events = self.parse_tap('TAP version 13\n' + s) 8325 self.assert_version(events, version=13) 8326 return events 8327 8328 def test_empty(self): 8329 events = self.parse_tap('') 8330 self.assert_last(events) 8331 8332 def test_empty_plan(self): 8333 events = self.parse_tap('1..0') 8334 self.assert_plan(events, count=0, late=False, skipped=True) 8335 self.assert_last(events) 8336 8337 def test_plan_directive(self): 8338 events = self.parse_tap('1..0 # skipped for some reason') 8339 self.assert_plan(events, count=0, late=False, skipped=True, 8340 explanation='for some reason') 8341 self.assert_last(events) 8342 8343 events = self.parse_tap('1..1 # skipped for some reason\nok 1') 8344 self.assert_error(events) 8345 self.assert_plan(events, count=1, late=False, skipped=True, 8346 explanation='for some reason') 8347 self.assert_test(events, number=1, name='', result=TestResult.OK) 8348 self.assert_last(events) 8349 8350 events = self.parse_tap('1..1 # todo not supported here\nok 1') 8351 self.assert_error(events) 8352 self.assert_plan(events, count=1, late=False, skipped=False, 8353 explanation='not supported here') 8354 self.assert_test(events, number=1, name='', result=TestResult.OK) 8355 self.assert_last(events) 8356 8357 def test_one_test_ok(self): 8358 events = self.parse_tap('ok') 8359 self.assert_test(events, number=1, name='', result=TestResult.OK) 8360 self.assert_last(events) 8361 8362 def test_one_test_with_number(self): 8363 events = self.parse_tap('ok 1') 8364 self.assert_test(events, number=1, name='', result=TestResult.OK) 8365 self.assert_last(events) 8366 8367 def test_one_test_with_name(self): 8368 events = self.parse_tap('ok 1 abc') 8369 self.assert_test(events, number=1, name='abc', result=TestResult.OK) 8370 self.assert_last(events) 8371 8372 def test_one_test_not_ok(self): 8373 events = self.parse_tap('not ok') 8374 self.assert_test(events, number=1, name='', result=TestResult.FAIL) 8375 self.assert_last(events) 8376 8377 def test_one_test_todo(self): 8378 events = self.parse_tap('not ok 1 abc # TODO') 8379 self.assert_test(events, number=1, name='abc', result=TestResult.EXPECTEDFAIL) 8380 self.assert_last(events) 8381 8382 events = self.parse_tap('ok 1 abc # TODO') 8383 self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS) 8384 self.assert_last(events) 8385 8386 def test_one_test_skip(self): 8387 events = self.parse_tap('ok 1 abc # SKIP') 8388 self.assert_test(events, number=1, name='abc', result=TestResult.SKIP) 8389 self.assert_last(events) 8390 8391 def test_one_test_skip_failure(self): 8392 events = self.parse_tap('not ok 1 abc # SKIP') 8393 self.assert_test(events, number=1, name='abc', result=TestResult.FAIL) 8394 self.assert_last(events) 8395 8396 def test_many_early_plan(self): 8397 events = self.parse_tap('1..4\nok 1\nnot ok 2\nok 3\nnot ok 4') 8398 self.assert_plan(events, count=4, late=False) 8399 self.assert_test(events, number=1, name='', result=TestResult.OK) 8400 self.assert_test(events, number=2, name='', result=TestResult.FAIL) 8401 self.assert_test(events, number=3, name='', result=TestResult.OK) 8402 self.assert_test(events, number=4, name='', result=TestResult.FAIL) 8403 self.assert_last(events) 8404 8405 def test_many_late_plan(self): 8406 events = self.parse_tap('ok 1\nnot ok 2\nok 3\nnot ok 4\n1..4') 8407 self.assert_test(events, number=1, name='', result=TestResult.OK) 8408 self.assert_test(events, number=2, name='', result=TestResult.FAIL) 8409 self.assert_test(events, number=3, name='', result=TestResult.OK) 8410 self.assert_test(events, number=4, name='', result=TestResult.FAIL) 8411 self.assert_plan(events, count=4, late=True) 8412 self.assert_last(events) 8413 8414 def test_directive_case(self): 8415 events = self.parse_tap('ok 1 abc # skip') 8416 self.assert_test(events, number=1, name='abc', result=TestResult.SKIP) 8417 self.assert_last(events) 8418 8419 events = self.parse_tap('ok 1 abc # ToDo') 8420 self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS) 8421 self.assert_last(events) 8422 8423 def test_directive_explanation(self): 8424 events = self.parse_tap('ok 1 abc # skip why') 8425 self.assert_test(events, number=1, name='abc', result=TestResult.SKIP, 8426 explanation='why') 8427 self.assert_last(events) 8428 8429 events = self.parse_tap('ok 1 abc # ToDo Because') 8430 self.assert_test(events, number=1, name='abc', result=TestResult.UNEXPECTEDPASS, 8431 explanation='Because') 8432 self.assert_last(events) 8433 8434 def test_one_test_early_plan(self): 8435 events = self.parse_tap('1..1\nok') 8436 self.assert_plan(events, count=1, late=False) 8437 self.assert_test(events, number=1, name='', result=TestResult.OK) 8438 self.assert_last(events) 8439 8440 def test_one_test_late_plan(self): 8441 events = self.parse_tap('ok\n1..1') 8442 self.assert_test(events, number=1, name='', result=TestResult.OK) 8443 self.assert_plan(events, count=1, late=True) 8444 self.assert_last(events) 8445 8446 def test_out_of_order(self): 8447 events = self.parse_tap('ok 2') 8448 self.assert_error(events) 8449 self.assert_test(events, number=2, name='', result=TestResult.OK) 8450 self.assert_last(events) 8451 8452 def test_middle_plan(self): 8453 events = self.parse_tap('ok 1\n1..2\nok 2') 8454 self.assert_test(events, number=1, name='', result=TestResult.OK) 8455 self.assert_plan(events, count=2, late=True) 8456 self.assert_error(events) 8457 self.assert_test(events, number=2, name='', result=TestResult.OK) 8458 self.assert_last(events) 8459 8460 def test_too_many_plans(self): 8461 events = self.parse_tap('1..1\n1..2\nok 1') 8462 self.assert_plan(events, count=1, late=False) 8463 self.assert_error(events) 8464 self.assert_test(events, number=1, name='', result=TestResult.OK) 8465 self.assert_last(events) 8466 8467 def test_too_many(self): 8468 events = self.parse_tap('ok 1\nnot ok 2\n1..1') 8469 self.assert_test(events, number=1, name='', result=TestResult.OK) 8470 self.assert_test(events, number=2, name='', result=TestResult.FAIL) 8471 self.assert_plan(events, count=1, late=True) 8472 self.assert_error(events) 8473 self.assert_last(events) 8474 8475 events = self.parse_tap('1..1\nok 1\nnot ok 2') 8476 self.assert_plan(events, count=1, late=False) 8477 self.assert_test(events, number=1, name='', result=TestResult.OK) 8478 self.assert_test(events, number=2, name='', result=TestResult.FAIL) 8479 self.assert_error(events) 8480 self.assert_last(events) 8481 8482 def test_too_few(self): 8483 events = self.parse_tap('ok 1\nnot ok 2\n1..3') 8484 self.assert_test(events, number=1, name='', result=TestResult.OK) 8485 self.assert_test(events, number=2, name='', result=TestResult.FAIL) 8486 self.assert_plan(events, count=3, late=True) 8487 self.assert_error(events) 8488 self.assert_last(events) 8489 8490 events = self.parse_tap('1..3\nok 1\nnot ok 2') 8491 self.assert_plan(events, count=3, late=False) 8492 self.assert_test(events, number=1, name='', result=TestResult.OK) 8493 self.assert_test(events, number=2, name='', result=TestResult.FAIL) 8494 self.assert_error(events) 8495 self.assert_last(events) 8496 8497 def test_too_few_bailout(self): 8498 events = self.parse_tap('1..3\nok 1\nnot ok 2\nBail out! no third test') 8499 self.assert_plan(events, count=3, late=False) 8500 self.assert_test(events, number=1, name='', result=TestResult.OK) 8501 self.assert_test(events, number=2, name='', result=TestResult.FAIL) 8502 self.assert_bailout(events, message='no third test') 8503 self.assert_last(events) 8504 8505 def test_diagnostics(self): 8506 events = self.parse_tap('1..1\n# ignored\nok 1') 8507 self.assert_plan(events, count=1, late=False) 8508 self.assert_test(events, number=1, name='', result=TestResult.OK) 8509 self.assert_last(events) 8510 8511 events = self.parse_tap('# ignored\n1..1\nok 1\n# ignored too') 8512 self.assert_plan(events, count=1, late=False) 8513 self.assert_test(events, number=1, name='', result=TestResult.OK) 8514 self.assert_last(events) 8515 8516 events = self.parse_tap('# ignored\nok 1\n1..1\n# ignored too') 8517 self.assert_test(events, number=1, name='', result=TestResult.OK) 8518 self.assert_plan(events, count=1, late=True) 8519 self.assert_last(events) 8520 8521 def test_empty_line(self): 8522 events = self.parse_tap('1..1\n\nok 1') 8523 self.assert_plan(events, count=1, late=False) 8524 self.assert_test(events, number=1, name='', result=TestResult.OK) 8525 self.assert_last(events) 8526 8527 def test_unexpected(self): 8528 events = self.parse_tap('1..1\ninvalid\nok 1') 8529 self.assert_plan(events, count=1, late=False) 8530 self.assert_error(events) 8531 self.assert_test(events, number=1, name='', result=TestResult.OK) 8532 self.assert_last(events) 8533 8534 def test_version(self): 8535 events = self.parse_tap('TAP version 13\n') 8536 self.assert_version(events, version=13) 8537 self.assert_last(events) 8538 8539 events = self.parse_tap('TAP version 12\n') 8540 self.assert_error(events) 8541 self.assert_last(events) 8542 8543 events = self.parse_tap('1..0\nTAP version 13\n') 8544 self.assert_plan(events, count=0, late=False, skipped=True) 8545 self.assert_error(events) 8546 self.assert_last(events) 8547 8548 def test_yaml(self): 8549 events = self.parse_tap_v13('ok\n ---\n foo: abc\n bar: def\n ...\nok 2') 8550 self.assert_test(events, number=1, name='', result=TestResult.OK) 8551 self.assert_test(events, number=2, name='', result=TestResult.OK) 8552 self.assert_last(events) 8553 8554 events = self.parse_tap_v13('ok\n ---\n foo: abc\n bar: def') 8555 self.assert_test(events, number=1, name='', result=TestResult.OK) 8556 self.assert_error(events) 8557 self.assert_last(events) 8558 8559 events = self.parse_tap_v13('ok 1\n ---\n foo: abc\n bar: def\nnot ok 2') 8560 self.assert_test(events, number=1, name='', result=TestResult.OK) 8561 self.assert_error(events) 8562 self.assert_test(events, number=2, name='', result=TestResult.FAIL) 8563 self.assert_last(events) 8564 8565 8566def _clang_at_least(compiler, minver: str, apple_minver: str) -> bool: 8567 """ 8568 check that Clang compiler is at least a specified version, whether AppleClang or regular Clang 8569 8570 Parameters 8571 ---------- 8572 compiler: 8573 Meson compiler object 8574 minver: str 8575 Clang minimum version 8576 apple_minver: str 8577 AppleCLang minimum version 8578 8579 Returns 8580 ------- 8581 at_least: bool 8582 Clang is at least the specified version 8583 """ 8584 if isinstance(compiler, (mesonbuild.compilers.AppleClangCCompiler, 8585 mesonbuild.compilers.AppleClangCPPCompiler)): 8586 return version_compare(compiler.version, apple_minver) 8587 return version_compare(compiler.version, minver) 8588 8589 8590def unset_envs(): 8591 # For unit tests we must fully control all command lines 8592 # so that there are no unexpected changes coming from the 8593 # environment, for example when doing a package build. 8594 varnames = ['CPPFLAGS', 'LDFLAGS'] + list(mesonbuild.compilers.compilers.cflags_mapping.values()) 8595 for v in varnames: 8596 if v in os.environ: 8597 del os.environ[v] 8598 8599def convert_args(argv): 8600 # If we got passed a list of tests, pass it on 8601 pytest_args = ['-v'] if '-v' in argv else [] 8602 test_list = [] 8603 for arg in argv: 8604 if arg.startswith('-'): 8605 if arg in ('-f', '--failfast'): 8606 arg = '--exitfirst' 8607 pytest_args.append(arg) 8608 continue 8609 # ClassName.test_name => 'ClassName and test_name' 8610 if '.' in arg: 8611 arg = ' and '.join(arg.split('.')) 8612 test_list.append(arg) 8613 if test_list: 8614 pytest_args += ['-k', ' or '.join(test_list)] 8615 return pytest_args 8616 8617def main(): 8618 unset_envs() 8619 try: 8620 import pytest # noqa: F401 8621 # Need pytest-xdist for `-n` arg 8622 import xdist # noqa: F401 8623 pytest_args = ['-n', 'auto', './run_unittests.py'] 8624 pytest_args += convert_args(sys.argv[1:]) 8625 return subprocess.run(python_command + ['-m', 'pytest'] + pytest_args).returncode 8626 except ImportError: 8627 print('pytest-xdist not found, using unittest instead') 8628 # All attempts at locating pytest failed, fall back to plain unittest. 8629 cases = ['InternalTests', 'DataTests', 'AllPlatformTests', 'FailureTests', 8630 'PythonTests', 'NativeFileTests', 'RewriterTests', 'CrossFileTests', 8631 'TAPParserTests', 8632 8633 'LinuxlikeTests', 'LinuxCrossArmTests', 'LinuxCrossMingwTests', 8634 'WindowsTests', 'DarwinTests'] 8635 8636 return unittest.main(defaultTest=cases, buffer=True) 8637 8638if __name__ == '__main__': 8639 print('Meson build system', mesonbuild.coredata.version, 'Unit Tests') 8640 raise SystemExit(main()) 8641