blob: 411c97b36036f457157a110d253f7458371c5759 [file] [log] [blame]
# SPDX-License-Identifier: Apache-2.0
# Copyright 2016-2021 The Meson development team
from configparser import ConfigParser
from pathlib import Path
from unittest import mock
import contextlib
import io
import json
import operator
import os
import pickle
import stat
import subprocess
import tempfile
import typing as T
import unittest
import mesonbuild.mlog
import mesonbuild.depfile
import mesonbuild.dependencies.base
import mesonbuild.dependencies.factory
import mesonbuild.envconfig
import mesonbuild.environment
import mesonbuild.modules.gnome
from mesonbuild import coredata
from mesonbuild.compilers.c import ClangCCompiler, GnuCCompiler
from mesonbuild.compilers.cpp import VisualStudioCPPCompiler
from mesonbuild.compilers.d import DmdDCompiler
from mesonbuild.linkers import linkers
from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments, ObjectHolder
from mesonbuild.interpreterbase import typed_pos_args, InvalidArguments, typed_kwargs, ContainerTypeInfo, KwargInfo
from mesonbuild.mesonlib import (
LibType, MachineChoice, PerMachine, Version, is_windows, is_osx,
is_cygwin, is_openbsd, search_version, MesonException, OptionKey,
OptionType
)
from mesonbuild.interpreter.type_checking import in_set_validator, NoneType
from mesonbuild.dependencies.pkgconfig import PkgConfigDependency, PkgConfigInterface, PkgConfigCLI
from mesonbuild.programs import ExternalProgram
import mesonbuild.modules.pkgconfig
from run_tests import (
FakeCompilerOptions, get_fake_env, get_fake_options
)
from .helpers import *
class InternalTests(unittest.TestCase):
def test_version_number(self):
self.assertEqual(search_version('foobar 1.2.3'), '1.2.3')
self.assertEqual(search_version('1.2.3'), '1.2.3')
self.assertEqual(search_version('foobar 2016.10.28 1.2.3'), '1.2.3')
self.assertEqual(search_version('2016.10.28 1.2.3'), '1.2.3')
self.assertEqual(search_version('foobar 2016.10.128'), '2016.10.128')
self.assertEqual(search_version('2016.10.128'), '2016.10.128')
self.assertEqual(search_version('2016.10'), '2016.10')
self.assertEqual(search_version('2016.10 1.2.3'), '1.2.3')
self.assertEqual(search_version('oops v1.2.3'), '1.2.3')
self.assertEqual(search_version('2016.oops 1.2.3'), '1.2.3')
self.assertEqual(search_version('2016.x'), 'unknown version')
self.assertEqual(search_version(r'something version is \033[32;2m1.2.0\033[0m.'), '1.2.0')
# Literal output of mvn
self.assertEqual(search_version(r'''\
\033[1mApache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)\033[0m
Maven home: /nix/store/g84a9wnid2h1d3z2wfydy16dky73wh7i-apache-maven-3.8.1/maven
Java version: 11.0.10, vendor: Oracle Corporation, runtime: /nix/store/afsnl4ahmm9svvl7s1a0cj41vw4nkmz4-openjdk-11.0.10+9/lib/openjdk
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "5.12.17", arch: "amd64", family: "unix"'''),
'3.8.1')
def test_mode_symbolic_to_bits(self):
modefunc = mesonbuild.mesonlib.FileMode.perms_s_to_bits
self.assertEqual(modefunc('---------'), 0)
self.assertEqual(modefunc('r--------'), stat.S_IRUSR)
self.assertEqual(modefunc('---r-----'), stat.S_IRGRP)
self.assertEqual(modefunc('------r--'), stat.S_IROTH)
self.assertEqual(modefunc('-w-------'), stat.S_IWUSR)
self.assertEqual(modefunc('----w----'), stat.S_IWGRP)
self.assertEqual(modefunc('-------w-'), stat.S_IWOTH)
self.assertEqual(modefunc('--x------'), stat.S_IXUSR)
self.assertEqual(modefunc('-----x---'), stat.S_IXGRP)
self.assertEqual(modefunc('--------x'), stat.S_IXOTH)
self.assertEqual(modefunc('--S------'), stat.S_ISUID)
self.assertEqual(modefunc('-----S---'), stat.S_ISGID)
self.assertEqual(modefunc('--------T'), stat.S_ISVTX)
self.assertEqual(modefunc('--s------'), stat.S_ISUID | stat.S_IXUSR)
self.assertEqual(modefunc('-----s---'), stat.S_ISGID | stat.S_IXGRP)
self.assertEqual(modefunc('--------t'), stat.S_ISVTX | stat.S_IXOTH)
self.assertEqual(modefunc('rwx------'), stat.S_IRWXU)
self.assertEqual(modefunc('---rwx---'), stat.S_IRWXG)
self.assertEqual(modefunc('------rwx'), stat.S_IRWXO)
# We could keep listing combinations exhaustively but that seems
# tedious and pointless. Just test a few more.
self.assertEqual(modefunc('rwxr-xr-x'),
stat.S_IRWXU |
stat.S_IRGRP | stat.S_IXGRP |
stat.S_IROTH | stat.S_IXOTH)
self.assertEqual(modefunc('rw-r--r--'),
stat.S_IRUSR | stat.S_IWUSR |
stat.S_IRGRP |
stat.S_IROTH)
self.assertEqual(modefunc('rwsr-x---'),
stat.S_IRWXU | stat.S_ISUID |
stat.S_IRGRP | stat.S_IXGRP)
def test_compiler_args_class_none_flush(self):
cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock())
a = cc.compiler_args(['-I.'])
#first we are checking if the tree construction deduplicates the correct -I argument
a += ['-I..']
a += ['-I./tests/']
a += ['-I./tests2/']
#think this here as assertion, we cannot apply it, otherwise the CompilerArgs would already flush the changes:
# assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..', '-I.'])
a += ['-I.']
a += ['-I.', '-I./tests/']
self.assertEqual(a, ['-I.', '-I./tests/', '-I./tests2/', '-I..'])
#then we are checking that when CompilerArgs already have a build container list, that the deduplication is taking the correct one
a += ['-I.', '-I./tests2/']
self.assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..'])
def test_compiler_args_class_d(self):
d = DmdDCompiler([], 'fake', MachineChoice.HOST, 'info', 'arch')
# check include order is kept when deduplicating
a = d.compiler_args(['-Ifirst', '-Isecond', '-Ithird'])
a += ['-Ifirst']
self.assertEqual(a, ['-Ifirst', '-Isecond', '-Ithird'])
def test_compiler_args_class_clike(self):
cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock())
# Test that empty initialization works
a = cc.compiler_args()
self.assertEqual(a, [])
# Test that list initialization works
a = cc.compiler_args(['-I.', '-I..'])
self.assertEqual(a, ['-I.', '-I..'])
# Test that there is no de-dup on initialization
self.assertEqual(cc.compiler_args(['-I.', '-I.']), ['-I.', '-I.'])
## Test that appending works
a.append('-I..')
self.assertEqual(a, ['-I..', '-I.'])
a.append('-O3')
self.assertEqual(a, ['-I..', '-I.', '-O3'])
## Test that in-place addition works
a += ['-O2', '-O2']
self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2', '-O2'])
# Test that removal works
a.remove('-O2')
self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2'])
# Test that de-dup happens on addition
a += ['-Ifoo', '-Ifoo']
self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
# .extend() is just +=, so we don't test it
## Test that addition works
# Test that adding a list with just one old arg works and yields the same array
a = a + ['-Ifoo']
self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
# Test that adding a list with one arg new and one old works
a = a + ['-Ifoo', '-Ibaz']
self.assertEqual(a, ['-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2'])
# Test that adding args that must be prepended and appended works
a = a + ['-Ibar', '-Wall']
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
## Test that reflected addition works
# Test that adding to a list with just one old arg works and yields the same array
a = ['-Ifoo'] + a
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
# Test that adding to a list with just one new arg that is not pre-pended works
a = ['-Werror'] + a
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Werror', '-O3', '-O2', '-Wall'])
# Test that adding to a list with two new args preserves the order
a = ['-Ldir', '-Lbah'] + a
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
# Test that adding to a list with old args does nothing
a = ['-Ibar', '-Ibaz', '-Ifoo'] + a
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
## Test that adding libraries works
l = cc.compiler_args(['-Lfoodir', '-lfoo'])
self.assertEqual(l, ['-Lfoodir', '-lfoo'])
# Adding a library and a libpath appends both correctly
l += ['-Lbardir', '-lbar']
self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
# Adding the same library again does nothing
l += ['-lbar']
self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
## Test that 'direct' append and extend works
l = cc.compiler_args(['-Lfoodir', '-lfoo'])
self.assertEqual(l, ['-Lfoodir', '-lfoo'])
# Direct-adding a library and a libpath appends both correctly
l.extend_direct(['-Lbardir', '-lbar'])
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar'])
# Direct-adding the same library again still adds it
l.append_direct('-lbar')
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar'])
# Direct-adding with absolute path deduplicates
l.append_direct('/libbaz.a')
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
# Adding libbaz again does nothing
l.append_direct('/libbaz.a')
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
def test_compiler_args_class_visualstudio(self):
linker = linkers.MSVCDynamicLinker(MachineChoice.HOST, [])
# Version just needs to be > 19.0.0
cc = VisualStudioCPPCompiler([], [], '20.00', MachineChoice.HOST, False, mock.Mock(), 'x64', linker=linker)
a = cc.compiler_args(cc.get_always_args())
self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/utf-8', '/Zc:__cplusplus'])
# Ensure /source-charset: removes /utf-8
a.append('/source-charset:utf-8')
self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/Zc:__cplusplus', '/source-charset:utf-8'])
# Ensure /execution-charset: removes /utf-8
a = cc.compiler_args(cc.get_always_args() + ['/execution-charset:utf-8'])
self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/Zc:__cplusplus', '/execution-charset:utf-8'])
# Ensure /validate-charset- removes /utf-8
a = cc.compiler_args(cc.get_always_args() + ['/validate-charset-'])
self.assertEqual(a.to_native(copy=True), ['/nologo', '/showIncludes', '/Zc:__cplusplus', '/validate-charset-'])
def test_compiler_args_class_gnuld(self):
## Test --start/end-group
linker = linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', [])
gcc = GnuCCompiler([], [], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker)
## Ensure that the fake compiler is never called by overriding the relevant function
gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include']
## Test that 'direct' append and extend works
l = gcc.compiler_args(['-Lfoodir', '-lfoo'])
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-lfoo'])
# Direct-adding a library and a libpath appends both correctly
l.extend_direct(['-Lbardir', '-lbar'])
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-Wl,--end-group'])
# Direct-adding the same library again still adds it
l.append_direct('-lbar')
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '-Wl,--end-group'])
# Direct-adding with absolute path deduplicates
l.append_direct('/libbaz.a')
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
# Adding libbaz again does nothing
l.append_direct('/libbaz.a')
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
# Adding a non-library argument doesn't include it in the group
l += ['-Lfoo', '-Wl,--export-dynamic']
self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group', '-Wl,--export-dynamic'])
# -Wl,-lfoo is detected as a library and gets added to the group
l.append('-Wl,-ldl')
self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--export-dynamic', '-Wl,-ldl', '-Wl,--end-group'])
def test_compiler_args_remove_system(self):
## Test --start/end-group
linker = linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', [])
gcc = GnuCCompiler([], [], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker)
## Ensure that the fake compiler is never called by overriding the relevant function
gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include']
## Test that 'direct' append and extend works
l = gcc.compiler_args(['-Lfoodir', '-lfoo'])
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-lfoo'])
## Test that to_native removes all system includes
l += ['-isystem/usr/include', '-isystem=/usr/share/include', '-DSOMETHING_IMPORTANT=1', '-isystem', '/usr/local/include']
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-lfoo', '-DSOMETHING_IMPORTANT=1'])
def test_string_templates_substitution(self):
dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict
substfunc = mesonbuild.mesonlib.substitute_values
ME = mesonbuild.mesonlib.MesonException
# Identity
self.assertEqual(dictfunc([], []), {})
# One input, no outputs
inputs = ['bar/foo.c.in']
outputs = []
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
'@PLAINNAME0@': 'foo.c.in', '@BASENAME0@': 'foo.c',
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
cmd = ['some', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), cmd)
cmd = ['@INPUT@.out', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', 'strings']
self.assertEqual(substfunc(cmd, d),
[inputs[0] + '.out'] + [d['@PLAINNAME@'] + '.ok'] + cmd[2:])
cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
self.assertEqual(substfunc(cmd, d),
inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
cmd = ['@OUTPUT@']
self.assertRaises(ME, substfunc, cmd, d)
# One input, one output
inputs = ['bar/foo.c.in']
outputs = ['out.c']
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
'@PLAINNAME0@': 'foo.c.in', '@BASENAME0@': 'foo.c',
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
cmd = ['some', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), cmd)
cmd = ['@INPUT@.out', '@OUTPUT@', 'strings']
self.assertEqual(substfunc(cmd, d),
[inputs[0] + '.out'] + outputs + cmd[2:])
cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', '@OUTPUT0@']
self.assertEqual(substfunc(cmd, d),
[inputs[0] + '.out', d['@PLAINNAME@'] + '.ok'] + outputs)
cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
self.assertEqual(substfunc(cmd, d),
inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
# One input, one output with a subdir
outputs = ['dir/out.c']
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
'@PLAINNAME0@': 'foo.c.in', '@BASENAME0@': 'foo.c',
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
# Check dictionary
self.assertEqual(ret, d)
# Two inputs, no outputs
inputs = ['bar/foo.c.in', 'baz/foo.c.in']
outputs = []
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
'@PLAINNAME0@': 'foo.c.in', '@PLAINNAME1@': 'foo.c.in',
'@BASENAME0@': 'foo.c', '@BASENAME1@': 'foo.c'}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
cmd = ['some', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), cmd)
cmd = ['@INPUT@', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), inputs + cmd[1:])
cmd = ['@INPUT0@.out', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
cmd = ['@INPUT0@.out', '@INPUT1@.ok', 'strings']
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
cmd = ['@INPUT0@', '@INPUT1@', 'strings']
self.assertEqual(substfunc(cmd, d), inputs + cmd[2:])
# Many inputs, can't use @INPUT@ like this
cmd = ['@INPUT@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
# Not enough inputs
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
# Too many inputs
cmd = ['@PLAINNAME@']
self.assertRaises(ME, substfunc, cmd, d)
cmd = ['@BASENAME@']
self.assertRaises(ME, substfunc, cmd, d)
# No outputs
cmd = ['@OUTPUT@']
self.assertRaises(ME, substfunc, cmd, d)
cmd = ['@OUTPUT0@']
self.assertRaises(ME, substfunc, cmd, d)
cmd = ['@OUTDIR@']
self.assertRaises(ME, substfunc, cmd, d)
# Two inputs, one output
outputs = ['dir/out.c']
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
'@PLAINNAME0@': 'foo.c.in', '@PLAINNAME1@': 'foo.c.in',
'@BASENAME0@': 'foo.c', '@BASENAME1@': 'foo.c',
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
cmd = ['some', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), cmd)
cmd = ['@OUTPUT@', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out'] + cmd[1:])
cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', 'strings']
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
# Many inputs, can't use @INPUT@ like this
cmd = ['@INPUT@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
# Not enough inputs
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
# Not enough outputs
cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
# Two inputs, two outputs
outputs = ['dir/out.c', 'dir/out2.c']
ret = dictfunc(inputs, outputs)
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
'@PLAINNAME0@': 'foo.c.in', '@PLAINNAME1@': 'foo.c.in',
'@BASENAME0@': 'foo.c', '@BASENAME1@': 'foo.c',
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1],
'@OUTDIR@': 'dir'}
# Check dictionary
self.assertEqual(ret, d)
# Check substitutions
cmd = ['some', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), cmd)
cmd = ['@OUTPUT@', 'ordinary', 'strings']
self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings']
self.assertEqual(substfunc(cmd, d), outputs + cmd[2:])
cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@']
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir'])
# Many inputs, can't use @INPUT@ like this
cmd = ['@INPUT@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
# Not enough inputs
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
# Not enough outputs
cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
# Many outputs, can't use @OUTPUT@ like this
cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
self.assertRaises(ME, substfunc, cmd, d)
def test_needs_exe_wrapper_override(self):
config = ConfigParser()
config['binaries'] = {
'c': '\'/usr/bin/gcc\'',
}
config['host_machine'] = {
'system': '\'linux\'',
'cpu_family': '\'arm\'',
'cpu': '\'armv7\'',
'endian': '\'little\'',
}
# Can not be used as context manager because we need to
# open it a second time and this is not possible on
# Windows.
configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding='utf-8')
configfilename = configfile.name
config.write(configfile)
configfile.flush()
configfile.close()
opts = get_fake_options()
opts.cross_file = (configfilename,)
env = get_fake_env(opts=opts)
detected_value = env.need_exe_wrapper()
os.unlink(configfilename)
desired_value = not detected_value
config['properties'] = {
'needs_exe_wrapper': 'true' if desired_value else 'false'
}
configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False, encoding='utf-8')
configfilename = configfile.name
config.write(configfile)
configfile.close()
opts = get_fake_options()
opts.cross_file = (configfilename,)
env = get_fake_env(opts=opts)
forced_value = env.need_exe_wrapper()
os.unlink(configfilename)
self.assertEqual(forced_value, desired_value)
def test_listify(self):
listify = mesonbuild.mesonlib.listify
# Test sanity
self.assertEqual([1], listify(1))
self.assertEqual([], listify([]))
self.assertEqual([1], listify([1]))
# Test flattening
self.assertEqual([1, 2, 3], listify([1, [2, 3]]))
self.assertEqual([1, 2, 3], listify([1, [2, [3]]]))
self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False))
# Test flattening and unholdering
class TestHeldObj(mesonbuild.mesonlib.HoldableObject):
def __init__(self, val: int) -> None:
self._val = val
class MockInterpreter:
def __init__(self) -> None:
self.subproject = ''
self.environment = None
heldObj1 = TestHeldObj(1)
holder1 = ObjectHolder(heldObj1, MockInterpreter())
self.assertEqual([holder1], listify(holder1))
self.assertEqual([holder1], listify([holder1]))
self.assertEqual([holder1, 2], listify([holder1, 2]))
self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]]))
def test_extract_as_list(self):
extract = mesonbuild.mesonlib.extract_as_list
# Test sanity
kwargs = {'sources': [1, 2, 3]}
self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
self.assertEqual(kwargs, {'sources': [1, 2, 3]})
self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True))
self.assertEqual(kwargs, {})
class TestHeldObj(mesonbuild.mesonlib.HoldableObject):
pass
class MockInterpreter:
def __init__(self) -> None:
self.subproject = ''
self.environment = None
heldObj = TestHeldObj()
# Test unholding
holder3 = ObjectHolder(heldObj, MockInterpreter())
kwargs = {'sources': [1, 2, holder3]}
self.assertEqual(kwargs, {'sources': [1, 2, holder3]})
# flatten nested lists
kwargs = {'sources': [1, [2, [3]]]}
self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
def _test_all_naming(self, cc, env, patterns, platform):
shr = patterns[platform]['shared']
stc = patterns[platform]['static']
shrstc = shr + tuple(x for x in stc if x not in shr)
stcshr = stc + tuple(x for x in shr if x not in stc)
p = cc.get_library_naming(env, LibType.SHARED)
self.assertEqual(p, shr)
p = cc.get_library_naming(env, LibType.STATIC)
self.assertEqual(p, stc)
p = cc.get_library_naming(env, LibType.PREFER_STATIC)
self.assertEqual(p, stcshr)
p = cc.get_library_naming(env, LibType.PREFER_SHARED)
self.assertEqual(p, shrstc)
# Test find library by mocking up openbsd
if platform != 'openbsd':
return
with tempfile.TemporaryDirectory() as tmpdir:
for i in ['libfoo.so.6.0', 'libfoo.so.5.0', 'libfoo.so.54.0', 'libfoo.so.66a.0b', 'libfoo.so.70.0.so.1',
'libbar.so.7.10', 'libbar.so.7.9', 'libbar.so.7.9.3']:
libpath = Path(tmpdir) / i
libpath.write_text('', encoding='utf-8')
found = cc._find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED, lib_prefix_warning=True)
self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0')
found = cc._find_library_real('bar', env, [tmpdir], '', LibType.PREFER_SHARED, lib_prefix_warning=True)
self.assertEqual(os.path.basename(found[0]), 'libbar.so.7.10')
def test_find_library_patterns(self):
'''
Unit test for the library search patterns used by find_library()
'''
unix_static = ('lib{}.a', '{}.a')
msvc_static = ('lib{}.a', 'lib{}.lib', '{}.a', '{}.lib')
# This is the priority list of pattern matching for library searching
patterns = {'openbsd': {'shared': ('lib{}.so', '{}.so', 'lib{}.so.[0-9]*.[0-9]*', '{}.so.[0-9]*.[0-9]*'),
'static': unix_static},
'linux': {'shared': ('lib{}.so', '{}.so'),
'static': unix_static},
'darwin': {'shared': ('lib{}.dylib', 'lib{}.so', '{}.dylib', '{}.so'),
'static': unix_static},
'cygwin': {'shared': ('cyg{}.dll', 'cyg{}.dll.a', 'lib{}.dll',
'lib{}.dll.a', '{}.dll', '{}.dll.a'),
'static': ('cyg{}.a',) + unix_static},
'windows-msvc': {'shared': ('lib{}.lib', '{}.lib'),
'static': msvc_static},
'windows-mingw': {'shared': ('lib{}.dll.a', 'lib{}.lib', 'lib{}.dll',
'{}.dll.a', '{}.lib', '{}.dll'),
'static': msvc_static}}
env = get_fake_env()
cc = detect_c_compiler(env, MachineChoice.HOST)
if is_osx():
self._test_all_naming(cc, env, patterns, 'darwin')
elif is_cygwin():
self._test_all_naming(cc, env, patterns, 'cygwin')
elif is_windows():
if cc.get_argument_syntax() == 'msvc':
self._test_all_naming(cc, env, patterns, 'windows-msvc')
else:
self._test_all_naming(cc, env, patterns, 'windows-mingw')
elif is_openbsd():
self._test_all_naming(cc, env, patterns, 'openbsd')
else:
self._test_all_naming(cc, env, patterns, 'linux')
env.machines.host.system = 'openbsd'
self._test_all_naming(cc, env, patterns, 'openbsd')
env.machines.host.system = 'darwin'
self._test_all_naming(cc, env, patterns, 'darwin')
env.machines.host.system = 'cygwin'
self._test_all_naming(cc, env, patterns, 'cygwin')
env.machines.host.system = 'windows'
self._test_all_naming(cc, env, patterns, 'windows-mingw')
@skipIfNoPkgconfig
def test_pkgconfig_parse_libs(self):
'''
Unit test for parsing of pkg-config output to search for libraries
https://github.com/mesonbuild/meson/issues/3951
'''
def create_static_lib(name):
if not is_osx():
name.open('w', encoding='utf-8').close()
return
src = name.with_suffix('.c')
out = name.with_suffix('.o')
with src.open('w', encoding='utf-8') as f:
f.write('int meson_foobar (void) { return 0; }')
# use of x86_64 is hardcoded in run_tests.py:get_fake_env()
subprocess.check_call(['clang', '-c', str(src), '-o', str(out), '-arch', 'x86_64'])
subprocess.check_call(['ar', 'csr', str(name), str(out)])
with tempfile.TemporaryDirectory() as tmpdir:
pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True)
env = get_fake_env()
compiler = detect_c_compiler(env, MachineChoice.HOST)
env.coredata.compilers.host = {'c': compiler}
env.coredata.options[OptionKey('link_args', lang='c')] = FakeCompilerOptions()
p1 = Path(tmpdir) / '1'
p2 = Path(tmpdir) / '2'
p1.mkdir()
p2.mkdir()
# libfoo.a is in one prefix
create_static_lib(p1 / 'libfoo.a')
# libbar.a is in both prefixes
create_static_lib(p1 / 'libbar.a')
create_static_lib(p2 / 'libbar.a')
# Ensure that we never statically link to these
create_static_lib(p1 / 'libpthread.a')
create_static_lib(p1 / 'libm.a')
create_static_lib(p1 / 'libc.a')
create_static_lib(p1 / 'libdl.a')
create_static_lib(p1 / 'librt.a')
class FakeInstance(PkgConfigCLI):
def _call_pkgbin(self, args, env=None):
if '--libs' not in args:
return 0, '', ''
if args[-1] == 'foo':
return 0, f'-L{p2.as_posix()} -lfoo -L{p1.as_posix()} -lbar', ''
if args[-1] == 'bar':
return 0, f'-L{p2.as_posix()} -lbar', ''
if args[-1] == 'internal':
return 0, f'-L{p1.as_posix()} -lpthread -lm -lc -lrt -ldl', ''
with mock.patch.object(PkgConfigInterface, 'instance') as instance_method:
instance_method.return_value = FakeInstance(env, MachineChoice.HOST, silent=True)
kwargs = {'required': True, 'silent': True}
foo_dep = PkgConfigDependency('foo', env, kwargs)
self.assertEqual(foo_dep.get_link_args(),
[(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()])
bar_dep = PkgConfigDependency('bar', env, kwargs)
self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()])
internal_dep = PkgConfigDependency('internal', env, kwargs)
if compiler.get_argument_syntax() == 'msvc':
self.assertEqual(internal_dep.get_link_args(), [])
else:
link_args = internal_dep.get_link_args()
for link_arg in link_args:
for lib in ('pthread', 'm', 'c', 'dl', 'rt'):
self.assertNotIn(f'lib{lib}.a', link_arg, msg=link_args)
def test_version_compare(self):
comparefunc = mesonbuild.mesonlib.version_compare_many
for (a, b, result) in [
('0.99.beta19', '>= 0.99.beta14', True),
]:
self.assertEqual(comparefunc(a, b)[0], result)
for (a, b, op) in [
# examples from https://fedoraproject.org/wiki/Archive:Tools/RPM/VersionComparison
("1.0010", "1.9", operator.gt),
("1.05", "1.5", operator.eq),
("1.0", "1", operator.gt),
("2.50", "2.5", operator.gt),
("fc4", "fc.4", operator.eq),
("FC5", "fc4", operator.lt),
("2a", "2.0", operator.lt),
("1.0", "1.fc4", operator.gt),
("3.0.0_fc", "3.0.0.fc", operator.eq),
# from RPM tests
("1.0", "1.0", operator.eq),
("1.0", "2.0", operator.lt),
("2.0", "1.0", operator.gt),
("2.0.1", "2.0.1", operator.eq),
("2.0", "2.0.1", operator.lt),
("2.0.1", "2.0", operator.gt),
("2.0.1a", "2.0.1a", operator.eq),
("2.0.1a", "2.0.1", operator.gt),
("2.0.1", "2.0.1a", operator.lt),
("5.5p1", "5.5p1", operator.eq),
("5.5p1", "5.5p2", operator.lt),
("5.5p2", "5.5p1", operator.gt),
("5.5p10", "5.5p10", operator.eq),
("5.5p1", "5.5p10", operator.lt),
("5.5p10", "5.5p1", operator.gt),
("10xyz", "10.1xyz", operator.lt),
("10.1xyz", "10xyz", operator.gt),
("xyz10", "xyz10", operator.eq),
("xyz10", "xyz10.1", operator.lt),
("xyz10.1", "xyz10", operator.gt),
("xyz.4", "xyz.4", operator.eq),
("xyz.4", "8", operator.lt),
("8", "xyz.4", operator.gt),
("xyz.4", "2", operator.lt),
("2", "xyz.4", operator.gt),
("5.5p2", "5.6p1", operator.lt),
("5.6p1", "5.5p2", operator.gt),
("5.6p1", "6.5p1", operator.lt),
("6.5p1", "5.6p1", operator.gt),
("6.0.rc1", "6.0", operator.gt),
("6.0", "6.0.rc1", operator.lt),
("10b2", "10a1", operator.gt),
("10a2", "10b2", operator.lt),
("1.0aa", "1.0aa", operator.eq),
("1.0a", "1.0aa", operator.lt),
("1.0aa", "1.0a", operator.gt),
("10.0001", "10.0001", operator.eq),
("10.0001", "10.1", operator.eq),
("10.1", "10.0001", operator.eq),
("10.0001", "10.0039", operator.lt),
("10.0039", "10.0001", operator.gt),
("4.999.9", "5.0", operator.lt),
("5.0", "4.999.9", operator.gt),
("20101121", "20101121", operator.eq),
("20101121", "20101122", operator.lt),
("20101122", "20101121", operator.gt),
("2_0", "2_0", operator.eq),
("2.0", "2_0", operator.eq),
("2_0", "2.0", operator.eq),
("a", "a", operator.eq),
("a+", "a+", operator.eq),
("a+", "a_", operator.eq),
("a_", "a+", operator.eq),
("+a", "+a", operator.eq),
("+a", "_a", operator.eq),
("_a", "+a", operator.eq),
("+_", "+_", operator.eq),
("_+", "+_", operator.eq),
("_+", "_+", operator.eq),
("+", "_", operator.eq),
("_", "+", operator.eq),
# other tests
('0.99.beta19', '0.99.beta14', operator.gt),
("1.0.0", "2.0.0", operator.lt),
(".0.0", "2.0.0", operator.lt),
("alpha", "beta", operator.lt),
("1.0", "1.0.0", operator.lt),
("2.456", "2.1000", operator.lt),
("2.1000", "3.111", operator.lt),
("2.001", "2.1", operator.eq),
("2.34", "2.34", operator.eq),
("6.1.2", "6.3.8", operator.lt),
("1.7.3.0", "2.0.0", operator.lt),
("2.24.51", "2.25", operator.lt),
("2.1.5+20120813+gitdcbe778", "2.1.5", operator.gt),
("3.4.1", "3.4b1", operator.gt),
("041206", "200090325", operator.lt),
("0.6.2+git20130413", "0.6.2", operator.gt),
("2.6.0+bzr6602", "2.6.0", operator.gt),
("2.6.0", "2.6b2", operator.gt),
("2.6.0+bzr6602", "2.6b2x", operator.gt),
("0.6.7+20150214+git3a710f9", "0.6.7", operator.gt),
("15.8b", "15.8.0.1", operator.lt),
("1.2rc1", "1.2.0", operator.lt),
]:
ver_a = Version(a)
ver_b = Version(b)
if op is operator.eq:
for o, name in [(op, 'eq'), (operator.ge, 'ge'), (operator.le, 'le')]:
self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
if op is operator.lt:
for o, name in [(op, 'lt'), (operator.le, 'le'), (operator.ne, 'ne')]:
self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
for o, name in [(operator.gt, 'gt'), (operator.ge, 'ge'), (operator.eq, 'eq')]:
self.assertFalse(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
if op is operator.gt:
for o, name in [(op, 'gt'), (operator.ge, 'ge'), (operator.ne, 'ne')]:
self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
for o, name in [(operator.lt, 'lt'), (operator.le, 'le'), (operator.eq, 'eq')]:
self.assertFalse(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
def test_msvc_toolset_version(self):
'''
Ensure that the toolset version returns the correct value for this MSVC
'''
env = get_fake_env()
cc = detect_c_compiler(env, MachineChoice.HOST)
if cc.get_argument_syntax() != 'msvc':
raise unittest.SkipTest('Test only applies to MSVC-like compilers')
toolset_ver = cc.get_toolset_version()
self.assertIsNotNone(toolset_ver)
# Visual Studio 2015 and older versions do not define VCToolsVersion
# TODO: ICL doesn't set this in the VSC2015 profile either
if cc.id == 'msvc' and int(''.join(cc.version.split('.')[0:2])) < 1910:
return
if 'VCToolsVersion' in os.environ:
vctools_ver = os.environ['VCToolsVersion']
else:
self.assertIn('VCINSTALLDIR', os.environ)
# See https://devblogs.microsoft.com/cppblog/finding-the-visual-c-compiler-tools-in-visual-studio-2017/
vctools_ver = (Path(os.environ['VCINSTALLDIR']) / 'Auxiliary' / 'Build' / 'Microsoft.VCToolsVersion.default.txt').read_text(encoding='utf-8')
self.assertTrue(vctools_ver.startswith(toolset_ver),
msg=f'{vctools_ver!r} does not start with {toolset_ver!r}')
def test_split_args(self):
split_args = mesonbuild.mesonlib.split_args
join_args = mesonbuild.mesonlib.join_args
if is_windows():
test_data = [
# examples from https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments
(r'"a b c" d e', ['a b c', 'd', 'e'], True),
(r'"ab\"c" "\\" d', ['ab"c', '\\', 'd'], False),
(r'a\\\b d"e f"g h', [r'a\\\b', 'de fg', 'h'], False),
(r'a\\\"b c d', [r'a\"b', 'c', 'd'], False),
(r'a\\\\"b c" d e', [r'a\\b c', 'd', 'e'], False),
# other basics
(r'""', [''], True),
(r'a b c d "" e', ['a', 'b', 'c', 'd', '', 'e'], True),
(r"'a b c' d e", ["'a", 'b', "c'", 'd', 'e'], True),
(r"'a&b&c' d e", ["'a&b&c'", 'd', 'e'], True),
(r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], True),
(r"'a & b & c d e'", ["'a", '&', 'b', '&', 'c', 'd', "e'"], True),
('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False),
# more illustrative tests
(r'cl test.cpp /O1 /Fe:test.exe', ['cl', 'test.cpp', '/O1', '/Fe:test.exe'], True),
(r'cl "test.cpp /O1 /Fe:test.exe"', ['cl', 'test.cpp /O1 /Fe:test.exe'], True),
(r'cl /DNAME=\"Bob\" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], False),
(r'cl "/DNAME=\"Bob\"" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], True),
(r'cl /DNAME=\"Bob, Alice\" test.cpp', ['cl', '/DNAME="Bob,', 'Alice"', 'test.cpp'], False),
(r'cl "/DNAME=\"Bob, Alice\"" test.cpp', ['cl', '/DNAME="Bob, Alice"', 'test.cpp'], True),
(r'cl C:\path\with\backslashes.cpp', ['cl', r'C:\path\with\backslashes.cpp'], True),
(r'cl C:\\path\\with\\double\\backslashes.cpp', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], True),
(r'cl "C:\\path\\with\\double\\backslashes.cpp"', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], False),
(r'cl C:\path with spaces\test.cpp', ['cl', r'C:\path', 'with', r'spaces\test.cpp'], False),
(r'cl "C:\path with spaces\test.cpp"', ['cl', r'C:\path with spaces\test.cpp'], True),
(r'cl /DPATH="C:\path\with\backslashes test.cpp', ['cl', r'/DPATH=C:\path\with\backslashes test.cpp'], False),
(r'cl /DPATH=\"C:\\ends\\with\\backslashes\\\" test.cpp', ['cl', r'/DPATH="C:\\ends\\with\\backslashes\"', 'test.cpp'], False),
(r'cl /DPATH="C:\\ends\\with\\backslashes\\" test.cpp', ['cl', '/DPATH=C:\\\\ends\\\\with\\\\backslashes\\', 'test.cpp'], False),
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\"', 'test.cpp'], True),
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\ test.cpp'], False),
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\"', 'test.cpp'], True),
]
else:
test_data = [
(r"'a b c' d e", ['a b c', 'd', 'e'], True),
(r"a/b/c d e", ['a/b/c', 'd', 'e'], True),
(r"a\b\c d e", [r'abc', 'd', 'e'], False),
(r"a\\b\\c d e", [r'a\b\c', 'd', 'e'], False),
(r'"a b c" d e', ['a b c', 'd', 'e'], False),
(r'"a\\b\\c\\" d e', ['a\\b\\c\\', 'd', 'e'], False),
(r"'a\b\c\' d e", ['a\\b\\c\\', 'd', 'e'], True),
(r"'a&b&c' d e", ['a&b&c', 'd', 'e'], True),
(r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], False),
(r"'a & b & c d e'", ['a & b & c d e'], True),
(r"abd'e f'g h", [r'abde fg', 'h'], False),
('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False),
('g++ -DNAME="Bob" test.cpp', ['g++', '-DNAME=Bob', 'test.cpp'], False),
("g++ '-DNAME=\"Bob\"' test.cpp", ['g++', '-DNAME="Bob"', 'test.cpp'], True),
('g++ -DNAME="Bob, Alice" test.cpp', ['g++', '-DNAME=Bob, Alice', 'test.cpp'], False),
("g++ '-DNAME=\"Bob, Alice\"' test.cpp", ['g++', '-DNAME="Bob, Alice"', 'test.cpp'], True),
]
for (cmd, expected, roundtrip) in test_data:
self.assertEqual(split_args(cmd), expected)
if roundtrip:
self.assertEqual(join_args(expected), cmd)
def test_quote_arg(self):
split_args = mesonbuild.mesonlib.split_args
quote_arg = mesonbuild.mesonlib.quote_arg
if is_windows():
test_data = [
('', '""'),
('arg1', 'arg1'),
('/option1', '/option1'),
('/Ovalue', '/Ovalue'),
('/OBob&Alice', '/OBob&Alice'),
('/Ovalue with spaces', r'"/Ovalue with spaces"'),
(r'/O"value with spaces"', r'"/O\"value with spaces\""'),
(r'/OC:\path with spaces\test.exe', r'"/OC:\path with spaces\test.exe"'),
('/LIBPATH:C:\\path with spaces\\ends\\with\\backslashes\\', r'"/LIBPATH:C:\path with spaces\ends\with\backslashes\\"'),
('/LIBPATH:"C:\\path with spaces\\ends\\with\\backslashes\\\\"', r'"/LIBPATH:\"C:\path with spaces\ends\with\backslashes\\\\\""'),
(r'/DMSG="Alice said: \"Let\'s go\""', r'"/DMSG=\"Alice said: \\\"Let\'s go\\\"\""'),
]
else:
test_data = [
('arg1', 'arg1'),
('--option1', '--option1'),
('-O=value', '-O=value'),
('-O=Bob&Alice', "'-O=Bob&Alice'"),
('-O=value with spaces', "'-O=value with spaces'"),
('-O="value with spaces"', '\'-O=\"value with spaces\"\''),
('-O=/path with spaces/test', '\'-O=/path with spaces/test\''),
('-DMSG="Alice said: \\"Let\'s go\\""', "'-DMSG=\"Alice said: \\\"Let'\"'\"'s go\\\"\"'"),
]
for (arg, expected) in test_data:
self.assertEqual(quote_arg(arg), expected)
self.assertEqual(split_args(expected)[0], arg)
def test_depfile(self):
for (f, target, expdeps) in [
# empty, unknown target
([''], 'unknown', set()),
# simple target & deps
(['meson/foo.o : foo.c foo.h'], 'meson/foo.o', set({'foo.c', 'foo.h'})),
(['meson/foo.o: foo.c foo.h'], 'foo.c', set()),
# get all deps
(['meson/foo.o: foo.c foo.h',
'foo.c: gen.py'], 'meson/foo.o', set({'foo.c', 'foo.h', 'gen.py'})),
(['meson/foo.o: foo.c foo.h',
'foo.c: gen.py'], 'foo.c', set({'gen.py'})),
# linue continuation, multiple targets
(['foo.o \\', 'foo.h: bar'], 'foo.h', set({'bar'})),
(['foo.o \\', 'foo.h: bar'], 'foo.o', set({'bar'})),
# \\ handling
(['foo: Program\\ F\\iles\\\\X'], 'foo', set({'Program Files\\X'})),
# $ handling
(['f$o.o: c/b'], 'f$o.o', set({'c/b'})),
(['f$$o.o: c/b'], 'f$o.o', set({'c/b'})),
# cycles
(['a: b', 'b: a'], 'a', set({'a', 'b'})),
(['a: b', 'b: a'], 'b', set({'a', 'b'})),
]:
d = mesonbuild.depfile.DepFile(f)
deps = d.get_all_dependencies(target)
self.assertEqual(sorted(deps), sorted(expdeps))
def test_log_once(self):
f = io.StringIO()
with mock.patch('mesonbuild.mlog._logger.log_file', f), \
mock.patch('mesonbuild.mlog._logger.logged_once', set()):
mesonbuild.mlog.log('foo', once=True)
mesonbuild.mlog.log('foo', once=True)
actual = f.getvalue().strip()
self.assertEqual(actual, 'foo', actual)
def test_log_once_ansi(self):
f = io.StringIO()
with mock.patch('mesonbuild.mlog._logger.log_file', f), \
mock.patch('mesonbuild.mlog._logger.logged_once', set()):
mesonbuild.mlog.log(mesonbuild.mlog.bold('foo'), once=True)
mesonbuild.mlog.log(mesonbuild.mlog.bold('foo'), once=True)
actual = f.getvalue().strip()
self.assertEqual(actual.count('foo'), 1, actual)
mesonbuild.mlog.log('foo', once=True)
actual = f.getvalue().strip()
self.assertEqual(actual.count('foo'), 1, actual)
f.truncate()
mesonbuild.mlog.warning('bar', once=True)
mesonbuild.mlog.warning('bar', once=True)
actual = f.getvalue().strip()
self.assertEqual(actual.count('bar'), 1, actual)
def test_sort_libpaths(self):
sort_libpaths = mesonbuild.dependencies.base.sort_libpaths
self.assertEqual(sort_libpaths(
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'],
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
self.assertEqual(sort_libpaths(
['/usr/local/lib', '/home/mesonuser/.local/lib', '/usr/lib'],
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
self.assertEqual(sort_libpaths(
['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'],
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
self.assertEqual(sort_libpaths(
['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'],
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/libdata/pkgconfig']),
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
def test_dependency_factory_order(self):
b = mesonbuild.dependencies.base
F = mesonbuild.dependencies.factory
with tempfile.TemporaryDirectory() as tmpdir:
with chdir(tmpdir):
env = get_fake_env()
env.scratch_dir = tmpdir
f = F.DependencyFactory(
'test_dep',
methods=[b.DependencyMethods.PKGCONFIG, b.DependencyMethods.CMAKE]
)
actual = [m() for m in f(env, MachineChoice.HOST, {'required': False})]
self.assertListEqual([m.type_name for m in actual], ['pkgconfig', 'cmake'])
f = F.DependencyFactory(
'test_dep',
methods=[b.DependencyMethods.CMAKE, b.DependencyMethods.PKGCONFIG]
)
actual = [m() for m in f(env, MachineChoice.HOST, {'required': False})]
self.assertListEqual([m.type_name for m in actual], ['cmake', 'pkgconfig'])
def test_validate_json(self) -> None:
"""Validate the json schema for the test cases."""
try:
from fastjsonschema import compile, JsonSchemaValueException as JsonSchemaFailure
fast = True
except ImportError:
try:
from jsonschema import validate, ValidationError as JsonSchemaFailure
fast = False
except:
if is_ci():
raise
raise unittest.SkipTest('neither Python fastjsonschema nor jsonschema module not found.')
with open('data/test.schema.json', 'r', encoding='utf-8') as f:
data = json.loads(f.read())
if fast:
schema_validator = compile(data)
else:
schema_validator = lambda x: validate(x, schema=data)
errors: T.List[T.Tuple[Path, Exception]] = []
for p in Path('test cases').glob('**/test.json'):
try:
schema_validator(json.loads(p.read_text(encoding='utf-8')))
except JsonSchemaFailure as e:
errors.append((p.resolve(), e))
for f, e in errors:
print(f'Failed to validate: "{f}"')
print(str(e))
self.assertFalse(errors)
def test_typed_pos_args_types(self) -> None:
@typed_pos_args('foo', str, int, bool)
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
self.assertIsInstance(args, tuple)
self.assertIsInstance(args[0], str)
self.assertIsInstance(args[1], int)
self.assertIsInstance(args[2], bool)
_(None, mock.Mock(), ['string', 1, False], None)
def test_typed_pos_args_types_invalid(self) -> None:
@typed_pos_args('foo', str, int, bool)
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string', 1.0, False], None)
self.assertEqual(str(cm.exception), 'foo argument 2 was of type "float" but should have been "int"')
def test_typed_pos_args_types_wrong_number(self) -> None:
@typed_pos_args('foo', str, int, bool)
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string', 1], None)
self.assertEqual(str(cm.exception), 'foo takes exactly 3 arguments, but got 2.')
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string', 1, True, True], None)
self.assertEqual(str(cm.exception), 'foo takes exactly 3 arguments, but got 4.')
def test_typed_pos_args_varargs(self) -> None:
@typed_pos_args('foo', str, varargs=str)
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertIsInstance(args, tuple)
self.assertIsInstance(args[0], str)
self.assertIsInstance(args[1], list)
self.assertIsInstance(args[1][0], str)
self.assertIsInstance(args[1][1], str)
_(None, mock.Mock(), ['string', 'var', 'args'], None)
def test_typed_pos_args_varargs_not_given(self) -> None:
@typed_pos_args('foo', str, varargs=str)
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertIsInstance(args, tuple)
self.assertIsInstance(args[0], str)
self.assertIsInstance(args[1], list)
self.assertEqual(args[1], [])
_(None, mock.Mock(), ['string'], None)
def test_typed_pos_args_varargs_invalid(self) -> None:
@typed_pos_args('foo', str, varargs=str)
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string', 'var', 'args', 0], None)
self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been "str"')
def test_typed_pos_args_varargs_invalid_multiple_types(self) -> None:
@typed_pos_args('foo', str, varargs=(str, list))
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string', 'var', 'args', 0], None)
self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been one of: "str", "list"')
def test_typed_pos_args_max_varargs(self) -> None:
@typed_pos_args('foo', str, varargs=str, max_varargs=5)
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertIsInstance(args, tuple)
self.assertIsInstance(args[0], str)
self.assertIsInstance(args[1], list)
self.assertIsInstance(args[1][0], str)
self.assertIsInstance(args[1][1], str)
_(None, mock.Mock(), ['string', 'var', 'args'], None)
def test_typed_pos_args_max_varargs_exceeded(self) -> None:
@typed_pos_args('foo', str, varargs=str, max_varargs=1)
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string', 'var', 'args'], None)
self.assertEqual(str(cm.exception), 'foo takes between 1 and 2 arguments, but got 3.')
def test_typed_pos_args_min_varargs(self) -> None:
@typed_pos_args('foo', varargs=str, max_varargs=2, min_varargs=1)
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertIsInstance(args, tuple)
self.assertIsInstance(args[0], list)
self.assertIsInstance(args[0][0], str)
self.assertIsInstance(args[0][1], str)
_(None, mock.Mock(), ['string', 'var'], None)
def test_typed_pos_args_min_varargs_not_met(self) -> None:
@typed_pos_args('foo', str, varargs=str, min_varargs=1)
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string'], None)
self.assertEqual(str(cm.exception), 'foo takes at least 2 arguments, but got 1.')
def test_typed_pos_args_min_and_max_varargs_exceeded(self) -> None:
@typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2)
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string', 'var', 'args', 'bar'], None)
self.assertEqual(str(cm.exception), 'foo takes between 2 and 3 arguments, but got 4.')
def test_typed_pos_args_min_and_max_varargs_not_met(self) -> None:
@typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2)
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string'], None)
self.assertEqual(str(cm.exception), 'foo takes between 2 and 3 arguments, but got 1.')
def test_typed_pos_args_variadic_and_optional(self) -> None:
@typed_pos_args('foo', str, optargs=[str], varargs=str, min_varargs=0)
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(AssertionError) as cm:
_(None, mock.Mock(), ['string'], None)
self.assertEqual(
str(cm.exception),
'varargs and optargs not supported together as this would be ambiguous')
def test_typed_pos_args_min_optargs_not_met(self) -> None:
@typed_pos_args('foo', str, str, optargs=[str])
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string'], None)
self.assertEqual(str(cm.exception), 'foo takes at least 2 arguments, but got 1.')
def test_typed_pos_args_min_optargs_max_exceeded(self) -> None:
@typed_pos_args('foo', str, optargs=[str])
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
self.assertTrue(False) # should not be reachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), ['string', '1', '2'], None)
self.assertEqual(str(cm.exception), 'foo takes at most 2 arguments, but got 3.')
def test_typed_pos_args_optargs_not_given(self) -> None:
@typed_pos_args('foo', str, optargs=[str])
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
self.assertEqual(len(args), 2)
self.assertIsInstance(args[0], str)
self.assertEqual(args[0], 'string')
self.assertIsNone(args[1])
_(None, mock.Mock(), ['string'], None)
def test_typed_pos_args_optargs_some_given(self) -> None:
@typed_pos_args('foo', str, optargs=[str, int])
def _(obj, node, args: T.Tuple[str, T.Optional[str], T.Optional[int]], kwargs) -> None:
self.assertEqual(len(args), 3)
self.assertIsInstance(args[0], str)
self.assertEqual(args[0], 'string')
self.assertIsInstance(args[1], str)
self.assertEqual(args[1], '1')
self.assertIsNone(args[2])
_(None, mock.Mock(), ['string', '1'], None)
def test_typed_pos_args_optargs_all_given(self) -> None:
@typed_pos_args('foo', str, optargs=[str])
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
self.assertEqual(len(args), 2)
self.assertIsInstance(args[0], str)
self.assertEqual(args[0], 'string')
self.assertIsInstance(args[1], str)
_(None, mock.Mock(), ['string', '1'], None)
def test_typed_kwarg_basic(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', str, default='')
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
self.assertIsInstance(kwargs['input'], str)
self.assertEqual(kwargs['input'], 'foo')
_(None, mock.Mock(), [], {'input': 'foo'})
def test_typed_kwarg_missing_required(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', str, required=True),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
self.assertTrue(False) # should be unreachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), [], {})
self.assertEqual(str(cm.exception), 'testfunc is missing required keyword argument "input"')
def test_typed_kwarg_missing_optional(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', (str, type(None))),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.Optional[str]]) -> None:
self.assertIsNone(kwargs['input'])
_(None, mock.Mock(), [], {})
def test_typed_kwarg_default(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', str, default='default'),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
self.assertEqual(kwargs['input'], 'default')
_(None, mock.Mock(), [], {})
def test_typed_kwarg_container_valid(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(list, str), default=[], required=True),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None:
self.assertEqual(kwargs['input'], ['str'])
_(None, mock.Mock(), [], {'input': ['str']})
def test_typed_kwarg_container_invalid(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(list, str), required=True),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None:
self.assertTrue(False) # should be unreachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), [], {'input': {}})
self.assertEqual(str(cm.exception), "testfunc keyword argument 'input' was of type dict[] but should have been array[str]")
def test_typed_kwarg_contained_invalid(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(dict, str), required=True),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.Dict[str, str]]) -> None:
self.assertTrue(False) # should be unreachable
with self.assertRaises(InvalidArguments) as cm:
_(None, mock.Mock(), [], {'input': {'key': 1, 'bar': 2}})
self.assertEqual(str(cm.exception), "testfunc keyword argument 'input' was of type dict[int] but should have been dict[str]")
def test_typed_kwarg_container_listify(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(list, str), default=[], listify=True),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None:
self.assertEqual(kwargs['input'], ['str'])
_(None, mock.Mock(), [], {'input': 'str'})
def test_typed_kwarg_container_default_copy(self) -> None:
default: T.List[str] = []
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(list, str), listify=True, default=default),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None:
self.assertIsNot(kwargs['input'], default)
_(None, mock.Mock(), [], {})
def test_typed_kwarg_container_pairs(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(list, str, pairs=True), listify=True),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, T.List[str]]) -> None:
self.assertEqual(kwargs['input'], ['a', 'b'])
_(None, mock.Mock(), [], {'input': ['a', 'b']})
with self.assertRaises(MesonException) as cm:
_(None, mock.Mock(), [], {'input': ['a']})
self.assertEqual(str(cm.exception), "testfunc keyword argument 'input' was of type array[str] but should have been array[str] that has even size")
def test_typed_kwarg_since(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', str, since='1.0', since_message='Its awesome, use it',
deprecated='2.0', deprecated_message='Its terrible, dont use it')
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
self.assertIsInstance(kwargs['input'], str)
self.assertEqual(kwargs['input'], 'foo')
with self.subTest('use before available'), \
mock.patch('sys.stdout', io.StringIO()) as out, \
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': '0.1'}):
# With Meson 0.1 it should trigger the "introduced" warning but not the "deprecated" warning
_(None, mock.Mock(subproject=''), [], {'input': 'foo'})
self.assertRegex(out.getvalue(), r'WARNING:.*introduced.*input arg in testfunc. Its awesome, use it')
self.assertNotRegex(out.getvalue(), r'WARNING:.*deprecated.*input arg in testfunc. Its terrible, dont use it')
with self.subTest('no warnings should be triggered'), \
mock.patch('sys.stdout', io.StringIO()) as out, \
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': '1.5'}):
# With Meson 1.5 it shouldn't trigger any warning
_(None, mock.Mock(subproject=''), [], {'input': 'foo'})
self.assertNotRegex(out.getvalue(), r'WARNING:.*')
with self.subTest('use after deprecated'), \
mock.patch('sys.stdout', io.StringIO()) as out, \
mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': '2.0'}):
# With Meson 2.0 it should trigger the "deprecated" warning but not the "introduced" warning
_(None, mock.Mock(subproject=''), [], {'input': 'foo'})
self.assertRegex(out.getvalue(), r'WARNING:.*deprecated.*input arg in testfunc. Its terrible, dont use it')
self.assertNotRegex(out.getvalue(), r'WARNING:.*introduced.*input arg in testfunc. Its awesome, use it')
def test_typed_kwarg_validator(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', str, default='', validator=lambda x: 'invalid!' if x != 'foo' else None)
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
pass
# Should be valid
_(None, mock.Mock(), tuple(), dict(input='foo'))
with self.assertRaises(MesonException) as cm:
_(None, mock.Mock(), tuple(), dict(input='bar'))
self.assertEqual(str(cm.exception), "testfunc keyword argument \"input\" invalid!")
def test_typed_kwarg_convertor(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('native', bool, default=False, convertor=lambda n: MachineChoice.BUILD if n else MachineChoice.HOST)
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, MachineChoice]) -> None:
assert isinstance(kwargs['native'], MachineChoice)
_(None, mock.Mock(), tuple(), dict(native=True))
@mock.patch('mesonbuild.mesonlib.project_meson_versions', {'': '1.0'})
def test_typed_kwarg_since_values(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', ContainerTypeInfo(list, str), listify=True, default=[], deprecated_values={'foo': '0.9'}, since_values={'bar': '1.1'}),
KwargInfo('output', ContainerTypeInfo(dict, str), default={}, deprecated_values={'foo': '0.9', 'foo2': ('0.9', 'dont use it')}, since_values={'bar': '1.1', 'bar2': ('1.1', 'use this')}),
KwargInfo('install_dir', (bool, str, NoneType), deprecated_values={False: '0.9'}),
KwargInfo(
'mode',
(str, type(None)),
validator=in_set_validator({'clean', 'build', 'rebuild', 'deprecated', 'since'}),
deprecated_values={'deprecated': '1.0'},
since_values={'since': '1.1'}),
KwargInfo('dict', (ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str)), default={},
since_values={list: '1.9'}),
KwargInfo('new_dict', (ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str)), default={},
since_values={dict: '1.1'}),
KwargInfo('foo', (str, int, ContainerTypeInfo(list, str), ContainerTypeInfo(dict, str), ContainerTypeInfo(list, int)), default={},
since_values={str: '1.1', ContainerTypeInfo(list, str): '1.2', ContainerTypeInfo(dict, str): '1.3'},
deprecated_values={int: '0.8', ContainerTypeInfo(list, int): '0.9'}),
KwargInfo('tuple', (ContainerTypeInfo(list, (str, int))), default=[], listify=True,
since_values={ContainerTypeInfo(list, str): '1.1', ContainerTypeInfo(list, int): '1.2'}),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
pass
with self.subTest('deprecated array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'input': ['foo']})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "input" value "foo".*""")
with self.subTest('new array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'input': ['bar']})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "input" value "bar".*""")
with self.subTest('deprecated dict string value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'foo': 'a'}})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo".*""")
with self.subTest('deprecated dict string value with msg'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'foo2': 'a'}})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo2" in dict keys. dont use it.*""")
with self.subTest('new dict string value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'bar': 'b'}})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar".*""")
with self.subTest('new dict string value with msg'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'output': {'bar2': 'a'}})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar2" in dict keys. use this.*""")
with self.subTest('new string type'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'foo': 'foo'})
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "foo" of type str.*""")
with self.subTest('new array of string type'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'foo': ['foo']})
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.2': "testfunc" keyword argument "foo" of type array\[str\].*""")
with self.subTest('new dict of string type'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'foo': {'plop': 'foo'}})
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.3': "testfunc" keyword argument "foo" of type dict\[str\].*""")
with self.subTest('deprecated int value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'foo': 1})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.8': "testfunc" keyword argument "foo" of type int.*""")
with self.subTest('deprecated array int value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'foo': [1]})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "foo" of type array\[int\].*""")
with self.subTest('new list[str] value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'tuple': ['foo', 42]})
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "tuple" of type array\[str\].*""")
self.assertRegex(out.getvalue(), r"""WARNING: Project targets '1.0'.*introduced in '1.2': "testfunc" keyword argument "tuple" of type array\[int\].*""")
with self.subTest('deprecated array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'input': 'foo'})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "input" value "foo".*""")
with self.subTest('new array string value'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'input': 'bar'})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "input" value "bar".*""")
with self.subTest('non string union'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'install_dir': False})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '0.9': "testfunc" keyword argument "install_dir" value "False".*""")
with self.subTest('deprecated string union'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'mode': 'deprecated'})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*deprecated since '1.0': "testfunc" keyword argument "mode" value "deprecated".*""")
with self.subTest('new string union'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'mode': 'since'})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "mode" value "since".*""")
with self.subTest('new container'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'dict': ['a=b']})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.9': "testfunc" keyword argument "dict" of type list.*""")
with self.subTest('new container set to default'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {'new_dict': {}})
self.assertRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "new_dict" of type dict.*""")
with self.subTest('new container default'), mock.patch('sys.stdout', io.StringIO()) as out:
_(None, mock.Mock(subproject=''), [], {})
self.assertNotRegex(out.getvalue(), r"""WARNING:.Project targets '1.0'.*introduced in '1.1': "testfunc" keyword argument "new_dict" of type dict.*""")
def test_typed_kwarg_evolve(self) -> None:
k = KwargInfo('foo', str, required=True, default='foo')
v = k.evolve(default='bar')
self.assertEqual(k.name, 'foo')
self.assertEqual(k.name, v.name)
self.assertEqual(k.types, str)
self.assertEqual(k.types, v.types)
self.assertEqual(k.required, True)
self.assertEqual(k.required, v.required)
self.assertEqual(k.default, 'foo')
self.assertEqual(v.default, 'bar')
def test_typed_kwarg_default_type(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('no_default', (str, ContainerTypeInfo(list, str), NoneType)),
KwargInfo('str_default', (str, ContainerTypeInfo(list, str)), default=''),
KwargInfo('list_default', (str, ContainerTypeInfo(list, str)), default=['']),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
self.assertEqual(kwargs['no_default'], None)
self.assertEqual(kwargs['str_default'], '')
self.assertEqual(kwargs['list_default'], [''])
_(None, mock.Mock(), [], {})
def test_typed_kwarg_invalid_default_type(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('invalid_default', (str, ContainerTypeInfo(list, str), NoneType), default=42),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
pass
self.assertRaises(AssertionError, _, None, mock.Mock(), [], {})
def test_typed_kwarg_container_in_tuple(self) -> None:
@typed_kwargs(
'testfunc',
KwargInfo('input', (str, ContainerTypeInfo(list, str))),
)
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
self.assertEqual(kwargs['input'], args[0])
_(None, mock.Mock(), [''], {'input': ''})
_(None, mock.Mock(), [['']], {'input': ['']})
self.assertRaises(InvalidArguments, _, None, mock.Mock(), [], {'input': 42})
def test_detect_cpu_family(self) -> None:
"""Test the various cpu families that we detect and normalize.
This is particularly useful as both documentation, and to keep testing
platforms that are less common.
"""
@contextlib.contextmanager
def mock_trial(value: str) -> T.Iterable[None]:
"""Mock all of the ways we could get the trial at once."""
mocked = mock.Mock(return_value=value)
with mock.patch('mesonbuild.environment.detect_windows_arch', mocked), \
mock.patch('mesonbuild.environment.platform.processor', mocked), \
mock.patch('mesonbuild.environment.platform.machine', mocked):
yield
cases = [
('x86', 'x86'),
('i386', 'x86'),
('bepc', 'x86'), # Haiku
('earm', 'arm'), # NetBSD
('arm', 'arm'),
('ppc64', 'ppc64'),
('powerpc64', 'ppc64'),
('powerpc', 'ppc'),
('ppc', 'ppc'),
('macppc', 'ppc'),
('power macintosh', 'ppc'),
('mips64el', 'mips'),
('mips64', 'mips'),
('mips', 'mips'),
('mipsel', 'mips'),
('ip30', 'mips'),
('ip35', 'mips'),
('parisc64', 'parisc'),
('sun4u', 'sparc64'),
('sun4v', 'sparc64'),
('amd64', 'x86_64'),
('x64', 'x86_64'),
('i86pc', 'x86_64'), # Solaris
('aarch64', 'aarch64'),
('aarch64_be', 'aarch64'),
]
cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock())
with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=False)):
for test, expected in cases:
with self.subTest(test, has_define=False), mock_trial(test):
actual = mesonbuild.environment.detect_cpu_family({'c': cc})
self.assertEqual(actual, expected)
with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=True)):
for test, expected in [('x86_64', 'x86'), ('aarch64', 'arm'), ('ppc', 'ppc64'), ('mips64', 'mips64')]:
with self.subTest(test, has_define=True), mock_trial(test):
actual = mesonbuild.environment.detect_cpu_family({'c': cc})
self.assertEqual(actual, expected)
# machine_info_can_run calls detect_cpu_family with no compilers at all
with mock.patch(
'mesonbuild.environment.any_compiler_has_define',
mock.Mock(side_effect=AssertionError('Should not be called')),
):
for test, expected in [('mips64', 'mips64')]:
with self.subTest(test, has_compiler=False), mock_trial(test):
actual = mesonbuild.environment.detect_cpu_family({})
self.assertEqual(actual, expected)
def test_detect_cpu(self) -> None:
@contextlib.contextmanager
def mock_trial(value: str) -> T.Iterable[None]:
"""Mock all of the ways we could get the trial at once."""
mocked = mock.Mock(return_value=value)
with mock.patch('mesonbuild.environment.detect_windows_arch', mocked), \
mock.patch('mesonbuild.environment.platform.processor', mocked), \
mock.patch('mesonbuild.environment.platform.machine', mocked):
yield
cases = [
('amd64', 'x86_64'),
('x64', 'x86_64'),
('i86pc', 'x86_64'),
('earm', 'arm'),
('mips64el', 'mips'),
('mips64', 'mips'),
('mips', 'mips'),
('mipsel', 'mips'),
('aarch64', 'aarch64'),
('aarch64_be', 'aarch64'),
]
cc = ClangCCompiler([], [], 'fake', MachineChoice.HOST, False, mock.Mock())
with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=False)):
for test, expected in cases:
with self.subTest(test, has_define=False), mock_trial(test):
actual = mesonbuild.environment.detect_cpu({'c': cc})
self.assertEqual(actual, expected)
with mock.patch('mesonbuild.environment.any_compiler_has_define', mock.Mock(return_value=True)):
for test, expected in [('x86_64', 'i686'), ('aarch64', 'arm'), ('ppc', 'ppc64'), ('mips64', 'mips64')]:
with self.subTest(test, has_define=True), mock_trial(test):
actual = mesonbuild.environment.detect_cpu({'c': cc})
self.assertEqual(actual, expected)
with mock.patch(
'mesonbuild.environment.any_compiler_has_define',
mock.Mock(side_effect=AssertionError('Should not be called')),
):
for test, expected in [('mips64', 'mips64')]:
with self.subTest(test, has_compiler=False), mock_trial(test):
actual = mesonbuild.environment.detect_cpu({})
self.assertEqual(actual, expected)
@mock.patch('mesonbuild.interpreter.Interpreter.load_root_meson_file', mock.Mock(return_value=None))
@mock.patch('mesonbuild.interpreter.Interpreter.sanity_check_ast', mock.Mock(return_value=None))
@mock.patch('mesonbuild.interpreter.Interpreter.parse_project', mock.Mock(return_value=None))
def test_interpreter_unpicklable(self) -> None:
build = mock.Mock()
build.environment = mock.Mock()
build.environment.get_source_dir = mock.Mock(return_value='')
with mock.patch('mesonbuild.interpreter.Interpreter._redetect_machines', mock.Mock()), \
self.assertRaises(mesonbuild.mesonlib.MesonBugException):
i = mesonbuild.interpreter.Interpreter(build)
pickle.dumps(i)
def test_major_versions_differ(self) -> None:
# Return True when going to next major release, when going to dev cycle,
# when going to rc cycle or when going out of rc cycle.
self.assertTrue(coredata.major_versions_differ('0.59.0', '0.60.0'))
self.assertTrue(coredata.major_versions_differ('0.59.0', '0.59.99'))
self.assertTrue(coredata.major_versions_differ('0.59.0', '0.60.0.rc1'))
self.assertTrue(coredata.major_versions_differ('0.59.99', '0.60.0.rc1'))
self.assertTrue(coredata.major_versions_differ('0.60.0.rc1', '0.60.0'))
# Return False when going to next point release or when staying in dev/rc cycle.
self.assertFalse(coredata.major_versions_differ('0.60.0', '0.60.0'))
self.assertFalse(coredata.major_versions_differ('0.60.0', '0.60.1'))
self.assertFalse(coredata.major_versions_differ('0.59.99', '0.59.99'))
self.assertFalse(coredata.major_versions_differ('0.60.0.rc1', '0.60.0.rc2'))
def test_option_key_from_string(self) -> None:
cases = [
('c_args', OptionKey('args', lang='c', _type=OptionType.COMPILER)),
('build.cpp_args', OptionKey('args', machine=MachineChoice.BUILD, lang='cpp', _type=OptionType.COMPILER)),
('prefix', OptionKey('prefix', _type=OptionType.BUILTIN)),
('made_up', OptionKey('made_up', _type=OptionType.PROJECT)),
# TODO: the from_String method should be splitting the prefix off of
# these, as we have the type already, but it doesn't. For now have a
# test so that we don't change the behavior un-intentionally
('b_lto', OptionKey('b_lto', _type=OptionType.BASE)),
('backend_startup_project', OptionKey('backend_startup_project', _type=OptionType.BACKEND)),
]
for raw, expected in cases:
with self.subTest(raw):
self.assertEqual(OptionKey.from_string(raw), expected)