From 1c11a04f94ae18fbe494cce84038763f11b65583 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 2 Oct 2024 12:43:17 +0100 Subject: [PATCH] unittests: Test env2mfile's dpkg_architecture_to_machine_info This test parses several possible outputs of dpkg-architecture and asserts that they produce the expected MachineInfo. To avoid depending on suitable cross-tools being installed, use unittest.mock to override locate_path with a version that pretends that all of the tools we're interested in are in /usr/bin. Similarly, use mock environment variables to exercise what happens when we have those set. The test data used here exercises most variations: * big- and little-endianness * GNU CPU (x86_64) differing from dpkg CPU (amd64) * Linux, kFreeBSD and Hurd * special-cased architectures: x86, arm, mips64el, ppc64el expected_compilers() intentionally doesn't assume that every compiler is gcc (even though they all are, right now), because #13721 proposes adding valac which does not take a gcc suffix. Signed-off-by: Simon McVittie --- unittests/internaltests.py | 391 +++++++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) diff --git a/unittests/internaltests.py b/unittests/internaltests.py index 310b4f680..13d3446bf 100644 --- a/unittests/internaltests.py +++ b/unittests/internaltests.py @@ -4,6 +4,7 @@ from configparser import ConfigParser from pathlib import Path from unittest import mock +import argparse import contextlib import io import json @@ -13,6 +14,7 @@ import pickle import stat import subprocess import tempfile +import textwrap import typing as T import unittest @@ -23,6 +25,7 @@ import mesonbuild.dependencies.factory import mesonbuild.envconfig import mesonbuild.environment import mesonbuild.modules.gnome +import mesonbuild.scripts.env2mfile from mesonbuild import coredata from mesonbuild.compilers.c import ClangCCompiler, GnuCCompiler from mesonbuild.compilers.cpp import VisualStudioCPPCompiler @@ -1715,3 +1718,391 @@ class InternalTests(unittest.TestCase): for raw, expected in cases: with self.subTest(raw): self.assertEqual(OptionKey.from_string(raw), expected) + + def test_env2mfile_deb(self) -> None: + MachineInfo = mesonbuild.scripts.env2mfile.MachineInfo + to_machine_info = mesonbuild.scripts.env2mfile.dpkg_architecture_to_machine_info + + # For testing purposes, behave as though all cross-programs + # exist in /usr/bin + def locate_path(program: str) -> T.List[str]: + if os.path.isabs(program): + return [program] + return ['/usr/bin/' + program] + + def expected_compilers( + gnu_tuple: str, + gcc_suffix: str = '', + ) -> T.Dict[str, T.List[str]]: + return { + 'c': [f'/usr/bin/{gnu_tuple}-gcc{gcc_suffix}'], + 'cpp': [f'/usr/bin/{gnu_tuple}-g++{gcc_suffix}'], + 'objc': [f'/usr/bin/{gnu_tuple}-gobjc{gcc_suffix}'], + 'objcpp': [f'/usr/bin/{gnu_tuple}-gobjc++{gcc_suffix}'], + } + + def expected_binaries(gnu_tuple: str) -> T.Dict[str, T.List[str]]: + return { + 'ar': [f'/usr/bin/{gnu_tuple}-ar'], + 'strip': [f'/usr/bin/{gnu_tuple}-strip'], + 'objcopy': [f'/usr/bin/{gnu_tuple}-objcopy'], + 'ld': [f'/usr/bin/{gnu_tuple}-ld'], + 'cmake': ['/usr/bin/cmake'], + 'pkg-config': [f'/usr/bin/{gnu_tuple}-pkg-config'], + 'cups-config': ['/usr/bin/cups-config'], + } + + for title, dpkg_arch, gccsuffix, env, expected in [ + ( + # s390x is an example of the common case where the + # Meson CPU name, the GNU CPU name, the dpkg architecture + # name and uname -m all agree. + # (alpha, m68k, ppc64, riscv64, sh4, sparc64 are similar) + 's390x-linux-gnu', + # Output of `dpkg-architecture -a...`, filtered to + # only the DEB_HOST_ parts because that's all we use + textwrap.dedent( + ''' + DEB_HOST_ARCH=s390x + DEB_HOST_ARCH_ABI=base + DEB_HOST_ARCH_BITS=64 + DEB_HOST_ARCH_CPU=s390x + DEB_HOST_ARCH_ENDIAN=big + DEB_HOST_ARCH_LIBC=gnu + DEB_HOST_ARCH_OS=linux + DEB_HOST_GNU_CPU=s390x + DEB_HOST_GNU_SYSTEM=linux-gnu + DEB_HOST_GNU_TYPE=s390x-linux-gnu + DEB_HOST_MULTIARCH=s390x-linux-gnu + ''' + ), + '', + {'PATH': '/usr/bin'}, + MachineInfo( + compilers=expected_compilers('s390x-linux-gnu'), + binaries=expected_binaries('s390x-linux-gnu'), + properties={}, + compile_args={}, + link_args={}, + cmake={ + 'CMAKE_C_COMPILER': ['/usr/bin/s390x-linux-gnu-gcc'], + 'CMAKE_CXX_COMPILER': ['/usr/bin/s390x-linux-gnu-g++'], + 'CMAKE_SYSTEM_NAME': 'Linux', + 'CMAKE_SYSTEM_PROCESSOR': 's390x', + }, + system='linux', + subsystem='linux', + kernel='linux', + cpu='s390x', + cpu_family='s390x', + endian='big', + ), + ), + # Debian amd64 vs. GNU, Meson, etc. x86_64. + # arm64/aarch64, hppa/parisc, i386/i686/x86, loong64/loongarch64, + # powerpc/ppc are similar. + ( + 'x86_64-linux-gnu', + textwrap.dedent( + ''' + DEB_HOST_ARCH=amd64 + DEB_HOST_ARCH_ABI=base + DEB_HOST_ARCH_BITS=64 + DEB_HOST_ARCH_CPU=amd64 + DEB_HOST_ARCH_ENDIAN=little + DEB_HOST_ARCH_LIBC=gnu + DEB_HOST_ARCH_OS=linux + DEB_HOST_GNU_CPU=x86_64 + DEB_HOST_GNU_SYSTEM=linux-gnu + DEB_HOST_GNU_TYPE=x86_64-linux-gnu + DEB_HOST_MULTIARCH=x86_64-linux-gnu + ''' + ), + '', + {'PATH': '/usr/bin'}, + MachineInfo( + compilers=expected_compilers('x86_64-linux-gnu'), + binaries=expected_binaries('x86_64-linux-gnu'), + properties={}, + compile_args={}, + link_args={}, + cmake={ + 'CMAKE_C_COMPILER': ['/usr/bin/x86_64-linux-gnu-gcc'], + 'CMAKE_CXX_COMPILER': ['/usr/bin/x86_64-linux-gnu-g++'], + 'CMAKE_SYSTEM_NAME': 'Linux', + 'CMAKE_SYSTEM_PROCESSOR': 'x86_64', + }, + system='linux', + subsystem='linux', + kernel='linux', + # TODO: In native builds we get x86_64, but in + # cross-builds it's amd64 + cpu='TODO', + cpu_family='x86_64', + endian='little', + ), + ), + ( + 'arm-linux-gnueabihf with non-default gcc and environment', + textwrap.dedent( + ''' + DEB_HOST_ARCH=armhf + DEB_HOST_ARCH_ABI=eabihf + DEB_HOST_ARCH_BITS=32 + DEB_HOST_ARCH_CPU=arm + DEB_HOST_ARCH_ENDIAN=little + DEB_HOST_ARCH_LIBC=gnu + DEB_HOST_ARCH_OS=linux + DEB_HOST_GNU_CPU=arm + DEB_HOST_GNU_SYSTEM=linux-gnueabihf + DEB_HOST_GNU_TYPE=arm-linux-gnueabihf + DEB_HOST_MULTIARCH=arm-linux-gnueabihf + ''' + ), + '-12', + { + 'PATH': '/usr/bin', + 'CPPFLAGS': '-DNDEBUG', + 'CFLAGS': '-std=c99', + 'CXXFLAGS': '-std=c++11', + 'OBJCFLAGS': '-fobjc-exceptions', + 'OBJCXXFLAGS': '-fobjc-nilcheck', + 'LDFLAGS': '-Wl,-O1', + }, + MachineInfo( + compilers=expected_compilers('arm-linux-gnueabihf', '-12'), + binaries=expected_binaries('arm-linux-gnueabihf'), + properties={}, + compile_args={ + 'c': ['-DNDEBUG', '-std=c99'], + 'cpp': ['-DNDEBUG', '-std=c++11'], + 'objc': ['-DNDEBUG', '-fobjc-exceptions'], + 'objcpp': ['-DNDEBUG', '-fobjc-nilcheck'], + }, + link_args={ + 'c': ['-std=c99', '-Wl,-O1'], + 'cpp': ['-std=c++11', '-Wl,-O1'], + 'objc': ['-fobjc-exceptions', '-Wl,-O1'], + 'objcpp': ['-fobjc-nilcheck', '-Wl,-O1'], + }, + cmake={ + 'CMAKE_C_COMPILER': ['/usr/bin/arm-linux-gnueabihf-gcc-12'], + 'CMAKE_CXX_COMPILER': ['/usr/bin/arm-linux-gnueabihf-g++-12'], + 'CMAKE_SYSTEM_NAME': 'Linux', + 'CMAKE_SYSTEM_PROCESSOR': 'armv7l', + }, + system='linux', + subsystem='linux', + kernel='linux', + # In a native build this would often be armv8l + # (the version of the running CPU) but the architecture + # baseline in Debian is officially ARMv7 + cpu='arm7hlf', + cpu_family='arm', + endian='little', + ), + ), + ( + 'special cases for i386 (i686, x86) and Hurd', + textwrap.dedent( + ''' + DEB_HOST_ARCH=hurd-i386 + DEB_HOST_ARCH_ABI=base + DEB_HOST_ARCH_BITS=32 + DEB_HOST_ARCH_CPU=i386 + DEB_HOST_ARCH_ENDIAN=little + DEB_HOST_ARCH_LIBC=gnu + DEB_HOST_ARCH_OS=hurd + DEB_HOST_GNU_CPU=i686 + DEB_HOST_GNU_SYSTEM=gnu + DEB_HOST_GNU_TYPE=i686-gnu + DEB_HOST_MULTIARCH=i386-gnu + ''' + ), + '', + {'PATH': '/usr/bin'}, + MachineInfo( + compilers=expected_compilers('i686-gnu'), + binaries=expected_binaries('i686-gnu'), + properties={}, + compile_args={}, + link_args={}, + cmake={ + 'CMAKE_C_COMPILER': ['/usr/bin/i686-gnu-gcc'], + 'CMAKE_CXX_COMPILER': ['/usr/bin/i686-gnu-g++'], + 'CMAKE_SYSTEM_NAME': 'GNU', + 'CMAKE_SYSTEM_PROCESSOR': 'i686', + }, + # TODO: Currently hurd, but should be gnu as per + # https://mesonbuild.com/Reference-tables.html + system='TODO', + subsystem='TODO', + # TODO: Currently linux, but should be gnu/hurd/mach? + # https://github.com/mesonbuild/meson/issues/13740 + kernel='TODO', + # TODO: Currently hurd-i386, but should be i686 + cpu='TODO', + cpu_family='x86', + endian='little', + ), + ), + ( + 'special cases for amd64 (x86_64) and kFreeBSD', + textwrap.dedent( + ''' + DEB_HOST_ARCH=kfreebsd-amd64 + DEB_HOST_ARCH_ABI=base + DEB_HOST_ARCH_BITS=64 + DEB_HOST_ARCH_CPU=x86_amd64 + DEB_HOST_ARCH_ENDIAN=little + DEB_HOST_ARCH_LIBC=gnu + DEB_HOST_ARCH_OS=kfreebsd + DEB_HOST_GNU_CPU=x86_64 + DEB_HOST_GNU_SYSTEM=kfreebsd-gnu + DEB_HOST_GNU_TYPE=x86_64-kfreebsd-gnu + DEB_HOST_MULTIARCH=x86_64-kfreebsd-gnu + ''' + ), + '', + {'PATH': '/usr/bin'}, + MachineInfo( + compilers=expected_compilers('x86_64-kfreebsd-gnu'), + binaries=expected_binaries('x86_64-kfreebsd-gnu'), + properties={}, + compile_args={}, + link_args={}, + cmake={ + 'CMAKE_C_COMPILER': ['/usr/bin/x86_64-kfreebsd-gnu-gcc'], + 'CMAKE_CXX_COMPILER': ['/usr/bin/x86_64-kfreebsd-gnu-g++'], + 'CMAKE_SYSTEM_NAME': 'kFreeBSD', + 'CMAKE_SYSTEM_PROCESSOR': 'x86_64', + }, + system='kfreebsd', + subsystem='kfreebsd', + # TODO: Currently linux but should be freebsd + kernel='TODO', + # TODO: Currently kfreebsd-amd64 but should be x86_64 + cpu='TODO', + cpu_family='x86_64', + endian='little', + ), + ), + ( + 'special case for mips64el', + textwrap.dedent( + ''' + DEB_HOST_ARCH=mips64el + DEB_HOST_ARCH_ABI=abi64 + DEB_HOST_ARCH_BITS=64 + DEB_HOST_ARCH_CPU=mips64el + DEB_HOST_ARCH_ENDIAN=little + DEB_HOST_ARCH_LIBC=gnu + DEB_HOST_ARCH_OS=linux + DEB_HOST_GNU_CPU=mips64el + DEB_HOST_GNU_SYSTEM=linux-gnuabi64 + DEB_HOST_GNU_TYPE=mips64el-linux-gnuabi64 + DEB_HOST_MULTIARCH=mips64el-linux-gnuabi64 + ''' + ), + '', + {'PATH': '/usr/bin'}, + MachineInfo( + compilers=expected_compilers('mips64el-linux-gnuabi64'), + binaries=expected_binaries('mips64el-linux-gnuabi64'), + properties={}, + compile_args={}, + link_args={}, + cmake={ + 'CMAKE_C_COMPILER': ['/usr/bin/mips64el-linux-gnuabi64-gcc'], + 'CMAKE_CXX_COMPILER': ['/usr/bin/mips64el-linux-gnuabi64-g++'], + 'CMAKE_SYSTEM_NAME': 'Linux', + 'CMAKE_SYSTEM_PROCESSOR': 'mips64', + }, + system='linux', + subsystem='linux', + kernel='linux', + cpu='mips64', + cpu_family='mips64', + endian='little', + ), + ), + ( + 'special case for ppc64el', + textwrap.dedent( + ''' + DEB_HOST_ARCH=ppc64el + DEB_HOST_ARCH_ABI=base + DEB_HOST_ARCH_BITS=64 + DEB_HOST_ARCH_CPU=ppc64el + DEB_HOST_ARCH_ENDIAN=little + DEB_HOST_ARCH_LIBC=gnu + DEB_HOST_ARCH_OS=linux + DEB_HOST_GNU_CPU=powerpc64le + DEB_HOST_GNU_SYSTEM=linux-gnu + DEB_HOST_GNU_TYPE=powerpc64le-linux-gnu + DEB_HOST_MULTIARCH=powerpc64le-linux-gnu + ''' + ), + '', + {'PATH': '/usr/bin'}, + MachineInfo( + compilers=expected_compilers('powerpc64le-linux-gnu'), + binaries=expected_binaries('powerpc64le-linux-gnu'), + properties={}, + compile_args={}, + link_args={}, + cmake={ + 'CMAKE_C_COMPILER': ['/usr/bin/powerpc64le-linux-gnu-gcc'], + 'CMAKE_CXX_COMPILER': ['/usr/bin/powerpc64le-linux-gnu-g++'], + 'CMAKE_SYSTEM_NAME': 'Linux', + 'CMAKE_SYSTEM_PROCESSOR': 'ppc64le', + }, + system='linux', + subsystem='linux', + kernel='linux', + # TODO: Currently ppc64el, but native builds have ppc64le, + # and maybe it should be ppc64 in both cases? + # https://github.com/mesonbuild/meson/issues/13741 + cpu='TODO', + cpu_family='ppc64', + endian='little', + ), + ), + ]: + with self.subTest(title), \ + unittest.mock.patch.dict('os.environ', env, clear=True), \ + unittest.mock.patch('mesonbuild.scripts.env2mfile.locate_path') as mock_locate_path: + mock_locate_path.side_effect = locate_path + options = argparse.Namespace() + options.gccsuffix = gccsuffix + actual = to_machine_info(dpkg_arch, options) + + if expected.system == 'TODO': + print(f'TODO: {title}: system() -> {actual.system}') + else: + self.assertEqual(actual.system, expected.system) + + if expected.subsystem == 'TODO': + print(f'TODO: {title}: subsystem() -> {actual.subsystem}') + else: + self.assertEqual(actual.subsystem, expected.subsystem) + + if expected.kernel == 'TODO': + print(f'TODO: {title}: kernel() -> {actual.kernel}') + else: + self.assertEqual(actual.kernel, expected.kernel) + + if expected.cpu == 'TODO': + print(f'TODO: {title}: cpu() -> {actual.cpu}') + else: + self.assertEqual(actual.cpu, expected.cpu) + + self.assertEqual(actual.cpu_family, expected.cpu_family) + self.assertEqual(actual.endian, expected.endian) + + self.assertEqual(actual.compilers, expected.compilers) + self.assertEqual(actual.binaries, expected.binaries) + self.assertEqual(actual.properties, expected.properties) + self.assertEqual(actual.compile_args, expected.compile_args) + self.assertEqual(actual.link_args, expected.link_args) + self.assertEqual(actual.cmake, expected.cmake)