summaryrefslogtreecommitdiffstats
path: root/unittests/internaltests.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-29 04:41:38 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-29 04:41:38 +0000
commit7b6e527f440cd7e6f8be2b07cee320ee6ca18786 (patch)
tree4a2738d69fa2814659fdadddf5826282e73d81f4 /unittests/internaltests.py
parentInitial commit. (diff)
downloadmeson-7b6e527f440cd7e6f8be2b07cee320ee6ca18786.tar.xz
meson-7b6e527f440cd7e6f8be2b07cee320ee6ca18786.zip
Adding upstream version 1.0.1.upstream/1.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'unittests/internaltests.py')
-rw-r--r--unittests/internaltests.py1648
1 files changed, 1648 insertions, 0 deletions
diff --git a/unittests/internaltests.py b/unittests/internaltests.py
new file mode 100644
index 0000000..79a3217
--- /dev/null
+++ b/unittests/internaltests.py
@@ -0,0 +1,1648 @@
+# 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 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.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 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 = 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 = mesonbuild.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 = mesonbuild.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', '-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 = 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")
+
+ 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'}),
+ )
+ 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". 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". use this.*""")
+
+ 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', '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)