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