Ctrl K
ComunidadGuías…

scripts/test_build_host_kernel.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Exercise the native builder with a small source tree and a fake compiler."""
import io
import os
from pathlib import Path
import shutil
import subprocess
import tarfile
import tempfile
import unittest

SCRIPT = Path(__file__).resolve().parent / "build-host-kernel.sh"
CONFIG = r'''#!/usr/bin/env python3
import os, sys
from pathlib import Path
args = iter(sys.argv[1:])
assert next(args) == '--file'
config = Path(next(args))
with config.open('a') as out:
    for action in args:
        option = next(args)
        if action == '--enable' and option != os.environ.get('DROP_OPTION'):
            out.write(f'CONFIG_{option}=y\n')
        elif action == '--disable':
            out.write(f'# CONFIG_{option} is not set\n')
        elif action == '--set-str':
            out.write(f'CONFIG_{option}="{next(args)}"\n')
'''
MAKE = r'''#!/usr/bin/env python3
import os, sys
from pathlib import Path
args = sys.argv[1:]
objects = Path(next(a[2:] for a in args if a.startswith('O=')))
with Path(os.environ['MAKE_LOG']).open('a') as log:
    log.write(' '.join(args) + '\n')
if args[-1] == 'defconfig':
    (objects / '.config').write_text('')
elif args[-1] in ('vmlinux', 'Image'):
    if os.environ.get('FAIL_BUILD'):
        sys.exit(17)
    target = objects / ('vmlinux' if args[-1] == 'vmlinux' else 'arch/arm64/boot/Image')
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_bytes(b'compiled ' + args[-1].encode())
'''


class NativeKernelBuildTests(unittest.TestCase):
    def setUp(self):
        self.temp = tempfile.TemporaryDirectory()
        self.addCleanup(self.temp.cleanup)
        self.root = Path(self.temp.name)
        self.tools = self.root / 'bin'
        self.tools.mkdir()
        for name in ('cc', 'flex', 'bison', 'bc', 'perl'):
            self.executable(name, '#!/bin/sh\nexit 0\n')
        self.executable('cc', '#!/bin/sh\n[ "${MISSING_KERNEL_HEADERS:-0}" = 0 ]\n')
        self.executable('uname', '#!/bin/sh\nif [ "$1" = -s ]; then echo "${TEST_HOST_OS:-Linux}"; else echo "$TEST_HOST_ARCH"; fi\n')
        self.executable('make', MAKE)
        self.archive = self.root / 'linux-7.2.12.tar.xz'
        with tarfile.open(self.archive, 'w:xz') as archive:
            member = tarfile.TarInfo('linux-7.2.12/scripts/config')
            member.mode = 0o755
            data = CONFIG.encode()
            member.size = len(data)
            archive.addfile(member, io.BytesIO(data))
        self.log = self.root / 'make.log'
        self.output = self.root / 'kernel'

    def executable(self, name, content):
        path = self.tools / name
        path.write_text(content)
        path.chmod(0o755)

    def build(self, architecture='x86_64', **overrides):
        env = dict(os.environ, PATH=f'{self.tools}{os.pathsep}{os.environ["PATH"]}',
                   TEST_HOST_ARCH=architecture, MAKE_LOG=str(self.log), FIRECRAB_KERNEL_BUILD_JOBS='2')
        env.update(overrides)
        return subprocess.run(['bash', str(SCRIPT), architecture, '7.2.12', str(self.archive),
                               str(self.root / 'work'), str(self.output)], env=env,
                              text=True, capture_output=True, check=False)

    def preflight(self, **overrides):
        env = dict(os.environ, PATH=f'{self.tools}{os.pathsep}{os.environ["PATH"]}', TEST_HOST_ARCH='x86_64')
        env.update(overrides)
        return subprocess.run(['/bin/bash', str(SCRIPT), '--check', 'x86_64'],
                              env=env, text=True, capture_output=True, check=False)

    def test_preflight_needs_no_source_archive_or_work_directory(self):
        self.archive.unlink()
        result = self.preflight()
        self.assertEqual(result.returncode, 0, result.stderr)
        self.assertFalse((self.root / 'work').exists())
        self.assertFalse(self.log.exists())

    def test_preflight_rejects_missing_make(self):
        for name in ('tar', 'xz', 'install', 'grep'):
            (self.tools / name).symlink_to(shutil.which(name))
        (self.tools / 'make').unlink()
        result = self.preflight(PATH=str(self.tools))
        self.assertNotEqual(result.returncode, 0)
        self.assertIn('Missing kernel build tool: make', result.stderr)
        self.assertFalse((self.root / 'work').exists())

    def test_preflight_rejects_missing_headers(self):
        result = self.preflight(MISSING_KERNEL_HEADERS='1')
        self.assertNotEqual(result.returncode, 0)
        self.assertIn('install libelf and OpenSSL development packages', result.stderr)
        self.assertFalse((self.root / 'work').exists())

    def test_x86_build_has_required_builtin_drivers_and_preserves_config(self):
        result = self.build()
        self.assertEqual(result.returncode, 0, result.stderr)
        self.assertEqual(self.output.read_bytes(), b'compiled vmlinux')
        config = self.output.with_name('kernel.config').read_text()
        for option in ('EXT4_FS', 'VIRTIO_BLK', 'VIRTIO_NET', 'VIRTIO_MMIO', 'VSOCKETS', 'VIRTIO_VSOCKETS'):
            self.assertIn(f'CONFIG_{option}=y', config)
        self.assertIn('# CONFIG_MODULES is not set', config)
        self.assertIn('ARCH=x86 -j2 vmlinux', self.log.read_text())

    def test_aarch64_build_produces_image_and_uses_4k_pages(self):
        result = self.build('aarch64')
        self.assertEqual(result.returncode, 0, result.stderr)
        self.assertEqual(self.output.read_bytes(), b'compiled Image')
        self.assertIn('CONFIG_ARM64_4K_PAGES=y', self.output.with_name('kernel.config').read_text())
        self.assertIn('ARCH=arm64 -j2 Image', self.log.read_text())

    def test_missing_builtin_driver_stops_before_compilation(self):
        result = self.build(DROP_OPTION='VIRTIO_NET')
        self.assertNotEqual(result.returncode, 0)
        self.assertIn('Missing built-in kernel option: VIRTIO_NET', result.stderr)
        self.assertFalse(self.output.exists())

    def test_failed_build_does_not_publish_a_kernel(self):
        result = self.build(FAIL_BUILD='1')
        self.assertEqual(result.returncode, 17)
        self.assertFalse(self.output.exists())

    def test_missing_development_headers_stop_before_extraction(self):
        result = self.build(MISSING_KERNEL_HEADERS='1')
        self.assertNotEqual(result.returncode, 0)
        self.assertIn('install libelf and OpenSSL development packages', result.stderr)
        self.assertFalse((self.root / 'work').exists())

    def test_non_linux_host_is_rejected_before_extraction(self):
        result = self.build(TEST_HOST_OS='Darwin')
        self.assertNotEqual(result.returncode, 0)
        self.assertIn('require a Linux host', result.stderr)
        self.assertFalse((self.root / 'work').exists())

    def test_invalid_job_count_is_rejected(self):
        for value in ('0', '00', '-1', 'many'):
            with self.subTest(value=value):
                result = self.build(FIRECRAB_KERNEL_BUILD_JOBS=value)
                self.assertNotEqual(result.returncode, 0)
                self.assertIn('positive integer', result.stderr)
                self.assertFalse((self.root / 'work').exists())

    def test_relative_work_directory_becomes_absolute_for_make(self):
        env = dict(os.environ, PATH=f'{self.tools}{os.pathsep}{os.environ["PATH"]}',
                   TEST_HOST_ARCH='x86_64', MAKE_LOG=str(self.log))
        result = subprocess.run(['bash', str(SCRIPT), 'x86_64', '7.2.12', self.archive.name,
                                 'work', self.output.name], cwd=self.root, env=env,
                                text=True, capture_output=True, check=False)
        self.assertEqual(result.returncode, 0, result.stderr)
        self.assertIn(f'O={self.root.resolve()}/work/objects', self.log.read_text())

    def test_foreign_host_architecture_is_rejected(self):
        result = self.build(TEST_HOST_ARCH='aarch64')
        self.assertNotEqual(result.returncode, 0)
        self.assertIn('matching host architecture', result.stderr)
        self.assertFalse((self.root / 'work').exists())

    def test_missing_compiler_tool_is_reported_before_extraction(self):
        # A deliberately isolated PATH still provides every other prerequisite.
        for name in ('bash', 'tar', 'xz', 'install'):
            target = shutil.which(name)
            if target:
                (self.tools / name).symlink_to(target)
        (self.tools / 'flex').unlink()
        env = dict(os.environ, PATH=str(self.tools), TEST_HOST_ARCH='x86_64')
        result = subprocess.run(['/bin/bash', str(SCRIPT), 'x86_64', '7.2.12', str(self.archive),
                                 str(self.root / 'work'), str(self.output)], env=env,
                                text=True, capture_output=True, check=False)
        self.assertNotEqual(result.returncode, 0)
        self.assertIn('Missing kernel build tool: flex', result.stderr)
        self.assertFalse((self.root / 'work').exists())


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