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