|
|
|
# Copyright 2016-2021 The Meson development team
|
|
|
|
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
from configparser import ConfigParser
|
|
|
|
from mesonbuild.mesonlib.universal import OptionType
|
|
|
|
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.compilers
|
|
|
|
import mesonbuild.envconfig
|
|
|
|
import mesonbuild.environment
|
|
|
|
import mesonbuild.modules.gnome
|
|
|
|
from mesonbuild import coredata
|
|
|
|
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,
|
|
|
|
)
|
|
|
|
from mesonbuild.interpreter.type_checking import in_set_validator, NoneType
|
|
|
|
from mesonbuild.dependencies import PkgConfigDependency
|
|
|
|
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 = mesonbuild.compilers.ClangCCompiler([], 'fake', MachineChoice.HOST, False, mock.Mock())
|
|
|
|
a = cc.compiler_args(['-I.'])
|
|
|
|
#first we are checking if the tree construction deduplicates the correct -I argument
|
|
|
|
a += ['-I..']
|
|
|
|
a += ['-I./tests/']
|
|
|
|
a += ['-I./tests2/']
|
|
|
|
#think this here as assertion, we cannot apply it, otherwise the CompilerArgs would already flush the changes:
|
|
|
|
# assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..', '-I.'])
|
|
|
|
a += ['-I.']
|
|
|
|
a += ['-I.', '-I./tests/']
|
|
|
|
self.assertEqual(a, ['-I.', '-I./tests/', '-I./tests2/', '-I..'])
|
|
|
|
|
|
|
|
#then we are checking that when CompilerArgs already have a build container list, that the deduplication is taking the correct one
|
|
|
|
a += ['-I.', '-I./tests2/']
|
|
|
|
self.assertEqual(a, ['-I.', '-I./tests2/', '-I./tests/', '-I..'])
|
|
|
|
|
|
|
|
def test_compiler_args_class_d(self):
|
|
|
|
d = mesonbuild.compilers.DmdDCompiler([], 'fake', MachineChoice.HOST, 'info', 'arch')
|
|
|
|
# check include order is kept when deduplicating
|
|
|
|
a = d.compiler_args(['-Ifirst', '-Isecond', '-Ithird'])
|
|
|
|
a += ['-Ifirst']
|
|
|
|
self.assertEqual(a, ['-Ifirst', '-Isecond', '-Ithird'])
|
|
|
|
|
|
|
|
def test_compiler_args_class_clike(self):
|
|
|
|
cc = mesonbuild.compilers.ClangCCompiler([], 'fake', MachineChoice.HOST, False, mock.Mock())
|
|
|
|
# Test that empty initialization works
|
|
|
|
a = cc.compiler_args()
|
|
|
|
self.assertEqual(a, [])
|
|
|
|
# Test that list initialization works
|
|
|
|
a = cc.compiler_args(['-I.', '-I..'])
|
|
|
|
self.assertEqual(a, ['-I.', '-I..'])
|
|
|
|
# Test that there is no de-dup on initialization
|
|
|
|
self.assertEqual(cc.compiler_args(['-I.', '-I.']), ['-I.', '-I.'])
|
|
|
|
|
|
|
|
## Test that appending works
|
|
|
|
a.append('-I..')
|
|
|
|
self.assertEqual(a, ['-I..', '-I.'])
|
|
|
|
a.append('-O3')
|
|
|
|
self.assertEqual(a, ['-I..', '-I.', '-O3'])
|
|
|
|
|
|
|
|
## Test that in-place addition works
|
|
|
|
a += ['-O2', '-O2']
|
|
|
|
self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2', '-O2'])
|
|
|
|
# Test that removal works
|
|
|
|
a.remove('-O2')
|
|
|
|
self.assertEqual(a, ['-I..', '-I.', '-O3', '-O2'])
|
|
|
|
# Test that de-dup happens on addition
|
|
|
|
a += ['-Ifoo', '-Ifoo']
|
|
|
|
self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
|
|
|
|
|
|
|
|
# .extend() is just +=, so we don't test it
|
|
|
|
|
|
|
|
## Test that addition works
|
|
|
|
# Test that adding a list with just one old arg works and yields the same array
|
|
|
|
a = a + ['-Ifoo']
|
|
|
|
self.assertEqual(a, ['-Ifoo', '-I..', '-I.', '-O3', '-O2'])
|
|
|
|
# Test that adding a list with one arg new and one old works
|
|
|
|
a = a + ['-Ifoo', '-Ibaz']
|
|
|
|
self.assertEqual(a, ['-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2'])
|
|
|
|
# Test that adding args that must be prepended and appended works
|
|
|
|
a = a + ['-Ibar', '-Wall']
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
|
|
|
|
|
|
|
|
## Test that reflected addition works
|
|
|
|
# Test that adding to a list with just one old arg works and yields the same array
|
|
|
|
a = ['-Ifoo'] + a
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-O3', '-O2', '-Wall'])
|
|
|
|
# Test that adding to a list with just one new arg that is not pre-pended works
|
|
|
|
a = ['-Werror'] + a
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Werror', '-O3', '-O2', '-Wall'])
|
|
|
|
# Test that adding to a list with two new args preserves the order
|
|
|
|
a = ['-Ldir', '-Lbah'] + a
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
|
|
|
|
# Test that adding to a list with old args does nothing
|
|
|
|
a = ['-Ibar', '-Ibaz', '-Ifoo'] + a
|
|
|
|
self.assertEqual(a, ['-Ibar', '-Ifoo', '-Ibaz', '-I..', '-I.', '-Ldir', '-Lbah', '-Werror', '-O3', '-O2', '-Wall'])
|
|
|
|
|
|
|
|
## Test that adding libraries works
|
|
|
|
l = cc.compiler_args(['-Lfoodir', '-lfoo'])
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo'])
|
|
|
|
# Adding a library and a libpath appends both correctly
|
|
|
|
l += ['-Lbardir', '-lbar']
|
|
|
|
self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
|
|
|
|
# Adding the same library again does nothing
|
|
|
|
l += ['-lbar']
|
|
|
|
self.assertEqual(l, ['-Lbardir', '-Lfoodir', '-lfoo', '-lbar'])
|
|
|
|
|
|
|
|
## Test that 'direct' append and extend works
|
|
|
|
l = cc.compiler_args(['-Lfoodir', '-lfoo'])
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo'])
|
|
|
|
# Direct-adding a library and a libpath appends both correctly
|
|
|
|
l.extend_direct(['-Lbardir', '-lbar'])
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar'])
|
|
|
|
# Direct-adding the same library again still adds it
|
|
|
|
l.append_direct('-lbar')
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar'])
|
|
|
|
# Direct-adding with absolute path deduplicates
|
|
|
|
l.append_direct('/libbaz.a')
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
|
|
|
|
# Adding libbaz again does nothing
|
|
|
|
l.append_direct('/libbaz.a')
|
|
|
|
self.assertEqual(l, ['-Lfoodir', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a'])
|
|
|
|
|
|
|
|
def test_compiler_args_class_gnuld(self):
|
|
|
|
## Test --start/end-group
|
|
|
|
linker = mesonbuild.linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', [])
|
|
|
|
gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker)
|
|
|
|
## Ensure that the fake compiler is never called by overriding the relevant function
|
|
|
|
gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include']
|
|
|
|
## Test that 'direct' append and extend works
|
|
|
|
l = gcc.compiler_args(['-Lfoodir', '-lfoo'])
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group'])
|
|
|
|
# Direct-adding a library and a libpath appends both correctly
|
|
|
|
l.extend_direct(['-Lbardir', '-lbar'])
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-Wl,--end-group'])
|
|
|
|
# Direct-adding the same library again still adds it
|
|
|
|
l.append_direct('-lbar')
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '-Wl,--end-group'])
|
|
|
|
# Direct-adding with absolute path deduplicates
|
|
|
|
l.append_direct('/libbaz.a')
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
|
|
|
|
# Adding libbaz again does nothing
|
|
|
|
l.append_direct('/libbaz.a')
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group'])
|
|
|
|
# Adding a non-library argument doesn't include it in the group
|
|
|
|
l += ['-Lfoo', '-Wl,--export-dynamic']
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--end-group', '-Wl,--export-dynamic'])
|
|
|
|
# -Wl,-lfoo is detected as a library and gets added to the group
|
|
|
|
l.append('-Wl,-ldl')
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoo', '-Lfoodir', '-Wl,--start-group', '-lfoo', '-Lbardir', '-lbar', '-lbar', '/libbaz.a', '-Wl,--export-dynamic', '-Wl,-ldl', '-Wl,--end-group'])
|
|
|
|
|
|
|
|
def test_compiler_args_remove_system(self):
|
|
|
|
## Test --start/end-group
|
|
|
|
linker = mesonbuild.linkers.GnuBFDDynamicLinker([], MachineChoice.HOST, '-Wl,', [])
|
|
|
|
gcc = mesonbuild.compilers.GnuCCompiler([], 'fake', False, MachineChoice.HOST, mock.Mock(), linker=linker)
|
|
|
|
## Ensure that the fake compiler is never called by overriding the relevant function
|
|
|
|
gcc.get_default_include_dirs = lambda: ['/usr/include', '/usr/share/include', '/usr/local/include']
|
|
|
|
## Test that 'direct' append and extend works
|
|
|
|
l = gcc.compiler_args(['-Lfoodir', '-lfoo'])
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group'])
|
|
|
|
## Test that to_native removes all system includes
|
|
|
|
l += ['-isystem/usr/include', '-isystem=/usr/share/include', '-DSOMETHING_IMPORTANT=1', '-isystem', '/usr/local/include']
|
|
|
|
self.assertEqual(l.to_native(copy=True), ['-Lfoodir', '-Wl,--start-group', '-lfoo', '-Wl,--end-group', '-DSOMETHING_IMPORTANT=1'])
|
|
|
|
|
|
|
|
def test_string_templates_substitution(self):
|
|
|
|
dictfunc = mesonbuild.mesonlib.get_filenames_templates_dict
|
|
|
|
substfunc = mesonbuild.mesonlib.substitute_values
|
|
|
|
ME = mesonbuild.mesonlib.MesonException
|
|
|
|
|
|
|
|
# Identity
|
|
|
|
self.assertEqual(dictfunc([], []), {})
|
|
|
|
|
|
|
|
# One input, no outputs
|
|
|
|
inputs = ['bar/foo.c.in']
|
|
|
|
outputs = []
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
|
|
|
|
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@INPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
|
|
|
|
cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
[inputs[0] + '.out'] + [d['@PLAINNAME@'] + '.ok'] + cmd[2:])
|
|
|
|
cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
|
|
|
|
cmd = ['@OUTPUT@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
|
|
|
|
# One input, one output
|
|
|
|
inputs = ['bar/foo.c.in']
|
|
|
|
outputs = ['out.c']
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
|
|
|
|
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
|
|
|
|
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': '.'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@INPUT@.out', '@OUTPUT@', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
[inputs[0] + '.out'] + outputs + cmd[2:])
|
|
|
|
cmd = ['@INPUT0@.out', '@PLAINNAME@.ok', '@OUTPUT0@']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
[inputs[0] + '.out', d['@PLAINNAME@'] + '.ok'] + outputs)
|
|
|
|
cmd = ['@INPUT@', '@BASENAME@.hah', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d),
|
|
|
|
inputs + [d['@BASENAME@'] + '.hah'] + cmd[2:])
|
|
|
|
|
|
|
|
# One input, one output with a subdir
|
|
|
|
outputs = ['dir/out.c']
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0],
|
|
|
|
'@PLAINNAME@': 'foo.c.in', '@BASENAME@': 'foo.c',
|
|
|
|
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
|
|
|
|
# Two inputs, no outputs
|
|
|
|
inputs = ['bar/foo.c.in', 'baz/foo.c.in']
|
|
|
|
outputs = []
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1]}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@INPUT@', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), inputs + cmd[1:])
|
|
|
|
cmd = ['@INPUT0@.out', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out'] + cmd[1:])
|
|
|
|
cmd = ['@INPUT0@.out', '@INPUT1@.ok', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [inputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
|
|
|
|
cmd = ['@INPUT0@', '@INPUT1@', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), inputs + cmd[2:])
|
|
|
|
# Many inputs, can't use @INPUT@ like this
|
|
|
|
cmd = ['@INPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough inputs
|
|
|
|
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Too many inputs
|
|
|
|
cmd = ['@PLAINNAME@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
cmd = ['@BASENAME@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# No outputs
|
|
|
|
cmd = ['@OUTPUT@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
cmd = ['@OUTPUT0@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
cmd = ['@OUTDIR@']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
|
|
|
|
# Two inputs, one output
|
|
|
|
outputs = ['dir/out.c']
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
|
|
|
|
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTDIR@': 'dir'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@OUTPUT@', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
|
|
|
|
cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out'] + cmd[1:])
|
|
|
|
cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok'] + cmd[2:])
|
|
|
|
# Many inputs, can't use @INPUT@ like this
|
|
|
|
cmd = ['@INPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough inputs
|
|
|
|
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough outputs
|
|
|
|
cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
|
|
|
|
# Two inputs, two outputs
|
|
|
|
outputs = ['dir/out.c', 'dir/out2.c']
|
|
|
|
ret = dictfunc(inputs, outputs)
|
|
|
|
d = {'@INPUT@': inputs, '@INPUT0@': inputs[0], '@INPUT1@': inputs[1],
|
|
|
|
'@OUTPUT@': outputs, '@OUTPUT0@': outputs[0], '@OUTPUT1@': outputs[1],
|
|
|
|
'@OUTDIR@': 'dir'}
|
|
|
|
# Check dictionary
|
|
|
|
self.assertEqual(ret, d)
|
|
|
|
# Check substitutions
|
|
|
|
cmd = ['some', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), cmd)
|
|
|
|
cmd = ['@OUTPUT@', 'ordinary', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), outputs + cmd[1:])
|
|
|
|
cmd = ['@OUTPUT0@', '@OUTPUT1@', 'strings']
|
|
|
|
self.assertEqual(substfunc(cmd, d), outputs + cmd[2:])
|
|
|
|
cmd = ['@OUTPUT0@.out', '@INPUT1@.ok', '@OUTDIR@']
|
|
|
|
self.assertEqual(substfunc(cmd, d), [outputs[0] + '.out', inputs[1] + '.ok', 'dir'])
|
|
|
|
# Many inputs, can't use @INPUT@ like this
|
|
|
|
cmd = ['@INPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough inputs
|
|
|
|
cmd = ['@INPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Not enough outputs
|
|
|
|
cmd = ['@OUTPUT2@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
# Many outputs, can't use @OUTPUT@ like this
|
|
|
|
cmd = ['@OUTPUT@.out', 'ordinary', 'strings']
|
|
|
|
self.assertRaises(ME, substfunc, cmd, d)
|
|
|
|
|
|
|
|
def test_needs_exe_wrapper_override(self):
|
|
|
|
config = ConfigParser()
|
|
|
|
config['binaries'] = {
|
|
|
|
'c': '\'/usr/bin/gcc\'',
|
|
|
|
}
|
|
|
|
config['host_machine'] = {
|
|
|
|
'system': '\'linux\'',
|
|
|
|
'cpu_family': '\'arm\'',
|
|
|
|
'cpu': '\'armv7\'',
|
|
|
|
'endian': '\'little\'',
|
|
|
|
}
|
|
|
|
# Can not be used as context manager because we need to
|
|
|
|
# open it a second time and this is not possible on
|
|
|
|
# Windows.
|
|
|
|
configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
|
|
|
|
configfilename = configfile.name
|
|
|
|
config.write(configfile)
|
|
|
|
configfile.flush()
|
|
|
|
configfile.close()
|
|
|
|
opts = get_fake_options()
|
|
|
|
opts.cross_file = (configfilename,)
|
|
|
|
env = get_fake_env(opts=opts)
|
|
|
|
detected_value = env.need_exe_wrapper()
|
|
|
|
os.unlink(configfilename)
|
|
|
|
|
|
|
|
desired_value = not detected_value
|
|
|
|
config['properties'] = {
|
|
|
|
'needs_exe_wrapper': 'true' if desired_value else 'false'
|
|
|
|
}
|
|
|
|
|
|
|
|
configfile = tempfile.NamedTemporaryFile(mode='w+', delete=False)
|
|
|
|
configfilename = configfile.name
|
|
|
|
config.write(configfile)
|
|
|
|
configfile.close()
|
|
|
|
opts = get_fake_options()
|
|
|
|
opts.cross_file = (configfilename,)
|
|
|
|
env = get_fake_env(opts=opts)
|
|
|
|
forced_value = env.need_exe_wrapper()
|
|
|
|
os.unlink(configfilename)
|
|
|
|
|
|
|
|
self.assertEqual(forced_value, desired_value)
|
|
|
|
|
|
|
|
def test_listify(self):
|
|
|
|
listify = mesonbuild.mesonlib.listify
|
|
|
|
# Test sanity
|
|
|
|
self.assertEqual([1], listify(1))
|
|
|
|
self.assertEqual([], listify([]))
|
|
|
|
self.assertEqual([1], listify([1]))
|
|
|
|
# Test flattening
|
|
|
|
self.assertEqual([1, 2, 3], listify([1, [2, 3]]))
|
|
|
|
self.assertEqual([1, 2, 3], listify([1, [2, [3]]]))
|
|
|
|
self.assertEqual([1, [2, [3]]], listify([1, [2, [3]]], flatten=False))
|
|
|
|
# Test flattening and unholdering
|
|
|
|
class TestHeldObj(mesonbuild.mesonlib.HoldableObject):
|
|
|
|
def __init__(self, val: int) -> None:
|
|
|
|
self._val = val
|
|
|
|
class MockInterpreter:
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self.subproject = ''
|
|
|
|
self.environment = None
|
|
|
|
heldObj1 = TestHeldObj(1)
|
|
|
|
holder1 = ObjectHolder(heldObj1, MockInterpreter())
|
|
|
|
self.assertEqual([holder1], listify(holder1))
|
|
|
|
self.assertEqual([holder1], listify([holder1]))
|
|
|
|
self.assertEqual([holder1, 2], listify([holder1, 2]))
|
|
|
|
self.assertEqual([holder1, 2, 3], listify([holder1, 2, [3]]))
|
|
|
|
|
|
|
|
def test_extract_as_list(self):
|
|
|
|
extract = mesonbuild.mesonlib.extract_as_list
|
|
|
|
# Test sanity
|
|
|
|
kwargs = {'sources': [1, 2, 3]}
|
|
|
|
self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
|
|
|
|
self.assertEqual(kwargs, {'sources': [1, 2, 3]})
|
|
|
|
self.assertEqual([1, 2, 3], extract(kwargs, 'sources', pop=True))
|
|
|
|
self.assertEqual(kwargs, {})
|
|
|
|
|
|
|
|
class TestHeldObj(mesonbuild.mesonlib.HoldableObject):
|
|
|
|
pass
|
|
|
|
class MockInterpreter:
|
|
|
|
def __init__(self) -> None:
|
|
|
|
self.subproject = ''
|
|
|
|
self.environment = None
|
|
|
|
heldObj = TestHeldObj()
|
|
|
|
|
|
|
|
# Test unholding
|
|
|
|
holder3 = ObjectHolder(heldObj, MockInterpreter())
|
|
|
|
kwargs = {'sources': [1, 2, holder3]}
|
|
|
|
self.assertEqual(kwargs, {'sources': [1, 2, holder3]})
|
|
|
|
|
|
|
|
# flatten nested lists
|
|
|
|
kwargs = {'sources': [1, [2, [3]]]}
|
|
|
|
self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
|
|
|
|
|
|
|
|
def _test_all_naming(self, cc, env, patterns, platform):
|
|
|
|
shr = patterns[platform]['shared']
|
|
|
|
stc = patterns[platform]['static']
|
|
|
|
shrstc = shr + tuple(x for x in stc if x not in shr)
|
|
|
|
stcshr = stc + tuple(x for x in shr if x not in stc)
|
|
|
|
p = cc.get_library_naming(env, LibType.SHARED)
|
|
|
|
self.assertEqual(p, shr)
|
|
|
|
p = cc.get_library_naming(env, LibType.STATIC)
|
|
|
|
self.assertEqual(p, stc)
|
|
|
|
p = cc.get_library_naming(env, LibType.PREFER_STATIC)
|
|
|
|
self.assertEqual(p, stcshr)
|
|
|
|
p = cc.get_library_naming(env, LibType.PREFER_SHARED)
|
|
|
|
self.assertEqual(p, shrstc)
|
|
|
|
# Test find library by mocking up openbsd
|
|
|
|
if platform != 'openbsd':
|
|
|
|
return
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
for i in ['libfoo.so.6.0', 'libfoo.so.5.0', 'libfoo.so.54.0', 'libfoo.so.66a.0b', 'libfoo.so.70.0.so.1']:
|
|
|
|
libpath = Path(tmpdir) / i
|
|
|
|
libpath.write_text('', encoding='utf-8')
|
|
|
|
found = cc._find_library_real('foo', env, [tmpdir], '', LibType.PREFER_SHARED)
|
|
|
|
self.assertEqual(os.path.basename(found[0]), 'libfoo.so.54.0')
|
|
|
|
|
|
|
|
def test_find_library_patterns(self):
|
|
|
|
'''
|
|
|
|
Unit test for the library search patterns used by find_library()
|
|
|
|
'''
|
|
|
|
unix_static = ('lib{}.a', '{}.a')
|
|
|
|
msvc_static = ('lib{}.a', 'lib{}.lib', '{}.a', '{}.lib')
|
|
|
|
# This is the priority list of pattern matching for library searching
|
|
|
|
patterns = {'openbsd': {'shared': ('lib{}.so', '{}.so', 'lib{}.so.[0-9]*.[0-9]*', '{}.so.[0-9]*.[0-9]*'),
|
|
|
|
'static': unix_static},
|
|
|
|
'linux': {'shared': ('lib{}.so', '{}.so'),
|
|
|
|
'static': unix_static},
|
|
|
|
'darwin': {'shared': ('lib{}.dylib', 'lib{}.so', '{}.dylib', '{}.so'),
|
|
|
|
'static': unix_static},
|
|
|
|
'cygwin': {'shared': ('cyg{}.dll', 'cyg{}.dll.a', 'lib{}.dll',
|
|
|
|
'lib{}.dll.a', '{}.dll', '{}.dll.a'),
|
|
|
|
'static': ('cyg{}.a',) + unix_static},
|
|
|
|
'windows-msvc': {'shared': ('lib{}.lib', '{}.lib'),
|
|
|
|
'static': msvc_static},
|
|
|
|
'windows-mingw': {'shared': ('lib{}.dll.a', 'lib{}.lib', 'lib{}.dll',
|
|
|
|
'{}.dll.a', '{}.lib', '{}.dll'),
|
|
|
|
'static': msvc_static}}
|
|
|
|
env = get_fake_env()
|
|
|
|
cc = detect_c_compiler(env, MachineChoice.HOST)
|
|
|
|
if is_osx():
|
|
|
|
self._test_all_naming(cc, env, patterns, 'darwin')
|
|
|
|
elif is_cygwin():
|
|
|
|
self._test_all_naming(cc, env, patterns, 'cygwin')
|
|
|
|
elif is_windows():
|
|
|
|
if cc.get_argument_syntax() == 'msvc':
|
|
|
|
self._test_all_naming(cc, env, patterns, 'windows-msvc')
|
|
|
|
else:
|
|
|
|
self._test_all_naming(cc, env, patterns, 'windows-mingw')
|
|
|
|
elif is_openbsd():
|
|
|
|
self._test_all_naming(cc, env, patterns, 'openbsd')
|
|
|
|
else:
|
|
|
|
self._test_all_naming(cc, env, patterns, 'linux')
|
|
|
|
env.machines.host.system = 'openbsd'
|
|
|
|
self._test_all_naming(cc, env, patterns, 'openbsd')
|
|
|
|
env.machines.host.system = 'darwin'
|
|
|
|
self._test_all_naming(cc, env, patterns, 'darwin')
|
|
|
|
env.machines.host.system = 'cygwin'
|
|
|
|
self._test_all_naming(cc, env, patterns, 'cygwin')
|
|
|
|
env.machines.host.system = 'windows'
|
|
|
|
self._test_all_naming(cc, env, patterns, 'windows-mingw')
|
|
|
|
|
|
|
|
@skipIfNoPkgconfig
|
|
|
|
def test_pkgconfig_parse_libs(self):
|
|
|
|
'''
|
|
|
|
Unit test for parsing of pkg-config output to search for libraries
|
|
|
|
|
|
|
|
https://github.com/mesonbuild/meson/issues/3951
|
|
|
|
'''
|
|
|
|
def create_static_lib(name):
|
|
|
|
if not is_osx():
|
|
|
|
name.open('w', encoding='utf-8').close()
|
|
|
|
return
|
|
|
|
src = name.with_suffix('.c')
|
|
|
|
out = name.with_suffix('.o')
|
|
|
|
with src.open('w', encoding='utf-8') as f:
|
|
|
|
f.write('int meson_foobar (void) { return 0; }')
|
|
|
|
subprocess.check_call(['clang', '-c', str(src), '-o', str(out)])
|
|
|
|
subprocess.check_call(['ar', 'csr', str(name), str(out)])
|
|
|
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
pkgbin = ExternalProgram('pkg-config', command=['pkg-config'], silent=True)
|
|
|
|
env = get_fake_env()
|
|
|
|
compiler = detect_c_compiler(env, MachineChoice.HOST)
|
|
|
|
env.coredata.compilers.host = {'c': compiler}
|
|
|
|
env.coredata.options[OptionKey('link_args', lang='c')] = FakeCompilerOptions()
|
|
|
|
p1 = Path(tmpdir) / '1'
|
|
|
|
p2 = Path(tmpdir) / '2'
|
|
|
|
p1.mkdir()
|
|
|
|
p2.mkdir()
|
|
|
|
# libfoo.a is in one prefix
|
|
|
|
create_static_lib(p1 / 'libfoo.a')
|
|
|
|
# libbar.a is in both prefixes
|
|
|
|
create_static_lib(p1 / 'libbar.a')
|
|
|
|
create_static_lib(p2 / 'libbar.a')
|
|
|
|
# Ensure that we never statically link to these
|
|
|
|
create_static_lib(p1 / 'libpthread.a')
|
|
|
|
create_static_lib(p1 / 'libm.a')
|
|
|
|
create_static_lib(p1 / 'libc.a')
|
|
|
|
create_static_lib(p1 / 'libdl.a')
|
|
|
|
create_static_lib(p1 / 'librt.a')
|
|
|
|
|
|
|
|
def fake_call_pkgbin(self, args, env=None):
|
|
|
|
if '--libs' not in args:
|
|
|
|
return 0, '', ''
|
|
|
|
if args[-1] == 'foo':
|
|
|
|
return 0, f'-L{p2.as_posix()} -lfoo -L{p1.as_posix()} -lbar', ''
|
|
|
|
if args[-1] == 'bar':
|
|
|
|
return 0, f'-L{p2.as_posix()} -lbar', ''
|
|
|
|
if args[-1] == 'internal':
|
|
|
|
return 0, f'-L{p1.as_posix()} -lpthread -lm -lc -lrt -ldl', ''
|
|
|
|
|
|
|
|
old_call = PkgConfigDependency._call_pkgbin
|
|
|
|
old_check = PkgConfigDependency.check_pkgconfig
|
|
|
|
PkgConfigDependency._call_pkgbin = fake_call_pkgbin
|
|
|
|
PkgConfigDependency.check_pkgconfig = lambda x, _: pkgbin
|
|
|
|
# Test begins
|
|
|
|
try:
|
|
|
|
kwargs = {'required': True, 'silent': True}
|
|
|
|
foo_dep = PkgConfigDependency('foo', env, kwargs)
|
|
|
|
self.assertEqual(foo_dep.get_link_args(),
|
|
|
|
[(p1 / 'libfoo.a').as_posix(), (p2 / 'libbar.a').as_posix()])
|
|
|
|
bar_dep = PkgConfigDependency('bar', env, kwargs)
|
|
|
|
self.assertEqual(bar_dep.get_link_args(), [(p2 / 'libbar.a').as_posix()])
|
|
|
|
internal_dep = PkgConfigDependency('internal', env, kwargs)
|
|
|
|
if compiler.get_argument_syntax() == 'msvc':
|
|
|
|
self.assertEqual(internal_dep.get_link_args(), [])
|
|
|
|
else:
|
|
|
|
link_args = internal_dep.get_link_args()
|
|
|
|
for link_arg in link_args:
|
|
|
|
for lib in ('pthread', 'm', 'c', 'dl', 'rt'):
|
|
|
|
self.assertNotIn(f'lib{lib}.a', link_arg, msg=link_args)
|
|
|
|
finally:
|
|
|
|
# Test ends
|
|
|
|
PkgConfigDependency._call_pkgbin = old_call
|
|
|
|
PkgConfigDependency.check_pkgconfig = old_check
|
|
|
|
# Reset dependency class to ensure that in-process configure doesn't mess up
|
|
|
|
PkgConfigDependency.pkgbin_cache = {}
|
|
|
|
PkgConfigDependency.class_pkgbin = PerMachine(None, None)
|
|
|
|
|
|
|
|
def test_version_compare(self):
|
|
|
|
comparefunc = mesonbuild.mesonlib.version_compare_many
|
|
|
|
for (a, b, result) in [
|
|
|
|
('0.99.beta19', '>= 0.99.beta14', True),
|
|
|
|
]:
|
|
|
|
self.assertEqual(comparefunc(a, b)[0], result)
|
|
|
|
|
|
|
|
for (a, b, op) in [
|
|
|
|
# examples from https://fedoraproject.org/wiki/Archive:Tools/RPM/VersionComparison
|
|
|
|
("1.0010", "1.9", operator.gt),
|
|
|
|
("1.05", "1.5", operator.eq),
|
|
|
|
("1.0", "1", operator.gt),
|
|
|
|
("2.50", "2.5", operator.gt),
|
|
|
|
("fc4", "fc.4", operator.eq),
|
|
|
|
("FC5", "fc4", operator.lt),
|
|
|
|
("2a", "2.0", operator.lt),
|
|
|
|
("1.0", "1.fc4", operator.gt),
|
|
|
|
("3.0.0_fc", "3.0.0.fc", operator.eq),
|
|
|
|
# from RPM tests
|
|
|
|
("1.0", "1.0", operator.eq),
|
|
|
|
("1.0", "2.0", operator.lt),
|
|
|
|
("2.0", "1.0", operator.gt),
|
|
|
|
("2.0.1", "2.0.1", operator.eq),
|
|
|
|
("2.0", "2.0.1", operator.lt),
|
|
|
|
("2.0.1", "2.0", operator.gt),
|
|
|
|
("2.0.1a", "2.0.1a", operator.eq),
|
|
|
|
("2.0.1a", "2.0.1", operator.gt),
|
|
|
|
("2.0.1", "2.0.1a", operator.lt),
|
|
|
|
("5.5p1", "5.5p1", operator.eq),
|
|
|
|
("5.5p1", "5.5p2", operator.lt),
|
|
|
|
("5.5p2", "5.5p1", operator.gt),
|
|
|
|
("5.5p10", "5.5p10", operator.eq),
|
|
|
|
("5.5p1", "5.5p10", operator.lt),
|
|
|
|
("5.5p10", "5.5p1", operator.gt),
|
|
|
|
("10xyz", "10.1xyz", operator.lt),
|
|
|
|
("10.1xyz", "10xyz", operator.gt),
|
|
|
|
("xyz10", "xyz10", operator.eq),
|
|
|
|
("xyz10", "xyz10.1", operator.lt),
|
|
|
|
("xyz10.1", "xyz10", operator.gt),
|
|
|
|
("xyz.4", "xyz.4", operator.eq),
|
|
|
|
("xyz.4", "8", operator.lt),
|
|
|
|
("8", "xyz.4", operator.gt),
|
|
|
|
("xyz.4", "2", operator.lt),
|
|
|
|
("2", "xyz.4", operator.gt),
|
|
|
|
("5.5p2", "5.6p1", operator.lt),
|
|
|
|
("5.6p1", "5.5p2", operator.gt),
|
|
|
|
("5.6p1", "6.5p1", operator.lt),
|
|
|
|
("6.5p1", "5.6p1", operator.gt),
|
|
|
|
("6.0.rc1", "6.0", operator.gt),
|
|
|
|
("6.0", "6.0.rc1", operator.lt),
|
|
|
|
("10b2", "10a1", operator.gt),
|
|
|
|
("10a2", "10b2", operator.lt),
|
|
|
|
("1.0aa", "1.0aa", operator.eq),
|
|
|
|
("1.0a", "1.0aa", operator.lt),
|
|
|
|
("1.0aa", "1.0a", operator.gt),
|
|
|
|
("10.0001", "10.0001", operator.eq),
|
|
|
|
("10.0001", "10.1", operator.eq),
|
|
|
|
("10.1", "10.0001", operator.eq),
|
|
|
|
("10.0001", "10.0039", operator.lt),
|
|
|
|
("10.0039", "10.0001", operator.gt),
|
|
|
|
("4.999.9", "5.0", operator.lt),
|
|
|
|
("5.0", "4.999.9", operator.gt),
|
|
|
|
("20101121", "20101121", operator.eq),
|
|
|
|
("20101121", "20101122", operator.lt),
|
|
|
|
("20101122", "20101121", operator.gt),
|
|
|
|
("2_0", "2_0", operator.eq),
|
|
|
|
("2.0", "2_0", operator.eq),
|
|
|
|
("2_0", "2.0", operator.eq),
|
|
|
|
("a", "a", operator.eq),
|
|
|
|
("a+", "a+", operator.eq),
|
|
|
|
("a+", "a_", operator.eq),
|
|
|
|
("a_", "a+", operator.eq),
|
|
|
|
("+a", "+a", operator.eq),
|
|
|
|
("+a", "_a", operator.eq),
|
|
|
|
("_a", "+a", operator.eq),
|
|
|
|
("+_", "+_", operator.eq),
|
|
|
|
("_+", "+_", operator.eq),
|
|
|
|
("_+", "_+", operator.eq),
|
|
|
|
("+", "_", operator.eq),
|
|
|
|
("_", "+", operator.eq),
|
|
|
|
# other tests
|
|
|
|
('0.99.beta19', '0.99.beta14', operator.gt),
|
|
|
|
("1.0.0", "2.0.0", operator.lt),
|
|
|
|
(".0.0", "2.0.0", operator.lt),
|
|
|
|
("alpha", "beta", operator.lt),
|
|
|
|
("1.0", "1.0.0", operator.lt),
|
|
|
|
("2.456", "2.1000", operator.lt),
|
|
|
|
("2.1000", "3.111", operator.lt),
|
|
|
|
("2.001", "2.1", operator.eq),
|
|
|
|
("2.34", "2.34", operator.eq),
|
|
|
|
("6.1.2", "6.3.8", operator.lt),
|
|
|
|
("1.7.3.0", "2.0.0", operator.lt),
|
|
|
|
("2.24.51", "2.25", operator.lt),
|
|
|
|
("2.1.5+20120813+gitdcbe778", "2.1.5", operator.gt),
|
|
|
|
("3.4.1", "3.4b1", operator.gt),
|
|
|
|
("041206", "200090325", operator.lt),
|
|
|
|
("0.6.2+git20130413", "0.6.2", operator.gt),
|
|
|
|
("2.6.0+bzr6602", "2.6.0", operator.gt),
|
|
|
|
("2.6.0", "2.6b2", operator.gt),
|
|
|
|
("2.6.0+bzr6602", "2.6b2x", operator.gt),
|
|
|
|
("0.6.7+20150214+git3a710f9", "0.6.7", operator.gt),
|
|
|
|
("15.8b", "15.8.0.1", operator.lt),
|
|
|
|
("1.2rc1", "1.2.0", operator.lt),
|
|
|
|
]:
|
|
|
|
ver_a = Version(a)
|
|
|
|
ver_b = Version(b)
|
|
|
|
if op is operator.eq:
|
|
|
|
for o, name in [(op, 'eq'), (operator.ge, 'ge'), (operator.le, 'le')]:
|
|
|
|
self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
|
|
|
|
if op is operator.lt:
|
|
|
|
for o, name in [(op, 'lt'), (operator.le, 'le'), (operator.ne, 'ne')]:
|
|
|
|
self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
|
|
|
|
for o, name in [(operator.gt, 'gt'), (operator.ge, 'ge'), (operator.eq, 'eq')]:
|
|
|
|
self.assertFalse(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
|
|
|
|
if op is operator.gt:
|
|
|
|
for o, name in [(op, 'gt'), (operator.ge, 'ge'), (operator.ne, 'ne')]:
|
|
|
|
self.assertTrue(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
|
|
|
|
for o, name in [(operator.lt, 'lt'), (operator.le, 'le'), (operator.eq, 'eq')]:
|
|
|
|
self.assertFalse(o(ver_a, ver_b), f'{ver_a} {name} {ver_b}')
|
|
|
|
|
|
|
|
def test_msvc_toolset_version(self):
|
|
|
|
'''
|
|
|
|
Ensure that the toolset version returns the correct value for this MSVC
|
|
|
|
'''
|
|
|
|
env = get_fake_env()
|
|
|
|
cc = detect_c_compiler(env, MachineChoice.HOST)
|
|
|
|
if cc.get_argument_syntax() != 'msvc':
|
|
|
|
raise unittest.SkipTest('Test only applies to MSVC-like compilers')
|
|
|
|
toolset_ver = cc.get_toolset_version()
|
|
|
|
self.assertIsNotNone(toolset_ver)
|
|
|
|
# Visual Studio 2015 and older versions do not define VCToolsVersion
|
|
|
|
# TODO: ICL doesn't set this in the VSC2015 profile either
|
|
|
|
if cc.id == 'msvc' and int(''.join(cc.version.split('.')[0:2])) < 1910:
|
|
|
|
return
|
|
|
|
if 'VCToolsVersion' in os.environ:
|
|
|
|
vctools_ver = os.environ['VCToolsVersion']
|
|
|
|
else:
|
|
|
|
self.assertIn('VCINSTALLDIR', os.environ)
|
|
|
|
# See https://devblogs.microsoft.com/cppblog/finding-the-visual-c-compiler-tools-in-visual-studio-2017/
|
|
|
|
vctools_ver = (Path(os.environ['VCINSTALLDIR']) / 'Auxiliary' / 'Build' / 'Microsoft.VCToolsVersion.default.txt').read_text(encoding='utf-8')
|
|
|
|
self.assertTrue(vctools_ver.startswith(toolset_ver),
|
|
|
|
msg=f'{vctools_ver!r} does not start with {toolset_ver!r}')
|
|
|
|
|
|
|
|
def test_split_args(self):
|
|
|
|
split_args = mesonbuild.mesonlib.split_args
|
|
|
|
join_args = mesonbuild.mesonlib.join_args
|
|
|
|
if is_windows():
|
|
|
|
test_data = [
|
|
|
|
# examples from https://docs.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments
|
|
|
|
(r'"a b c" d e', ['a b c', 'd', 'e'], True),
|
|
|
|
(r'"ab\"c" "\\" d', ['ab"c', '\\', 'd'], False),
|
|
|
|
(r'a\\\b d"e f"g h', [r'a\\\b', 'de fg', 'h'], False),
|
|
|
|
(r'a\\\"b c d', [r'a\"b', 'c', 'd'], False),
|
|
|
|
(r'a\\\\"b c" d e', [r'a\\b c', 'd', 'e'], False),
|
|
|
|
# other basics
|
|
|
|
(r'""', [''], True),
|
|
|
|
(r'a b c d "" e', ['a', 'b', 'c', 'd', '', 'e'], True),
|
|
|
|
(r"'a b c' d e", ["'a", 'b', "c'", 'd', 'e'], True),
|
|
|
|
(r"'a&b&c' d e", ["'a&b&c'", 'd', 'e'], True),
|
|
|
|
(r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], True),
|
|
|
|
(r"'a & b & c d e'", ["'a", '&', 'b', '&', 'c', 'd', "e'"], True),
|
|
|
|
('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False),
|
|
|
|
# more illustrative tests
|
|
|
|
(r'cl test.cpp /O1 /Fe:test.exe', ['cl', 'test.cpp', '/O1', '/Fe:test.exe'], True),
|
|
|
|
(r'cl "test.cpp /O1 /Fe:test.exe"', ['cl', 'test.cpp /O1 /Fe:test.exe'], True),
|
|
|
|
(r'cl /DNAME=\"Bob\" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], False),
|
|
|
|
(r'cl "/DNAME=\"Bob\"" test.cpp', ['cl', '/DNAME="Bob"', 'test.cpp'], True),
|
|
|
|
(r'cl /DNAME=\"Bob, Alice\" test.cpp', ['cl', '/DNAME="Bob,', 'Alice"', 'test.cpp'], False),
|
|
|
|
(r'cl "/DNAME=\"Bob, Alice\"" test.cpp', ['cl', '/DNAME="Bob, Alice"', 'test.cpp'], True),
|
|
|
|
(r'cl C:\path\with\backslashes.cpp', ['cl', r'C:\path\with\backslashes.cpp'], True),
|
|
|
|
(r'cl C:\\path\\with\\double\\backslashes.cpp', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], True),
|
|
|
|
(r'cl "C:\\path\\with\\double\\backslashes.cpp"', ['cl', r'C:\\path\\with\\double\\backslashes.cpp'], False),
|
|
|
|
(r'cl C:\path with spaces\test.cpp', ['cl', r'C:\path', 'with', r'spaces\test.cpp'], False),
|
|
|
|
(r'cl "C:\path with spaces\test.cpp"', ['cl', r'C:\path with spaces\test.cpp'], True),
|
|
|
|
(r'cl /DPATH="C:\path\with\backslashes test.cpp', ['cl', r'/DPATH=C:\path\with\backslashes test.cpp'], False),
|
|
|
|
(r'cl /DPATH=\"C:\\ends\\with\\backslashes\\\" test.cpp', ['cl', r'/DPATH="C:\\ends\\with\\backslashes\"', 'test.cpp'], False),
|
|
|
|
(r'cl /DPATH="C:\\ends\\with\\backslashes\\" test.cpp', ['cl', '/DPATH=C:\\\\ends\\\\with\\\\backslashes\\', 'test.cpp'], False),
|
|
|
|
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\"', 'test.cpp'], True),
|
|
|
|
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\ test.cpp'], False),
|
|
|
|
(r'cl "/DNAME=\"C:\\ends\\with\\backslashes\\\\\"" test.cpp', ['cl', r'/DNAME="C:\\ends\\with\\backslashes\\"', 'test.cpp'], True),
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
test_data = [
|
|
|
|
(r"'a b c' d e", ['a b c', 'd', 'e'], True),
|
|
|
|
(r"a/b/c d e", ['a/b/c', 'd', 'e'], True),
|
|
|
|
(r"a\b\c d e", [r'abc', 'd', 'e'], False),
|
|
|
|
(r"a\\b\\c d e", [r'a\b\c', 'd', 'e'], False),
|
|
|
|
(r'"a b c" d e', ['a b c', 'd', 'e'], False),
|
|
|
|
(r'"a\\b\\c\\" d e', ['a\\b\\c\\', 'd', 'e'], False),
|
|
|
|
(r"'a\b\c\' d e", ['a\\b\\c\\', 'd', 'e'], True),
|
|
|
|
(r"'a&b&c' d e", ['a&b&c', 'd', 'e'], True),
|
|
|
|
(r"a & b & c d e", ['a', '&', 'b', '&', 'c', 'd', 'e'], False),
|
|
|
|
(r"'a & b & c d e'", ['a & b & c d e'], True),
|
|
|
|
(r"abd'e f'g h", [r'abde fg', 'h'], False),
|
|
|
|
('a b\nc\rd \n\re', ['a', 'b', 'c', 'd', 'e'], False),
|
|
|
|
|
|
|
|
('g++ -DNAME="Bob" test.cpp', ['g++', '-DNAME=Bob', 'test.cpp'], False),
|
|
|
|
("g++ '-DNAME=\"Bob\"' test.cpp", ['g++', '-DNAME="Bob"', 'test.cpp'], True),
|
|
|
|
('g++ -DNAME="Bob, Alice" test.cpp', ['g++', '-DNAME=Bob, Alice', 'test.cpp'], False),
|
|
|
|
("g++ '-DNAME=\"Bob, Alice\"' test.cpp", ['g++', '-DNAME="Bob, Alice"', 'test.cpp'], True),
|
|
|
|
]
|
|
|
|
|
|
|
|
for (cmd, expected, roundtrip) in test_data:
|
|
|
|
self.assertEqual(split_args(cmd), expected)
|
|
|
|
if roundtrip:
|
|
|
|
self.assertEqual(join_args(expected), cmd)
|
|
|
|
|
|
|
|
def test_quote_arg(self):
|
|
|
|
split_args = mesonbuild.mesonlib.split_args
|
|
|
|
quote_arg = mesonbuild.mesonlib.quote_arg
|
|
|
|
if is_windows():
|
|
|
|
test_data = [
|
|
|
|
('', '""'),
|
|
|
|
('arg1', 'arg1'),
|
|
|
|
('/option1', '/option1'),
|
|
|
|
('/Ovalue', '/Ovalue'),
|
|
|
|
('/OBob&Alice', '/OBob&Alice'),
|
|
|
|
('/Ovalue with spaces', r'"/Ovalue with spaces"'),
|
|
|
|
(r'/O"value with spaces"', r'"/O\"value with spaces\""'),
|
|
|
|
(r'/OC:\path with spaces\test.exe', r'"/OC:\path with spaces\test.exe"'),
|
|
|
|
('/LIBPATH:C:\\path with spaces\\ends\\with\\backslashes\\', r'"/LIBPATH:C:\path with spaces\ends\with\backslashes\\"'),
|
|
|
|
('/LIBPATH:"C:\\path with spaces\\ends\\with\\backslashes\\\\"', r'"/LIBPATH:\"C:\path with spaces\ends\with\backslashes\\\\\""'),
|
|
|
|
(r'/DMSG="Alice said: \"Let\'s go\""', r'"/DMSG=\"Alice said: \\\"Let\'s go\\\"\""'),
|
|
|
|
]
|
|
|
|
else:
|
|
|
|
test_data = [
|
|
|
|
('arg1', 'arg1'),
|
|
|
|
('--option1', '--option1'),
|
|
|
|
('-O=value', '-O=value'),
|
|
|
|
('-O=Bob&Alice', "'-O=Bob&Alice'"),
|
|
|
|
('-O=value with spaces', "'-O=value with spaces'"),
|
|
|
|
('-O="value with spaces"', '\'-O=\"value with spaces\"\''),
|
|
|
|
('-O=/path with spaces/test', '\'-O=/path with spaces/test\''),
|
|
|
|
('-DMSG="Alice said: \\"Let\'s go\\""', "'-DMSG=\"Alice said: \\\"Let'\"'\"'s go\\\"\"'"),
|
|
|
|
]
|
|
|
|
|
|
|
|
for (arg, expected) in test_data:
|
|
|
|
self.assertEqual(quote_arg(arg), expected)
|
|
|
|
self.assertEqual(split_args(expected)[0], arg)
|
|
|
|
|
|
|
|
def test_depfile(self):
|
|
|
|
for (f, target, expdeps) in [
|
|
|
|
# empty, unknown target
|
|
|
|
([''], 'unknown', set()),
|
|
|
|
# simple target & deps
|
|
|
|
(['meson/foo.o : foo.c foo.h'], 'meson/foo.o', set({'foo.c', 'foo.h'})),
|
|
|
|
(['meson/foo.o: foo.c foo.h'], 'foo.c', set()),
|
|
|
|
# get all deps
|
|
|
|
(['meson/foo.o: foo.c foo.h',
|
|
|
|
'foo.c: gen.py'], 'meson/foo.o', set({'foo.c', 'foo.h', 'gen.py'})),
|
|
|
|
(['meson/foo.o: foo.c foo.h',
|
|
|
|
'foo.c: gen.py'], 'foo.c', set({'gen.py'})),
|
|
|
|
# linue continuation, multiple targets
|
|
|
|
(['foo.o \\', 'foo.h: bar'], 'foo.h', set({'bar'})),
|
|
|
|
(['foo.o \\', 'foo.h: bar'], 'foo.o', set({'bar'})),
|
|
|
|
# \\ handling
|
|
|
|
(['foo: Program\\ F\\iles\\\\X'], 'foo', set({'Program Files\\X'})),
|
|
|
|
# $ handling
|
|
|
|
(['f$o.o: c/b'], 'f$o.o', set({'c/b'})),
|
|
|
|
(['f$$o.o: c/b'], 'f$o.o', set({'c/b'})),
|
|
|
|
# cycles
|
|
|
|
(['a: b', 'b: a'], 'a', set({'a', 'b'})),
|
|
|
|
(['a: b', 'b: a'], 'b', set({'a', 'b'})),
|
|
|
|
]:
|
|
|
|
d = mesonbuild.depfile.DepFile(f)
|
|
|
|
deps = d.get_all_dependencies(target)
|
|
|
|
self.assertEqual(sorted(deps), sorted(expdeps))
|
|
|
|
|
|
|
|
def test_log_once(self):
|
|
|
|
f = io.StringIO()
|
|
|
|
with mock.patch('mesonbuild.mlog.log_file', f), \
|
|
|
|
mock.patch('mesonbuild.mlog._logged_once', set()):
|
|
|
|
mesonbuild.mlog.log_once('foo')
|
|
|
|
mesonbuild.mlog.log_once('foo')
|
|
|
|
actual = f.getvalue().strip()
|
|
|
|
self.assertEqual(actual, 'foo', actual)
|
|
|
|
|
|
|
|
def test_log_once_ansi(self):
|
|
|
|
f = io.StringIO()
|
|
|
|
with mock.patch('mesonbuild.mlog.log_file', f), \
|
|
|
|
mock.patch('mesonbuild.mlog._logged_once', set()):
|
|
|
|
mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo'))
|
|
|
|
mesonbuild.mlog.log_once(mesonbuild.mlog.bold('foo'))
|
|
|
|
actual = f.getvalue().strip()
|
|
|
|
self.assertEqual(actual.count('foo'), 1, actual)
|
|
|
|
|
|
|
|
mesonbuild.mlog.log_once('foo')
|
|
|
|
actual = f.getvalue().strip()
|
|
|
|
self.assertEqual(actual.count('foo'), 1, actual)
|
|
|
|
|
|
|
|
f.truncate()
|
|
|
|
|
|
|
|
mesonbuild.mlog.warning('bar', once=True)
|
|
|
|
mesonbuild.mlog.warning('bar', once=True)
|
|
|
|
actual = f.getvalue().strip()
|
|
|
|
self.assertEqual(actual.count('bar'), 1, actual)
|
|
|
|
|
|
|
|
def test_sort_libpaths(self):
|
|
|
|
sort_libpaths = mesonbuild.dependencies.base.sort_libpaths
|
|
|
|
self.assertEqual(sort_libpaths(
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'],
|
|
|
|
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
|
|
|
|
self.assertEqual(sort_libpaths(
|
|
|
|
['/usr/local/lib', '/home/mesonuser/.local/lib', '/usr/lib'],
|
|
|
|
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
|
|
|
|
self.assertEqual(sort_libpaths(
|
|
|
|
['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'],
|
|
|
|
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/lib/pkgconfig']),
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
|
|
|
|
self.assertEqual(sort_libpaths(
|
|
|
|
['/usr/lib', '/usr/local/lib', '/home/mesonuser/.local/lib'],
|
|
|
|
['/home/mesonuser/.local/lib/pkgconfig', '/usr/local/libdata/pkgconfig']),
|
|
|
|
['/home/mesonuser/.local/lib', '/usr/local/lib', '/usr/lib'])
|
|
|
|
|
|
|
|
def test_dependency_factory_order(self):
|
|
|
|
b = mesonbuild.dependencies.base
|
|
|
|
F = mesonbuild.dependencies.factory
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
with chdir(tmpdir):
|
|
|
|
env = get_fake_env()
|
|
|
|
env.scratch_dir = tmpdir
|
|
|
|
|
|
|
|
f = F.DependencyFactory(
|
|
|
|
'test_dep',
|
|
|
|
methods=[b.DependencyMethods.PKGCONFIG, b.DependencyMethods.CMAKE]
|
|
|
|
)
|
|
|
|
actual = [m() for m in f(env, MachineChoice.HOST, {'required': False})]
|
|
|
|
self.assertListEqual([m.type_name for m in actual], ['pkgconfig', 'cmake'])
|
|
|
|
|
|
|
|
f = F.DependencyFactory(
|
|
|
|
'test_dep',
|
|
|
|
methods=[b.DependencyMethods.CMAKE, b.DependencyMethods.PKGCONFIG]
|
|
|
|
)
|
|
|
|
actual = [m() for m in f(env, MachineChoice.HOST, {'required': False})]
|
|
|
|
self.assertListEqual([m.type_name for m in actual], ['cmake', 'pkgconfig'])
|
|
|
|
|
|
|
|
def test_validate_json(self) -> None:
|
|
|
|
"""Validate the json schema for the test cases."""
|
|
|
|
try:
|
|
|
|
from jsonschema import validate, ValidationError
|
|
|
|
except ImportError:
|
|
|
|
if is_ci():
|
|
|
|
raise
|
|
|
|
raise unittest.SkipTest('Python jsonschema module not found.')
|
|
|
|
|
|
|
|
schema = json.loads(Path('data/test.schema.json').read_text(encoding='utf-8'))
|
|
|
|
|
|
|
|
errors = [] # type: T.Tuple[str, Exception]
|
|
|
|
for p in Path('test cases').glob('**/test.json'):
|
|
|
|
try:
|
|
|
|
validate(json.loads(p.read_text(encoding='utf-8')), schema=schema)
|
|
|
|
except ValidationError as e:
|
|
|
|
errors.append((p.resolve(), e))
|
|
|
|
|
|
|
|
for f, e in errors:
|
|
|
|
print(f'Failed to validate: "{f}"')
|
|
|
|
print(str(e))
|
|
|
|
|
|
|
|
self.assertFalse(errors)
|
|
|
|
|
|
|
|
def test_typed_pos_args_types(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, int, bool)
|
|
|
|
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
|
|
|
|
self.assertIsInstance(args, tuple)
|
|
|
|
self.assertIsInstance(args[0], str)
|
|
|
|
self.assertIsInstance(args[1], int)
|
|
|
|
self.assertIsInstance(args[2], bool)
|
|
|
|
|
|
|
|
_(None, mock.Mock(), ['string', 1, False], None)
|
|
|
|
|
|
|
|
def test_typed_pos_args_types_invalid(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, int, bool)
|
|
|
|
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string', 1.0, False], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo argument 2 was of type "float" but should have been "int"')
|
|
|
|
|
|
|
|
def test_typed_pos_args_types_wrong_number(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, int, bool)
|
|
|
|
def _(obj, node, args: T.Tuple[str, int, bool], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string', 1], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo takes exactly 3 arguments, but got 2.')
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string', 1, True, True], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo takes exactly 3 arguments, but got 4.')
|
|
|
|
|
|
|
|
def test_typed_pos_args_varargs(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=str)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
|
|
|
self.assertIsInstance(args, tuple)
|
|
|
|
self.assertIsInstance(args[0], str)
|
|
|
|
self.assertIsInstance(args[1], list)
|
|
|
|
self.assertIsInstance(args[1][0], str)
|
|
|
|
self.assertIsInstance(args[1][1], str)
|
|
|
|
|
|
|
|
_(None, mock.Mock(), ['string', 'var', 'args'], None)
|
|
|
|
|
|
|
|
def test_typed_pos_args_varargs_not_given(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=str)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
|
|
|
self.assertIsInstance(args, tuple)
|
|
|
|
self.assertIsInstance(args[0], str)
|
|
|
|
self.assertIsInstance(args[1], list)
|
|
|
|
self.assertEqual(args[1], [])
|
|
|
|
|
|
|
|
_(None, mock.Mock(), ['string'], None)
|
|
|
|
|
|
|
|
def test_typed_pos_args_varargs_invalid(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=str)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string', 'var', 'args', 0], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been "str"')
|
|
|
|
|
|
|
|
def test_typed_pos_args_varargs_invalid_mulitple_types(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=(str, list))
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string', 'var', 'args', 0], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo argument 4 was of type "int" but should have been one of: "str", "list"')
|
|
|
|
|
|
|
|
def test_typed_pos_args_max_varargs(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=str, max_varargs=5)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
|
|
|
self.assertIsInstance(args, tuple)
|
|
|
|
self.assertIsInstance(args[0], str)
|
|
|
|
self.assertIsInstance(args[1], list)
|
|
|
|
self.assertIsInstance(args[1][0], str)
|
|
|
|
self.assertIsInstance(args[1][1], str)
|
|
|
|
|
|
|
|
_(None, mock.Mock(), ['string', 'var', 'args'], None)
|
|
|
|
|
|
|
|
def test_typed_pos_args_max_varargs_exceeded(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=str, max_varargs=1)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string', 'var', 'args'], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo takes between 1 and 2 arguments, but got 3.')
|
|
|
|
|
|
|
|
def test_typed_pos_args_min_varargs(self) -> None:
|
|
|
|
@typed_pos_args('foo', varargs=str, max_varargs=2, min_varargs=1)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
|
|
|
self.assertIsInstance(args, tuple)
|
|
|
|
self.assertIsInstance(args[0], list)
|
|
|
|
self.assertIsInstance(args[0][0], str)
|
|
|
|
self.assertIsInstance(args[0][1], str)
|
|
|
|
|
|
|
|
_(None, mock.Mock(), ['string', 'var'], None)
|
|
|
|
|
|
|
|
def test_typed_pos_args_min_varargs_not_met(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=str, min_varargs=1)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string'], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo takes at least 2 arguments, but got 1.')
|
|
|
|
|
|
|
|
def test_typed_pos_args_min_and_max_varargs_exceeded(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string', 'var', 'args', 'bar'], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo takes between 2 and 3 arguments, but got 4.')
|
|
|
|
|
|
|
|
def test_typed_pos_args_min_and_max_varargs_not_met(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, varargs=str, min_varargs=1, max_varargs=2)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.Tuple[str, ...]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string'], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo takes between 2 and 3 arguments, but got 1.')
|
|
|
|
|
|
|
|
def test_typed_pos_args_variadic_and_optional(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, optargs=[str], varargs=str, min_varargs=0)
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.List[str]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(AssertionError) as cm:
|
|
|
|
_(None, mock.Mock(), ['string'], None)
|
|
|
|
self.assertEqual(
|
|
|
|
str(cm.exception),
|
|
|
|
'varargs and optargs not supported together as this would be ambiguous')
|
|
|
|
|
|
|
|
def test_typed_pos_args_min_optargs_not_met(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, str, optargs=[str])
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string'], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo takes at least 2 arguments, but got 1.')
|
|
|
|
|
|
|
|
def test_typed_pos_args_min_optargs_max_exceeded(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, optargs=[str])
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
|
|
|
|
self.assertTrue(False) # should not be reachable
|
|
|
|
|
|
|
|
with self.assertRaises(InvalidArguments) as cm:
|
|
|
|
_(None, mock.Mock(), ['string', '1', '2'], None)
|
|
|
|
self.assertEqual(str(cm.exception), 'foo takes at most 2 arguments, but got 3.')
|
|
|
|
|
|
|
|
def test_typed_pos_args_optargs_not_given(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, optargs=[str])
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
|
|
|
|
self.assertEqual(len(args), 2)
|
|
|
|
self.assertIsInstance(args[0], str)
|
|
|
|
self.assertEqual(args[0], 'string')
|
|
|
|
self.assertIsNone(args[1])
|
|
|
|
|
|
|
|
_(None, mock.Mock(), ['string'], None)
|
|
|
|
|
|
|
|
def test_typed_pos_args_optargs_some_given(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, optargs=[str, int])
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.Optional[str], T.Optional[int]], kwargs) -> None:
|
|
|
|
self.assertEqual(len(args), 3)
|
|
|
|
self.assertIsInstance(args[0], str)
|
|
|
|
self.assertEqual(args[0], 'string')
|
|
|
|
self.assertIsInstance(args[1], str)
|
|
|
|
self.assertEqual(args[1], '1')
|
|
|
|
self.assertIsNone(args[2])
|
|
|
|
|
|
|
|
_(None, mock.Mock(), ['string', '1'], None)
|
|
|
|
|
|
|
|
def test_typed_pos_args_optargs_all_given(self) -> None:
|
|
|
|
@typed_pos_args('foo', str, optargs=[str])
|
|
|
|
def _(obj, node, args: T.Tuple[str, T.Optional[str]], kwargs) -> None:
|
|
|
|
self.assertEqual(len(args), 2)
|
|
|
|
self.assertIsInstance(args[0], str)
|
|
|
|
self.assertEqual(args[0], 'string')
|
|
|
|
self.assertIsInstance(args[1], str)
|
|
|
|
|
|
|
|
_(None, mock.Mock(), ['string', '1'], None)
|
|
|
|
|
|
|
|
def test_typed_kwarg_basic(self) -> None:
|
|
|
|
@typed_kwargs(
|
|
|
|
'testfunc',
|
|
|
|
KwargInfo('input', str, 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")
|
|
|
|
|
|
|
|
@mock.patch.dict(mesonbuild.mesonlib.project_meson_versions, {})
|
|
|
|
def test_typed_kwarg_since(self) -> None:
|
|
|
|
@typed_kwargs(
|
|
|
|
'testfunc',
|
|
|
|
KwargInfo('input', str, since='1.0', deprecated='2.0')
|
|
|
|
)
|
|
|
|
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
|
|
|
|
self.assertIsInstance(kwargs['input'], str)
|
|
|
|
self.assertEqual(kwargs['input'], 'foo')
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
# With Meson 0.1 it should trigger the "introduced" warning but not the "deprecated" warning
|
|
|
|
mesonbuild.mesonlib.project_meson_versions[''] = '0.1'
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'input': 'foo'})
|
|
|
|
self.assertRegex(out.getvalue(), r'WARNING:.*introduced.*input arg in testfunc')
|
|
|
|
self.assertNotRegex(out.getvalue(), r'WARNING:.*deprecated.*input arg in testfunc')
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
# With Meson 1.5 it shouldn't trigger any warning
|
|
|
|
mesonbuild.mesonlib.project_meson_versions[''] = '1.5'
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'input': 'foo'})
|
|
|
|
self.assertNotRegex(out.getvalue(), r'WARNING:.*')
|
|
|
|
self.assertNotRegex(out.getvalue(), r'WARNING:.*')
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
# With Meson 2.0 it should trigger the "deprecated" warning but not the "introduced" warning
|
|
|
|
mesonbuild.mesonlib.project_meson_versions[''] = '2.0'
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'input': 'foo'})
|
|
|
|
self.assertRegex(out.getvalue(), r'WARNING:.*deprecated.*input arg in testfunc')
|
|
|
|
self.assertNotRegex(out.getvalue(), r'WARNING:.*introduced.*input arg in testfunc')
|
|
|
|
|
|
|
|
def test_typed_kwarg_validator(self) -> None:
|
|
|
|
@typed_kwargs(
|
|
|
|
'testfunc',
|
|
|
|
KwargInfo('input', str, 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.dict(mesonbuild.mesonlib.project_meson_versions, {})
|
|
|
|
def test_typed_kwarg_since_values(self) -> None:
|
|
|
|
@typed_kwargs(
|
|
|
|
'testfunc',
|
|
|
|
KwargInfo('input', ContainerTypeInfo(list, str), listify=True, default=[], deprecated_values={'foo': '0.9'}, since_values={'bar': '1.1'}),
|
|
|
|
KwargInfo('output', ContainerTypeInfo(dict, str), default={}, deprecated_values={'foo': '0.9'}, since_values={'bar': '1.1'}),
|
|
|
|
KwargInfo(
|
|
|
|
'mode',
|
|
|
|
(str, type(None)),
|
|
|
|
validator=in_set_validator({'clean', 'build', 'rebuild', 'deprecated', 'since'}),
|
|
|
|
deprecated_values={'deprecated': '1.0'},
|
|
|
|
since_values={'since': '1.1'}),
|
|
|
|
)
|
|
|
|
def _(obj, node, args: T.Tuple, kwargs: T.Dict[str, str]) -> None:
|
|
|
|
pass
|
|
|
|
|
|
|
|
mesonbuild.mesonlib.project_meson_versions[''] = '1.0'
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'input': ['foo']})
|
|
|
|
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '0.9': "testfunc" keyword argument "input" value "foo".*""")
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'input': ['bar']})
|
|
|
|
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "input" value "bar".*""")
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'output': {'foo': 'a'}})
|
|
|
|
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '0.9': "testfunc" keyword argument "output" value "foo".*""")
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'output': {'bar': 'b'}})
|
|
|
|
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "output" value "bar".*""")
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'mode': 'deprecated'})
|
|
|
|
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*deprecated since '1.0': "testfunc" keyword argument "mode" value "deprecated".*""")
|
|
|
|
|
|
|
|
with mock.patch('sys.stdout', io.StringIO()) as out:
|
|
|
|
_(None, mock.Mock(subproject=''), [], {'mode': 'since'})
|
|
|
|
self.assertRegex(out.getvalue(), r"""WARNING:.Project targeting '1.0'.*introduced in '1.1': "testfunc" keyword argument "mode" value "since".*""")
|
|
|
|
|
|
|
|
def test_typed_kwarg_evolve(self) -> None:
|
|
|
|
k = KwargInfo('foo', str, required=True, default='foo')
|
|
|
|
v = k.evolve(default='bar')
|
|
|
|
self.assertEqual(k.name, 'foo')
|
|
|
|
self.assertEqual(k.name, v.name)
|
|
|
|
self.assertEqual(k.types, str)
|
|
|
|
self.assertEqual(k.types, v.types)
|
|
|
|
self.assertEqual(k.required, True)
|
|
|
|
self.assertEqual(k.required, v.required)
|
|
|
|
self.assertEqual(k.default, 'foo')
|
|
|
|
self.assertEqual(v.default, 'bar')
|
|
|
|
|
|
|
|
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', 'mips64'),
|
|
|
|
('mips64', 'mips64'),
|
|
|
|
('mips', 'mips'),
|
|
|
|
('mipsel', 'mips'),
|
|
|
|
('ip30', 'mips64'),
|
|
|
|
('ip35', 'mips64'),
|
|
|
|
('parisc64', 'parisc'),
|
|
|
|
('sun4u', 'sparc64'),
|
|
|
|
('sun4v', 'sparc64'),
|
|
|
|
('amd64', 'x86_64'),
|
|
|
|
('x64', 'x86_64'),
|
|
|
|
('i86pc', 'x86_64'), # Solaris
|
|
|
|
('aarch64', 'aarch64'),
|
|
|
|
('aarch64_be', 'aarch64'),
|
|
|
|
]
|
|
|
|
|
|
|
|
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({})
|
|
|
|
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')]:
|
|
|
|
with self.subTest(test, has_define=True), 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', 'mips64'),
|
|
|
|
('mips64', 'mips64'),
|
|
|
|
('mips', 'mips'),
|
|
|
|
('mipsel', 'mips'),
|
|
|
|
('aarch64', 'aarch64'),
|
|
|
|
('aarch64_be', 'aarch64'),
|
|
|
|
]
|
|
|
|
|
|
|
|
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({})
|
|
|
|
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')]:
|
|
|
|
with self.subTest(test, has_define=True), mock_trial(test):
|
|
|
|
actual = mesonbuild.environment.detect_cpu({})
|
|
|
|
self.assertEqual(actual, expected)
|
|
|
|
|
|
|
|
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, mock=True)
|
|
|
|
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)
|