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