910e62b5创建于 1月15日历史提交
#!/usr/bin/env vpython3
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unittests for build_information.py."""

# pylint: disable=protected-access

import os
import sys
import unittest
from unittest import mock

# vpython-provided modules
# pylint: disable=import-error
from pyfakefs import fake_filesystem_unittest
# pylint: enable=import-error

# pylint: disable=wrong-import-position
sys.path.insert(0,
                os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from mcp_servers import build_information
# pylint: enable=wrong-import-position

CHROMIUM_ROOT = build_information.CHROMIUM_ROOT


class GetHostOsTest(unittest.TestCase):

    @mock.patch('sys.platform', 'linux')
    def test_get_host_os_linux(self):
        """Test get_host_os on linux."""
        self.assertEqual(build_information.get_host_os(),
                         build_information.ValidOs.LINUX)

    @mock.patch('sys.platform', 'cygwin')
    def test_get_host_os_cygwin_is_linux(self):
        """Test get_host_os on cygwin returns linux."""
        self.assertEqual(build_information.get_host_os(),
                         build_information.ValidOs.LINUX)

    @mock.patch('sys.platform', 'win32')
    def test_get_host_os_win(self):
        """Test get_host_os on windows."""
        self.assertEqual(build_information.get_host_os(),
                         build_information.ValidOs.WIN)

    @mock.patch('sys.platform', 'darwin')
    def test_get_host_os_mac(self):
        """Test get_host_os on mac."""
        self.assertEqual(build_information.get_host_os(),
                         build_information.ValidOs.MAC)

    @mock.patch('sys.platform', 'freebsd')
    def test_get_host_os_unknown(self):
        """Test get_host_os on an unknown os."""
        self.assertEqual(build_information.get_host_os(),
                         build_information.ValidOs.UNKNOWN)


class GetHostArchTest(unittest.TestCase):

    @mock.patch('mcp_servers.build_information._get_host_bits',
                return_value=build_information.Bitness.SIXTY_FOUR)
    @mock.patch('mcp_servers.build_information._get_host_architecture',
                return_value=build_information.Architecture.INTEL)
    def test_get_host_arch_x64(self, _mock_arch, _mock_bits):
        """Test get_host_arch for x64."""
        self.assertEqual(build_information.get_host_arch(),
                         build_information.ValidArch.X64)

    @mock.patch('mcp_servers.build_information._get_host_bits',
                return_value=build_information.Bitness.THIRTY_TWO)
    @mock.patch('mcp_servers.build_information._get_host_architecture',
                return_value=build_information.Architecture.INTEL)
    def test_get_host_arch_x86(self, _mock_arch, _mock_bits):
        """Test get_host_arch for x86."""
        self.assertEqual(build_information.get_host_arch(),
                         build_information.ValidArch.X86)

    @mock.patch('mcp_servers.build_information._get_host_bits',
                return_value=build_information.Bitness.SIXTY_FOUR)
    @mock.patch('mcp_servers.build_information._get_host_architecture',
                return_value=build_information.Architecture.ARM)
    def test_get_host_arch_arm64(self, _mock_arch, _mock_bits):
        """Test get_host_arch for arm64."""
        self.assertEqual(build_information.get_host_arch(),
                         build_information.ValidArch.ARM64)

    @mock.patch('mcp_servers.build_information._get_host_bits',
                return_value=build_information.Bitness.THIRTY_TWO)
    @mock.patch('mcp_servers.build_information._get_host_architecture',
                return_value=build_information.Architecture.ARM)
    def test_get_host_arch_arm(self, _mock_arch, _mock_bits):
        """Test get_host_arch for arm."""
        self.assertEqual(build_information.get_host_arch(),
                         build_information.ValidArch.ARM)

    @mock.patch('mcp_servers.build_information._get_host_bits')
    @mock.patch('mcp_servers.build_information._get_host_architecture',
                return_value=build_information.Architecture.UNKNOWN)
    def test_get_host_arch_unknown_arch(self, _mock_arch, _mock_bits):
        """Test get_host_arch for unknown architecture."""
        self.assertEqual(build_information.get_host_arch(),
                         build_information.ValidArch.UNKNOWN)

    @mock.patch('mcp_servers.build_information._get_host_bits',
                return_value=build_information.Bitness.UNKNOWN)
    @mock.patch('mcp_servers.build_information._get_host_architecture',
                return_value=build_information.Architecture.INTEL)
    def test_get_host_arch_intel_unknown_bits(self, _mock_arch, _mock_bits):
        """Test get_host_arch for intel with unknown bitness."""
        self.assertEqual(build_information.get_host_arch(),
                         build_information.ValidArch.UNKNOWN)

    @mock.patch('mcp_servers.build_information._get_host_bits',
                return_value=build_information.Bitness.UNKNOWN)
    @mock.patch('mcp_servers.build_information._get_host_architecture',
                return_value=build_information.Architecture.ARM)
    def test_get_host_arch_arm_unknown_bits(self, _mock_arch, _mock_bits):
        """Test get_host_arch for arm with unknown bitness."""
        self.assertEqual(build_information.get_host_arch(),
                         build_information.ValidArch.UNKNOWN)


class GetHostArchitectureTest(unittest.TestCase):

    @mock.patch('platform.processor', return_value='GenuineIntel')
    @mock.patch('platform.machine', return_value='x86_64')
    def test_get_host_architecture_intel_x64(self, _mock_machine,
                                             _mock_processor):
        """Test _get_host_architecture for intel x64."""
        self.assertEqual(build_information._get_host_architecture(),
                         build_information.Architecture.INTEL)

    @mock.patch('platform.processor', return_value='GenuineIntel')
    @mock.patch('platform.machine', return_value='x86')
    def test_get_host_architecture_intel_x86(self, _mock_machine,
                                             _mock_processor):
        """Test _get_host_architecture for intel x86."""
        self.assertEqual(build_information._get_host_architecture(),
                         build_information.Architecture.INTEL)

    @mock.patch('platform.processor', return_value='AuthenticAMD')
    @mock.patch('platform.machine', return_value='amd64')
    def test_get_host_architecture_intel_amd64(self, _mock_machine,
                                               _mock_processor):
        """Test _get_host_architecture for intel amd64."""
        self.assertEqual(build_information._get_host_architecture(),
                         build_information.Architecture.INTEL)

    @mock.patch('platform.processor', return_value='Apple M1')
    @mock.patch('platform.machine', return_value='arm64')
    def test_get_host_architecture_arm_native_arm64(self, _mock_machine,
                                                    _mock_processor):
        """Test _get_host_architecture for native arm64."""
        self.assertEqual(build_information._get_host_architecture(),
                         build_information.Architecture.ARM)

    @mock.patch('platform.processor', return_value='ARMv7')
    @mock.patch('platform.machine', return_value='arm')
    def test_get_host_architecture_arm_native_arm(self, _mock_machine,
                                                  _mock_processor):
        """Test _get_host_architecture for native arm."""
        self.assertEqual(build_information._get_host_architecture(),
                         build_information.Architecture.ARM)

    @mock.patch('platform.processor', return_value='... armv8 ...')
    @mock.patch('platform.machine', return_value='x86_64')
    def test_get_host_architecture_arm_emulated_x86(self, _mock_machine,
                                                    _mock_processor):
        """Test _get_host_architecture for emulated x86 on arm."""
        self.assertEqual(build_information._get_host_architecture(),
                         build_information.Architecture.ARM)

    @mock.patch('platform.processor', return_value='mips')
    @mock.patch('platform.machine', return_value='mips')
    def test_get_host_architecture_unknown(self, _mock_machine,
                                           _mock_processor):
        """Test _get_host_architecture for an unknown architecture."""
        self.assertEqual(build_information._get_host_architecture(),
                         build_information.Architecture.UNKNOWN)


class GetHostBitsTest(unittest.TestCase):

    @mock.patch('sys.maxsize', 2**32 + 1)
    def test_get_host_bits_64(self):
        """Test _get_host_bits for 64-bit."""
        self.assertEqual(build_information._get_host_bits(),
                         build_information.Bitness.SIXTY_FOUR)

    @mock.patch('sys.maxsize', 2**32 - 1)
    def test_get_host_bits_32(self):
        """Test _get_host_bits for 32-bit."""
        self.assertEqual(build_information._get_host_bits(),
                         build_information.Bitness.THIRTY_TWO)


class GetAllBuildDirectoriesTest(fake_filesystem_unittest.TestCase):

    def setUp(self):
        self.setUpPyfakefs()
        self.fs.create_dir(CHROMIUM_ROOT)

    def test_get_all_build_directories(self):
        """Test getting all build directories (standard and CrOS)."""
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out', 'Debug', 'args.gn'))
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out_cros', 'Debug', 'args.gn'))
        dirs = build_information.get_all_build_directories()
        self.assertEqual(
            set(dirs),
            {os.path.join('out', 'Debug'),
             os.path.join('out_cros', 'Debug')})


class GetValidBuildDirectoriesForConfigTest(fake_filesystem_unittest.TestCase):

    def setUp(self):
        self.setUpPyfakefs()
        self.fs.create_dir(CHROMIUM_ROOT)

        # Matching dir
        self.fs.create_file(os.path.join(CHROMIUM_ROOT, 'out', 'Linux_x64',
                                         'args.gn'),
                            contents='target_os = "linux"\ntarget_cpu = "x64"')
        # Mismatching os
        self.fs.create_file(os.path.join(CHROMIUM_ROOT, 'out', 'Win_x64',
                                         'args.gn'),
                            contents='target_os = "win"\ntarget_cpu = "x64"')
        # Mismatching cpu
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out', 'Linux_arm64', 'args.gn'),
            contents='target_os = "linux"\ntarget_cpu = "arm64"')
        # No args specified (should match)
        self.fs.create_file(os.path.join(CHROMIUM_ROOT, 'out', 'Default',
                                         'args.gn'),
                            contents='is_debug = true')
        # CrOS dir that matches
        self.fs.create_file(os.path.join(CHROMIUM_ROOT, 'out_cros',
                                         'linux_x64', 'args.gn'),
                            contents='target_os = "linux"\ntarget_cpu = "x64"')
        # Dir with no args.gn
        self.fs.create_dir(os.path.join(CHROMIUM_ROOT, 'out', 'NoArgs'))

    @mock.patch('mcp_servers.build_information.get_host_arch',
                return_value='x64')
    @mock.patch('mcp_servers.build_information.get_host_os',
                return_value='linux')
    def test_get_valid_build_information(self, _, __):
        """Test getting valid build directories for a specific config."""
        dirs = build_information.get_valid_build_directories_for_config(
            'linux', 'x64')
        self.assertEqual(
            set(dirs), {
                os.path.join('out', 'Linux_x64'),
                os.path.join('out', 'Default'),
                os.path.join('out_cros', 'linux_x64')
            })


class GetValidBuildDirectoriesForCurrentHostTest(unittest.TestCase):

    @mock.patch(
        'mcp_servers.build_information.get_valid_build_directories_for_config')
    @mock.patch('mcp_servers.build_information.get_host_arch')
    @mock.patch('mcp_servers.build_information.get_host_os')
    def test_valid_host_info(self, mock_get_os, mock_get_arch,
                             mock_get_valid_dirs):
        """Test getting dirs for current host with valid host info."""
        mock_get_os.return_value = build_information.ValidOs.LINUX
        mock_get_arch.return_value = build_information.ValidArch.X64
        mock_get_valid_dirs.return_value = ['out/Release']

        result = (
            build_information.get_valid_build_directories_for_current_host())

        mock_get_valid_dirs.assert_called_once_with('linux', 'x64')
        self.assertEqual(result, ['out/Release'])

    @mock.patch('mcp_servers.build_information.get_host_arch')
    @mock.patch('mcp_servers.build_information.get_host_os')
    def test_unknown_os(self, mock_get_os, mock_get_arch):
        """Test getting dirs for host with unknown OS returns empty list."""
        mock_get_os.return_value = build_information.ValidOs.UNKNOWN
        mock_get_arch.return_value = build_information.ValidArch.X64

        result = (
            build_information.get_valid_build_directories_for_current_host())
        self.assertEqual(result, [])

    @mock.patch('mcp_servers.build_information.get_host_arch')
    @mock.patch('mcp_servers.build_information.get_host_os')
    def test_unknown_arch(self, mock_get_os, mock_get_arch):
        """Test getting dirs for host with unknown arch returns empty list."""
        mock_get_os.return_value = build_information.ValidOs.LINUX
        mock_get_arch.return_value = build_information.ValidArch.UNKNOWN

        result = (
            build_information.get_valid_build_directories_for_current_host())
        self.assertEqual(result, [])


class GetStandardBuildDirectoriesTest(fake_filesystem_unittest.TestCase):

    def setUp(self):
        self.setUpPyfakefs()
        self.fs.create_dir(CHROMIUM_ROOT)

    def test_get_standard_build_directories(self):
        """Test getting standard build directories."""
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out', 'Debug', 'args.gn'))
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out_cros', 'Debug', 'args.gn'))
        dirs = build_information._get_standard_build_directories()
        self.assertEqual(set(dirs), {os.path.join('out', 'Debug')})


class GetCrosBuildDirectoriesTest(fake_filesystem_unittest.TestCase):

    def setUp(self):
        self.setUpPyfakefs()
        self.fs.create_dir(CHROMIUM_ROOT)

    def test_get_cros_build_directories(self):
        """Test getting CrOS build directories."""
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out', 'Debug', 'args.gn'))
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out_cros', 'Debug', 'args.gn'))
        dirs = build_information._get_cros_build_directories()
        self.assertEqual(set(dirs), {os.path.join('out_cros', 'Debug')})


class GetBuildDirectoriesUnderDirTest(fake_filesystem_unittest.TestCase):

    def setUp(self):
        self.setUpPyfakefs()
        self.fs.create_dir(CHROMIUM_ROOT)

    def test_get_build_directories_under_dir_no_directories(self):
        """Test behavior with no valid dirs."""
        self.fs.create_dir(os.path.join(CHROMIUM_ROOT, 'out'))
        self.assertEqual(
            build_information._get_build_directories_under_dir('out'), [])

    def test_get_build_directories_under_dir_multiple_valid(self):
        """Test behavior with multiple valid dirs."""
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out', 'Debug', 'args.gn'))
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out', 'Release', 'args.gn'))
        self.assertEqual(
            set(build_information._get_build_directories_under_dir('out')),
            {os.path.join('out', 'Debug'),
             os.path.join('out', 'Release')})

    def test_get_build_directories_under_dir_with_invalid(self):
        """Test behavior with a mix of valid/invalid dirs."""
        self.fs.create_dir(os.path.join(CHROMIUM_ROOT, 'out', 'Debug'))
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out', 'Release', 'args.gn'))
        self.assertEqual(
            build_information._get_build_directories_under_dir('out'),
            [os.path.join('out', 'Release')])

    def test_get_build_directories_under_dir_with_glob(self):
        """Test behavior with a glob pattern."""
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out_foo', 'Debug', 'args.gn'))
        self.fs.create_file(
            os.path.join(CHROMIUM_ROOT, 'out_bar', 'Release', 'args.gn'))
        self.fs.create_dir(os.path.join(CHROMIUM_ROOT, 'out_baz', 'Debug'))
        self.assertEqual(
            set(build_information._get_build_directories_under_dir('out_*')), {
                os.path.join('out_foo', 'Debug'),
                os.path.join('out_bar', 'Release')
            })


class DirectoryBuildsForConfigTest(fake_filesystem_unittest.TestCase):

    def setUp(self):
        self.setUpPyfakefs()
        self.fs.create_dir(CHROMIUM_ROOT)
        self.build_dir = os.path.join(CHROMIUM_ROOT, 'out', 'Release')
        self.fs.create_dir(self.build_dir)
        self.args_gn_path = os.path.join(self.build_dir, 'args.gn')

        self.os_patcher = mock.patch(
            'mcp_servers.build_information.get_host_os', return_value='linux')
        self.os_mock = self.os_patcher.start()
        self.addCleanup(self.os_patcher.stop)
        self.arch_patcher = mock.patch(
            'mcp_servers.build_information.get_host_arch', return_value='x64')
        self.arch_mock = self.arch_patcher.start()
        self.addCleanup(self.arch_patcher.stop)

    def test_no_args_gn_file(self):
        """Test directory without args.gn returns False."""
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))

    def test_empty_args_gn_matching_defaults(self):
        """Test empty args.gn is true if defaults match requested config."""
        self.fs.create_file(self.args_gn_path, contents='')
        self.assertTrue(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))
        self.os_mock.assert_called_once()
        self.arch_mock.assert_called_once()

    def test_empty_args_gn_mismatching_os_default(self):
        """Test empty args.gn is false if default os mismatches."""
        self.os_mock.return_value = 'win'
        self.fs.create_file(self.args_gn_path, contents='')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))
        self.os_mock.assert_called_once()

    def test_empty_args_gn_mismatching_cpu_default(self):
        """Test empty args.gn is false if default cpu mismatches."""
        self.arch_mock.return_value = 'arm64'
        self.fs.create_file(self.args_gn_path, contents='')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))
        self.os_mock.assert_called_once()
        self.arch_mock.assert_called_once()

    def test_matching_os_and_cpu(self):
        """Test args.gn with matching os and cpu."""
        self.fs.create_file(self.args_gn_path,
                            contents='target_os = "linux"\ntarget_cpu = "x64"')
        self.assertTrue(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))

    def test_mismatching_os(self):
        """Test args.gn with mismatching os."""
        self.fs.create_file(self.args_gn_path,
                            contents='target_os = "win"\ntarget_cpu = "x64"')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))

    def test_mismatching_cpu(self):
        """Test args.gn with mismatching cpu."""
        self.fs.create_file(
            self.args_gn_path,
            contents='target_os = "linux"\ntarget_cpu = "arm64"')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))

    def test_only_os_matching_cpu_default(self):
        """Test only os in args.gn is true if default cpu matches."""
        self.fs.create_file(self.args_gn_path, contents='target_os = "linux"')
        self.assertTrue(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))
        self.arch_mock.assert_called_once()

    def test_only_os_mismatching_cpu_default(self):
        """Test only os in args.gn is false if default cpu mismatches."""
        self.arch_mock.return_value = 'arm64'
        self.fs.create_file(self.args_gn_path, contents='target_os = "linux"')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))
        self.arch_mock.assert_called_once()

    def test_only_cpu_matching_os_default(self):
        """Test only cpu in args.gn is true if default os matches."""
        self.fs.create_file(self.args_gn_path, contents='target_cpu = "x64"')
        self.assertTrue(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))
        self.os_mock.assert_called_once()

    def test_only_cpu_mismatching_os_default(self):
        """Test only cpu in args.gn is false if default os mismatches."""
        self.os_mock.return_value = 'win'
        self.fs.create_file(self.args_gn_path, contents='target_cpu = "x64"')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))
        self.os_mock.assert_called_once()

    def test_with_import_matching(self):
        """Test matching config from an imported .gni file."""
        gni_import_path = '//build/config.gni'
        gni_path = os.path.join(CHROMIUM_ROOT, 'build', 'config.gni')
        self.fs.create_file(gni_path, contents='target_os = "linux"')
        self.fs.create_file(
            self.args_gn_path,
            contents=f'import("{gni_import_path}")\ntarget_cpu = "x64"')
        self.assertTrue(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))

    def test_with_import_mismatching(self):
        """Test mismatching config from an imported .gni file."""
        gni_import_path = '//build/config.gni'
        gni_path = os.path.join(CHROMIUM_ROOT, 'build', 'config.gni')
        self.fs.create_file(gni_path, contents='target_os = "win"')
        self.fs.create_file(
            self.args_gn_path,
            contents=f'import("{gni_import_path}")\ntarget_cpu = "x64"')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))

    def test_with_nonexistent_import(self):
        """Test args.gn with a non-existent import returns False."""
        self.fs.create_file(self.args_gn_path,
                            contents='import("//build/nonexistent.gni")')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))

    def test_with_malformed_file(self):
        """Test that a malformed args.gn returns False."""
        self.fs.create_file(self.args_gn_path,
                            contents='target_os = "linux"\nasdf asdf')
        self.assertFalse(
            build_information._directory_builds_for_config(
                self.build_dir, 'linux', 'x64'))


if __name__ == '__main__':
    unittest.main()