#!/usr/bin/env perl use strict; use warnings; use Digest::SHA qw(sha256_hex); use File::Spec; use FindBin; use Test::More; my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' ); sub read_file { my ($relative_path) = @_; my $path = File::Spec->catfile( $repo_root, split( '/', $relative_path ) ); open( my $fh, '<', $path ) or die "Unable to read $path: $!"; my $contents = do { local $/; <$fh> }; close($fh); return $contents; } my $kas = read_file('xCAT-genesis-builder/oe/kas/common.yml'); like( $kas, qr/tag: yocto-6\.0\.2/, 'build uses Yocto 6.0.2' ); like( $kas, qr/fingerprint: 2AFB13F28FBBB0D1B9DAF63087EB3D32FB631AD9/, 'build trusts the Yocto release key' ); like( $kas, qr{yocto-release:.*?repo: xcat-core.*?path: xCAT-genesis-builder/oe/keys/yocto-release\.asc}s, 'build loads the Yocto release key from the source tree' ); like( $kas, qr{xcat-core:.*?layers:.*?xCAT-genesis-builder/oe/meta-xcat-genesis:}s, 'build loads the Genesis layer from the source tree' ); unlike( $kas, qr/gpg_keyserver:/, 'build does not depend on a public keyserver' ); like( $kas, qr/INHERIT \+= "archiver create-spdx vex"/, 'build generates source archives, an SBOM, and VEX metadata' ); like( $kas, qr/ARCHIVER_MODE\[src\] = "original"/, 'release archives retain original sources' ); like( $kas, qr/ARCHIVER_MODE\[diff\] = "1"/, 'release archives retain applied changes' ); like( $kas, qr/ARCHIVER_MODE\[recipe\] = "1"/, 'release archives retain recipe metadata' ); my $yocto_release_key = read_file('xCAT-genesis-builder/oe/keys/yocto-release.asc'); is( sha256_hex($yocto_release_key), '42d49e59f2aa01a1c1417a52d3c915a24a64060c852f33e3bd04fbb738457e70', 'vendored Yocto release key matches the reviewed key material' ); like( $kas, qr{bitbake:.*?commit: acfe02fa38b5da9e6a36c6cedcf91d4fcbefbfbd.*?signed: true}s, 'BitBake revision requires a signed tag' ); like( $kas, qr{openembedded-core:.*?commit: 5d1aa5c806c061a2994f4decb59016610f093213.*?signed: true}s, 'OpenEmbedded-Core revision requires a signed tag' ); like( $kas, qr{meta-openembedded:.*?commit: af8b6d6b2f0b11595b0a0d5b82efa3129d52a628}s, 'meta-openembedded revision is pinned' ); my $x86_kas = read_file('xCAT-genesis-builder/oe/kas/x86_64.yml'); like( $x86_kas, qr{includes:\s*\n\s*- xCAT-genesis-builder/oe/kas/common\.yml}, 'x86_64 build includes the common configuration' ); like( $x86_kas, qr/^machine: xcat-genesis-x86-64$/m, 'x86_64 build selects its machine' ); my $x86_32_kas = read_file('xCAT-genesis-builder/oe/kas/x86.yml'); like( $x86_32_kas, qr{includes:\s*\n\s*- xCAT-genesis-builder/oe/kas/common\.yml}, 'x86 build includes the common configuration' ); like( $x86_32_kas, qr/^machine: xcat-genesis-x86$/m, 'x86 build selects its machine' ); my $armv7hf_kas = read_file('xCAT-genesis-builder/oe/kas/armv7hf.yml'); like( $armv7hf_kas, qr{includes:\s*\n\s*- xCAT-genesis-builder/oe/kas/common\.yml}, 'armv7hf build includes the common configuration' ); like( $armv7hf_kas, qr/^machine: xcat-genesis-armv7hf$/m, 'armv7hf build selects its machine' ); my $aarch64_kas = read_file('xCAT-genesis-builder/oe/kas/aarch64.yml'); like( $aarch64_kas, qr{includes:\s*\n\s*- xCAT-genesis-builder/oe/kas/common\.yml}, 'aarch64 build includes the common configuration' ); like( $aarch64_kas, qr/^machine: xcat-genesis-aarch64$/m, 'aarch64 build selects its machine' ); my $riscv64_kas = read_file('xCAT-genesis-builder/oe/kas/riscv64.yml'); like( $riscv64_kas, qr{includes:\s*\n\s*- xCAT-genesis-builder/oe/kas/common\.yml}, 'riscv64 build includes the common configuration' ); like( $riscv64_kas, qr/^machine: xcat-genesis-riscv64$/m, 'riscv64 build selects its machine' ); my $ppc64le_kas = read_file('xCAT-genesis-builder/oe/kas/ppc64le.yml'); like( $ppc64le_kas, qr{includes:\s*\n\s*- xCAT-genesis-builder/oe/kas/common\.yml}, 'ppc64le build includes the common configuration' ); like( $ppc64le_kas, qr/^machine: xcat-genesis-ppc64le$/m, 'ppc64le build selects its machine' ); my $ppc64_kas = read_file('xCAT-genesis-builder/oe/kas/ppc64.yml'); like( $ppc64_kas, qr{includes:\s*\n\s*- xCAT-genesis-builder/oe/kas/common\.yml}, 'ppc64 build includes the common configuration' ); like( $ppc64_kas, qr/^machine: xcat-genesis-ppc64$/m, 'ppc64 build selects its machine' ); my $build = read_file('xCAT-genesis-builder/oe/build'); like( $build, qr/aarch64\|armv7hf\|riscv64\|x86\|x86_64\|ppc64\|ppc64le/, 'build accepts each supported architecture' ); like( $build, qr{kas/\$architecture\.yml}, 'build selects the architecture configuration directly' ); my $distro = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/conf/distro/xcat-genesis.conf' ); like( $distro, qr/^DISTRO_CODENAME = "cheetah"$/m, 'distro uses the Cheetah codename' ); like( $distro, qr/^LINUX_VERSION_EXTENSION = "-xCAT-genesis"$/m, 'kernel release identifies xCAT Genesis' ); like( $distro, qr/^KERNEL_MODULE_PACKAGE_SUFFIX = "-\$\{KERNEL_VERSION_PKG_NAME\}"$/m, 'kernel module package names use the normalized kernel release' ); like( $distro, qr/^TCLIBC = "glibc"$/m, 'distro uses glibc' ); like( $distro, qr/^INIT_MANAGER = "systemd"$/m, 'distro uses systemd' ); like( $distro, qr/^DISTRO_FEATURES_DEFAULTS = ""$/m, 'distro disables inherited feature defaults' ); like( $distro, qr/^DISTRO_FEATURES = "acl ipv4 ipv6 largefile pci seccomp"$/m, 'distro keeps an explicit feature list' ); like( $distro, qr/^NO_RECOMMENDATIONS = "1"$/m, 'image excludes package recommendations' ); like( $distro, qr/PACKAGECONFIG:pn-systemd = ".*\bsysext\b.*"/, 'systemd includes system extensions' ); like( $distro, qr/^PACKAGECONFIG:pn-networkmanager = "crypto-null libedit man-resolv-conf nmcli systemd"$/m, 'NetworkManager keeps the minimal feature set' ); like( $distro, qr/^PACKAGECONFIG:append:pn-iproute2 = " rdma"$/m, 'iproute2 includes the RDMA command' ); like( $distro, qr/^NETWORKMANAGER_DHCP_DEFAULT:pn-networkmanager = "internal"$/m, 'NetworkManager uses its internal DHCP client' ); like( $distro, qr/^NETWORKMANAGER_DNS_RC_MANAGER_DEFAULT:pn-networkmanager = "symlink"$/m, 'NetworkManager manages the resolver symlink' ); unlike( $distro, qr/\b(?:dhclient|dhcpcd)\b/, 'distro excludes external DHCP clients' ); my $machine = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/conf/machine/xcat-genesis-x86-64.conf' ); like( $machine, qr/^DEFAULTTUNE = "x86-64"$/m, 'x86_64 uses the baseline tune' ); unlike( $machine, qr/x86-64-v[234]/, 'x86_64 does not require a newer ISA level' ); like( $machine, qr/^MACHINE_FEATURES_DEFAULTS = ""$/m, 'machine disables inherited feature defaults' ); like( $machine, qr/^MACHINE_FEATURES = "pci"$/m, 'machine keeps an explicit feature list' ); like( $machine, qr/^IMAGE_FSTYPES = "cpio\.gz ext4"$/m, 'machine builds netboot and VM filesystems' ); like( $machine, qr/^QB_DEFAULT_FSTYPE = "ext4"$/m, 'QEMU uses a generated filesystem type' ); like( $machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyS0"$/m, 'x86_64 status console uses its serial terminal' ); like( $machine, qr/^XCAT_GENESIS_ARCHITECTURE = "x86_64"$/m, 'x86_64 exports its canonical extension identity' ); my $ppc64le_machine = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/conf/machine/xcat-genesis-ppc64le.conf' ); like( $ppc64le_machine, qr/^DEFAULTTUNE = "ppc64p8le"$/m, 'ppc64le uses the POWER8 little-endian tune' ); like( $ppc64le_machine, qr/^KERNEL_IMAGETYPE = "vmlinux"$/m, 'ppc64le builds a directly bootable kernel' ); like( $ppc64le_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "hvc0"$/m, 'ppc64le status console uses its hypervisor terminal' ); like( $ppc64le_machine, qr/^QB_MACHINE = "-machine pseries"$/m, 'ppc64le QEMU target uses pSeries' ); like( $ppc64le_machine, qr/^QB_CPU = "-cpu POWER8"$/m, 'ppc64le QEMU target uses the supported CPU baseline' ); unlike( $ppc64le_machine, qr/^DEFAULTTUNE = "ppc64"$/m, 'ppc64le is not aliased to big-endian ppc64' ); like( $ppc64le_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "ppc64le"$/m, 'ppc64le exports its canonical extension identity' ); my $ppc64_machine = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/conf/machine/xcat-genesis-ppc64.conf' ); like( $ppc64_machine, qr/^DEFAULTTUNE = "powerpc64"$/m, 'ppc64 uses the generic big-endian ABI' ); unlike( $ppc64_machine, qr/^DEFAULTTUNE = "ppc64p8le"$/m, 'ppc64 is not aliased to little-endian ppc64le' ); like( $ppc64_machine, qr/^QB_MACHINE = "-machine pseries"$/m, 'ppc64 QEMU target uses pSeries' ); like( $ppc64_machine, qr/^QB_CPU = "-cpu POWER8"$/m, 'ppc64 QEMU target can emulate the generic ABI' ); like( $ppc64_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "hvc0"$/m, 'ppc64 status console uses its hypervisor terminal' ); like( $ppc64_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "ppc64"$/m, 'ppc64 exports its canonical extension identity' ); my $x86_machine = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/conf/machine/xcat-genesis-x86.conf' ); like( $x86_machine, qr/^DEFAULTTUNE = "i686"$/m, 'x86 uses the i686 baseline' ); unlike( $x86_machine, qr/^DEFAULTTUNE = "i586"$/m, 'x86 does not retain the i586 baseline' ); like( $x86_machine, qr/^QB_SYSTEM_NAME = "qemu-system-i386"$/m, 'x86 uses the 32-bit QEMU system target' ); like( $x86_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyS0"$/m, 'x86 status console uses its serial terminal' ); like( $x86_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "x86"$/m, 'x86 exports its canonical extension identity' ); my $armv7hf_machine = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/conf/machine/xcat-genesis-armv7hf.conf' ); like( $armv7hf_machine, qr/^DEFAULTTUNE = "armv7ahf"$/m, 'armv7hf uses the ARMv7-A hard-float tune' ); like( $armv7hf_machine, qr/^ARM_INSTRUCTION_SET = "arm"$/m, 'armv7hf does not require Thumb instructions' ); unlike( $armv7hf_machine, qr/neon/i, 'armv7hf does not require NEON' ); like( $armv7hf_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "armv7hf"$/m, 'armv7hf exports its canonical extension identity' ); like( $armv7hf_machine, qr/^QB_MACHINE = "-machine virt,highmem=off"$/m, 'armv7hf QEMU target uses the virtual platform' ); like( $armv7hf_machine, qr/^QB_CPU = "-cpu cortex-a15"$/m, 'armv7hf QEMU target uses an ARMv7 CPU' ); like( $armv7hf_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyAMA0"$/m, 'armv7hf status console uses its PL011 terminal' ); my $aarch64_machine = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/conf/machine/xcat-genesis-aarch64.conf' ); like( $aarch64_machine, qr/^DEFAULTTUNE = "aarch64"$/m, 'aarch64 uses the generic ARMv8-A tune' ); like( $aarch64_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "aarch64"$/m, 'aarch64 exports its canonical extension identity' ); like( $aarch64_machine, qr/^QB_MACHINE = "-machine virt"$/m, 'aarch64 QEMU target uses the virtual platform' ); like( $aarch64_machine, qr/^QB_CPU = "-cpu cortex-a57"$/m, 'aarch64 QEMU target uses its conservative CPU baseline' ); like( $aarch64_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyAMA0"$/m, 'aarch64 status console uses its PL011 terminal' ); my $riscv64_machine = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/conf/machine/xcat-genesis-riscv64.conf' ); like( $riscv64_machine, qr/^DEFAULTTUNE = "riscv64"$/m, 'riscv64 uses the RV64GC tune' ); like( $riscv64_machine, qr/^XCAT_GENESIS_ARCHITECTURE = "riscv64"$/m, 'riscv64 exports its canonical extension identity' ); like( $riscv64_machine, qr/^EXTRA_IMAGEDEPENDS = "opensbi"$/m, 'riscv64 builds its OpenSBI firmware' ); like( $riscv64_machine, qr/^QB_DEFAULT_BIOS = "fw_jump\.elf"$/m, 'riscv64 QEMU target boots the built OpenSBI firmware' ); like( $riscv64_machine, qr/^QB_MACHINE = "-machine virt"$/m, 'riscv64 QEMU target uses the virtual platform' ); like( $riscv64_machine, qr/^QB_CPU = "-cpu rv64"$/m, 'riscv64 QEMU target uses the generic CPU' ); like( $riscv64_machine, qr/^XCAT_GENESIS_CONSOLE_TTY = "ttyS0"$/m, 'riscv64 status console uses its 8250 terminal' ); for my $machine_console ( [ x86_64 => $machine ], [ x86 => $x86_machine ], [ ppc64 => $ppc64_machine ], [ ppc64le => $ppc64le_machine ], [ armv7hf => $armv7hf_machine ], [ aarch64 => $aarch64_machine ], [ riscv64 => $riscv64_machine ], ) { unlike( $machine_console->[1], qr/^QB_KERNEL_CMDLINE_APPEND = ".*xcat\.debug-shell/m, "$machine_console->[0] VM starts the status console" ); } my $image = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/images/xcat-genesis-image.bb' ); unlike( $image, qr/\b(?:dhclient|dhcpcd|busybox-udhcpc|systemd-networkd)\b/, 'base image excludes alternate network clients' ); like( $image, qr/\bxcat-genesis-network\b/, 'base image includes the Genesis network policy' ); like( $image, qr/\bxcat-genesis-protocol\b/, 'base image includes the destiny clients' ); like( $image, qr/\bpackagegroup-xcat-genesis-hardware\b/, 'base image includes open hardware tools' ); like( $image, qr/\bxcat-genesis-extensions\b/, 'base image includes the signed extension loader' ); like( $image, qr/\bxcat-genesis-console\b/, 'base image includes the status console' ); my $hardware_group = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/packagegroups/packagegroup-xcat-genesis-hardware.bb' ); for my $tool ( qw( pciutils usbutils hwloc ethtool rdma-core iproute2-rdma lldpd ipmitool curl jq nvme-cli smartmontools sg3-utils lsscsi mdadm hdparm parted lvm2 memtester stress-ng edac-utils kernel-modules mstflint xcat-genesis-hardware-control ) ) { like( $hardware_group, qr/\b\Q$tool\E\b/, "hardware group includes $tool" ); } like( $hardware_group, qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-x86-64 = " dmidecode lshw mcelog"$/m, 'x86_64 diagnostics are architecture scoped' ); like( $hardware_group, qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-x86 = " dmidecode lshw"$/m, 'x86 omits the unsupported machine-check daemon' ); like( $hardware_group, qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-armv7hf = " dmidecode lshw"$/m, 'armv7hf diagnostics are architecture scoped' ); unlike( $hardware_group, qr/xcat-genesis-armv7(?:\s|\b)/, 'hardware packages do not use the old ARMv7 name' ); like( $hardware_group, qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-ppc64 = " dmidecode xcat-genesis-hardware-control-iprutils"$/m, 'ppc64 includes the Power RAID provider' ); like( $hardware_group, qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-ppc64le = " dmidecode xcat-genesis-hardware-control-iprutils"$/m, 'ppc64le includes the Power RAID provider' ); like( $hardware_group, qr/^RDEPENDS:\$\{PN\}:append:xcat-genesis-riscv64 = " lshw"$/m, 'RISC-V omits unsupported DMI tools' ); unlike( $hardware_group, qr/\b(?:storcli|perccli|ssacli|arcconf|nvidia-smi|rocm-smi)\b/i, 'base image excludes vendor-only tools' ); my $hardware_recipe = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-hardware-control/xcat-genesis-hardware-control_1.0.bb' ); like( $hardware_recipe, qr/^RDEPENDS:\$\{PN\} = "bash coreutils jq mstflint nvme-cli util-linux-flock"$/m, 'hardware control dependencies are explicit' ); like( $hardware_recipe, qr/^PACKAGES:prepend:xcat-genesis-ppc64 = "\$\{PN\}-iprutils "$/m, 'iprutils provider package supports ppc64' ); like( $hardware_recipe, qr/^PACKAGES:prepend:xcat-genesis-ppc64le = "\$\{PN\}-iprutils "$/m, 'iprutils provider package supports ppc64le' ); like( $hardware_recipe, qr/^RDEPENDS:\$\{PN\}-iprutils:xcat-genesis-ppc64 = "\$\{PN\} bash iprutils"$/m, 'ppc64 iprutils dependencies are architecture scoped' ); like( $hardware_recipe, qr/^RDEPENDS:\$\{PN\}-iprutils:xcat-genesis-ppc64le = "\$\{PN\} bash iprutils"$/m, 'iprutils provider dependencies are architecture scoped' ); like( $hardware_recipe, qr/\$\{datadir\}\/xcat\/genesis\/providers/, 'provider manifests use an architecture-neutral data path' ); unlike( $hardware_recipe, qr/\b(?:storcli|perccli|ssacli|arcconf|nvidia-smi|rocm-smi)\b/i, 'hardware control excludes vendor-only tools' ); my $hardware_dispatcher = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-hardware-control/files/genesis-hardware' ); like( $hardware_dispatcher, qr/destructive capability requires a task identity/, 'destructive operations require a task identity' ); like( $hardware_dispatcher, qr/destructive capability requires an exact device identity/, 'destructive operations require an exact device' ); like( $hardware_dispatcher, qr/write_audit started/, 'destructive operations start an audit record' ); like( $hardware_dispatcher, qr/write_audit completed/, 'successful destructive operations complete their audit record' ); like( $hardware_dispatcher, qr/write_audit failed/, 'failed destructive operations close their audit record' ); for my $provider (qw(nvme mstflint iprutils)) { my $manifest = read_file( "xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-hardware-control/files/$provider.json" ); unlike( $manifest, qr/"destructive"\s*:\s*true/, "$provider base provider is read-only" ); } my $mstflint = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-support/mstflint/mstflint_4.36.0.bb' ); like( $mstflint, qr/^LICENSE = "Linux-OpenIB & MIT & BSD-2-Clause"$/m, 'mstflint records every linked source license' ); like( $mstflint, qr/^SRCREV = "b9d9e844a14ae1c85cb90c76ccd4a56a0eccd599"$/m, 'mstflint source revision is pinned' ); like( $mstflint, qr/^RDEPENDS:\$\{PN\} = "python3-modules"$/m, 'mstflint installs its Python runtime' ); like( $mstflint, qr/^PACKAGECONFIG \?\?= "adb cables dc inband openssl"$/m, 'mstflint omits the non-portable rdmem tool' ); unlike( $mstflint, qr/--enable-(?:fw-mgr|nvml)/, 'mstflint excludes vendor-coupled features' ); my $iprutils = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-support/iprutils/iprutils_2.4.19.bb' ); like( $iprutils, qr/^LICENSE = "CPL-1\.0"$/m, 'iprutils records its source license' ); like( $iprutils, qr/^SRCREV = "9961e538736a6e81f1072a926c3ad91b8513c8c1"$/m, 'iprutils source revision is pinned' ); like( $iprutils, qr/^COMPATIBLE_HOST = "powerpc64\.\*-linux"$/m, 'iprutils is restricted to 64-bit Power targets' ); like( $iprutils, qr/--without-systemd --without-initscripts/, 'iprutils omits unused background services' ); my $kernel_append = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto_%.bbappend' ); like( $kernel_append, qr/file:\/\/xcat-genesis-x86-64\.cfg/, 'x86_64 kernel uses the Genesis hardware fragment' ); like( $kernel_append, qr/file:\/\/xcat-genesis-x86-common\.cfg/, 'x86 targets share their hardware fragment' ); like( $kernel_append, qr/file:\/\/xcat-genesis-x86\.cfg/, 'x86 kernel uses its baseline fragment' ); like( $kernel_append, qr/do_kernel_metadata:prepend:xcat-genesis-x86/, 'x86 replaces the upstream QEMU CPU policy' ); like( $kernel_append, qr/file:\/\/xcat-genesis-armv7hf\.cfg/, 'armv7hf kernel uses the Genesis hardware fragment' ); like( $kernel_append, qr/^SRCREV_machine:xcat-genesis-armv7hf = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m, 'armv7hf kernel revision is pinned' ); like( $kernel_append, qr/file:\/\/xcat-genesis-aarch64\.cfg/, 'aarch64 kernel uses the Genesis hardware fragment' ); like( $kernel_append, qr/^SRCREV_machine:xcat-genesis-aarch64 = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m, 'aarch64 kernel revision is pinned' ); like( $kernel_append, qr/file:\/\/xcat-genesis-riscv64\.cfg/, 'riscv64 kernel uses the Genesis hardware fragment' ); like( $kernel_append, qr/^SRCREV_machine:xcat-genesis-riscv64 = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m, 'riscv64 kernel revision is pinned' ); like( $kernel_append, qr/file:\/\/xcat-genesis-powerpc64\.cfg/, '64-bit Power kernels share the Genesis hardware fragment' ); like( $kernel_append, qr/^SRCREV_machine:xcat-genesis-ppc64le = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m, 'ppc64le kernel revision is pinned' ); like( $kernel_append, qr/do_kernel_metadata:prepend:xcat-genesis-ppc64le/, 'ppc64le repairs the upstream kernel metadata before configuration' ); like( $kernel_append, qr/^SRCREV_machine:xcat-genesis-ppc64 = "b1ba5428513b52c2bd6acfd3ad0a910f699bc395"$/m, 'ppc64 kernel revision is pinned' ); like( $kernel_append, qr/do_kernel_metadata:prepend:xcat-genesis-ppc64\(\)/, 'ppc64 repairs the upstream kernel metadata before configuration' ); like( $kernel_append, qr/file:\/\/xcat-genesis-ppc64\.cfg/, 'ppc64 kernel uses its big-endian fragment' ); my $ppc64_kernel_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-ppc64.cfg' ); like( $ppc64_kernel_config, qr/^CONFIG_CPU_BIG_ENDIAN=y$/m, 'ppc64 kernel is big endian' ); like( $ppc64_kernel_config, qr/^# CONFIG_CPU_LITTLE_ENDIAN is not set$/m, 'ppc64 kernel is not little endian' ); my $kernel_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-x86-common.cfg' ); for my $symbol ( qw( CONFIG_MODULES CONFIG_MEGARAID_SAS CONFIG_SCSI_MPT3SAS CONFIG_SCSI_HPSA CONFIG_BLK_DEV_NVME CONFIG_IXGBE CONFIG_I40E CONFIG_ICE CONFIG_MLX5_CORE CONFIG_INFINIBAND CONFIG_MLX5_INFINIBAND CONFIG_IPMI_HANDLER CONFIG_EDAC CONFIG_USB_STORAGE CONFIG_BLK_DEV_LOOP CONFIG_OVERLAY_FS CONFIG_SQUASHFS CONFIG_SQUASHFS_XATTR CONFIG_SQUASHFS_ZSTD ) ) { like( $kernel_config, qr/^\Q$symbol\E=[ym]$/m, "x86_64 kernel enables $symbol" ); } my $x86_kernel_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-x86.cfg' ); like( $x86_kernel_config, qr/^CONFIG_M686=y$/m, 'x86 kernel selects the i686 processor family' ); like( $x86_kernel_config, qr/^# CONFIG_M586 is not set$/m, 'x86 kernel does not retain i586' ); like( $x86_kernel_config, qr/^CONFIG_HIGHMEM4G=y$/m, 'x86 kernel supports the 32-bit high-memory model' ); like( $x86_kernel_config, qr/^# CONFIG_X86_MCE is not set$/m, 'x86 kernel supports processors without machine-check registers' ); for my $vendor (qw(AMD INTEL)) { like( $x86_kernel_config, qr/^# CONFIG_X86_MCE_\Q$vendor\E is not set$/m, "x86 kernel disables the $vendor machine-check feature" ); } my $x86_64_kernel_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-x86-64.cfg' ); like( $x86_64_kernel_config, qr/^CONFIG_EDAC_AMD64=m$/m, 'x86_64 keeps its architecture-specific EDAC driver' ); my $armv7hf_kernel_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-armv7hf.cfg' ); for my $symbol ( qw( CONFIG_ARCH_VIRT CONFIG_AEABI CONFIG_VFP CONFIG_SERIAL_AMBA_PL011 CONFIG_VIRTIO_MMIO CONFIG_VIRTIO_BLK CONFIG_VIRTIO_NET CONFIG_SCSI_MPT3SAS CONFIG_MEGARAID_SAS CONFIG_BLK_DEV_NVME CONFIG_IXGBE CONFIG_MLX4_CORE CONFIG_INFINIBAND CONFIG_IPMI_HANDLER CONFIG_USB_XHCI_HCD CONFIG_USB_STORAGE CONFIG_BLK_DEV_LOOP CONFIG_OVERLAY_FS CONFIG_SQUASHFS ) ) { like( $armv7hf_kernel_config, qr/^\Q$symbol\E=[ym]$/m, "armv7hf kernel enables $symbol" ); } my $aarch64_kernel_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-aarch64.cfg' ); for my $symbol ( qw( CONFIG_ARM64 CONFIG_ARCH_VIRT CONFIG_SERIAL_AMBA_PL011 CONFIG_VIRTIO_PCI CONFIG_VIRTIO_BLK CONFIG_VIRTIO_NET CONFIG_SCSI_MPT3SAS CONFIG_SCSI_HPSA CONFIG_MEGARAID_SAS CONFIG_BLK_DEV_NVME CONFIG_IXGBE CONFIG_I40E CONFIG_ICE CONFIG_MLX5_CORE CONFIG_INFINIBAND CONFIG_MLX5_INFINIBAND CONFIG_IPMI_HANDLER CONFIG_EDAC CONFIG_USB_XHCI_HCD CONFIG_USB_STORAGE CONFIG_BLK_DEV_LOOP CONFIG_OVERLAY_FS CONFIG_SQUASHFS ) ) { like( $aarch64_kernel_config, qr/^\Q$symbol\E=[ym]$/m, "aarch64 kernel enables $symbol" ); } my $riscv64_kernel_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-riscv64.cfg' ); for my $symbol ( qw( CONFIG_64BIT CONFIG_RISCV CONFIG_RISCV_SBI CONFIG_SERIAL_8250_CONSOLE CONFIG_VIRTIO_PCI CONFIG_VIRTIO_BLK CONFIG_VIRTIO_NET CONFIG_SCSI_MPT3SAS CONFIG_SCSI_HPSA CONFIG_MEGARAID_SAS CONFIG_BLK_DEV_NVME CONFIG_IXGBE CONFIG_I40E CONFIG_ICE CONFIG_MLX5_CORE CONFIG_INFINIBAND CONFIG_MLX5_INFINIBAND CONFIG_IPMI_HANDLER CONFIG_EDAC CONFIG_USB_XHCI_HCD CONFIG_USB_STORAGE CONFIG_BLK_DEV_LOOP CONFIG_OVERLAY_FS CONFIG_SQUASHFS ) ) { like( $riscv64_kernel_config, qr/^\Q$symbol\E=[ym]$/m, "riscv64 kernel enables $symbol" ); } my $powerpc64_kernel_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-kernel/linux/linux-yocto/xcat-genesis-powerpc64.cfg' ); for my $symbol ( qw( CONFIG_PPC_PSERIES CONFIG_PPC_POWERNV CONFIG_PPC_64S_HASH_MMU CONFIG_PPC_RADIX_MMU CONFIG_SCSI_IPR CONFIG_SCSI_IBMVSCSI CONFIG_SCSI_IBMVFC CONFIG_MEGARAID_SAS CONFIG_BLK_DEV_NVME CONFIG_IBMVETH CONFIG_IBMVNIC CONFIG_MLX5_CORE CONFIG_INFINIBAND CONFIG_IPMI_HANDLER CONFIG_USB_XHCI_HCD CONFIG_USB_STORAGE CONFIG_BLK_DEV_LOOP CONFIG_OVERLAY_FS CONFIG_SQUASHFS ) ) { like( $powerpc64_kernel_config, qr/^\Q$symbol\E=[ym]$/m, "64-bit Power kernel enables $symbol" ); } like( $powerpc64_kernel_config, qr/^# CONFIG_PPC_VAS is not set$/m, 'POWER8 image disables unsupported VAS facilities' ); my $protocol_recipe = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-xcat/xcat-genesis-protocol/xcat-genesis-protocol_1.0.bb' ); like( $protocol_recipe, qr{^FILESEXTRAPATHS:prepend := "\$\{THISDIR\}/\.\./\.\./\.\./\.\./\.\./xCAT-genesis-scripts/usr/bin:"$}m, 'destiny clients use the canonical scripts' ); like( $protocol_recipe, qr/file:\/\/getdestiny.*file:\/\/nextdestiny/s, 'both destiny clients are packaged' ); like( $protocol_recipe, qr/^RDEPENDS:\$\{PN\} = "bash openssl-bin"$/m, 'destiny client dependencies are explicit' ); unlike( $protocol_recipe, qr/\bdoxcat\b/, 'obsolete bootstrap script is excluded' ); my $networkmanager = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/networkmanager/networkmanager_%.bbappend' ); like( $networkmanager, qr/^EXTRA_OEMESON:append = " -Dtests=no"$/m, 'NetworkManager omits its test suite' ); like( $networkmanager, qr{sed -i '/\^Also=NetworkManager-wait-online\.service\$/d'}, 'NetworkManager does not enable wait-online' ); my $network_recipe = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/xcat-genesis-network_1.0.bb' ); like( $network_recipe, qr/^RDEPENDS:\$\{PN\} = "networkmanager-daemon networkmanager-nmcli"$/m, 'network policy installs the daemon and nmcli' ); my $network_config = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-connectivity/xcat-genesis-network/files/10-xcat-genesis.conf' ); like( $network_config, qr/^ipv4\.dhcp-timeout=20$/m, 'IPv4 DHCP has a bounded timeout' ); like( $network_config, qr/^ipv6\.dhcp-duid=ll$/m, 'IPv6 DHCP uses the link-layer DUID' ); like( $network_config, qr/^ipv6\.dhcp-timeout=20$/m, 'IPv6 DHCP has a bounded timeout' ); my $init_recipe = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/xcat-genesis-init_1.0.bb' ); like( $init_recipe, qr/\bcoreutils\b/, 'runtime status dependencies are explicit' ); like( $init_recipe, qr/\butil-linux-logger\b/, 'runtime logging dependency is explicit' ); like( $init_recipe, qr/file:\/\/genesis-status/, 'runtime status helper is packaged' ); like( $init_recipe, qr/file:\/\/genesis-maintenance-shell/, 'maintenance shell is packaged' ); like( $init_recipe, qr/file:\/\/genesis-functions/, 'shared runtime functions are packaged' ); like( $init_recipe, qr/install -m 0644 .*genesis-functions.*\$\{libexecdir\}\/xcat\/genesis-functions/s, 'shared runtime functions are installed as data' ); unlike( $init_recipe, qr/genesis-debug-shell|XCAT_GENESIS_DEBUG/, 'legacy debug shell is absent from the image recipe' ); my $legacy_debug_shell = File::Spec->catfile( $repo_root, qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files genesis-debug-shell) ); my $legacy_debug_service = File::Spec->catfile( $repo_root, qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-init files xcat-genesis-debug-shell@.service) ); ok( !-e $legacy_debug_shell, 'legacy debug shell script is removed' ); ok( !-e $legacy_debug_service, 'legacy debug shell service is removed' ); my $maintenance_shell = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-maintenance-shell' ); like( $maintenance_shell, qr/^#!\/bin\/bash$/m, 'maintenance shell uses the packaged Bash runtime' ); like( $maintenance_shell, qr/^printf 'xCAT Genesis maintenance shell\\n'$/m, 'maintenance shell identifies Genesis' ); like( $maintenance_shell, qr/^printf 'Exit returns to the status console\.\\n\\n'$/m, 'maintenance shell explains how to return to the console' ); like( $maintenance_shell, qr/^export PS1='genesis# '$/m, 'maintenance shell has an explicit prompt' ); like( $maintenance_shell, qr{^exec /bin/bash --noprofile --norc -i$}m, 'maintenance shell starts isolated Bash' ); my $status_helper = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/genesis-status' ); like( $status_helper, qr/mktemp .*\.\$\{component\}\.XXXXXX/, 'runtime status records use atomic temporary files' ); like( $status_helper, qr/tr -cd '\\040-\\176'/, 'runtime status details are printable text' ); my $console_recipe = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/xcat-genesis-console_1.0.bb' ); like( $console_recipe, qr/^DEPENDS = "libnewt systemd"$/m, 'status console builds against libnewt and the journal API' ); like( $console_recipe, qr/^RDEPENDS:\$\{PN\} = "libnewt libsystemd ncurses-terminfo-base xcat-genesis-init"$/m, 'status console installs its terminal and maintenance-shell runtime' ); like( $console_recipe, qr/^inherit meson pkgconfig systemd$/m, 'status console uses the project build definition' ); unlike( $console_recipe, qr/\b(?:whiptail|popt)\b/, 'status console omits the dialog wrapper' ); my $console_build = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/files/xcat-genesis-console/meson.build' ); like( $console_build, qr/'c_std=c17'/, 'status console uses the C17 language contract' ); like( $console_build, qr/dependency\('libnewt'\).*dependency\('libsystemd'\)/s, 'status console links Newt and the journal client library' ); like( $console_build, qr/'werror=true'/, 'status console treats compiler warnings as build failures' ); my $console_service = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/files/xcat-genesis-console@.service' ); like( $console_service, qr/^Environment=TERM=xterm$/m, 'status console redraws line graphics after a late attach' ); like( $console_service, qr/^Environment=COLUMNS=80$/m, 'status console fixes the serial width' ); like( $console_service, qr/^Environment=LINES=24$/m, 'status console fixes the serial height' ); like( $console_service, qr/^TTYPath=\/dev\/%I$/m, 'status console binds to the machine console' ); unlike( $console_service, qr/xcat\.debug-shell|genesis-debug-shell/, 'status console has no boot-time debug escape' ); my $console_source_dir = 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-console/files/xcat-genesis-console/src'; my $console_source = join "\n", map { read_file("$console_source_dir/$_") } qw(console.h main.c newt_ui.c plain_ui.c shell.c state.c support.c); ok( !-e File::Spec->catfile( $repo_root, qw(xCAT-genesis-builder oe meta-xcat-genesis recipes-core xcat-genesis-console files xcat-genesis-console.c) ), 'status console is not kept as a monolithic source file' ); unlike( $console_source, qr/genesis-debug-shell|XCAT_ON_DEMAND_SHELL/, 'status console only uses the common maintenance-shell launcher' ); like( $console_source, qr/newtDrawRootText\(1, 0, "xCAT Genesis"\)/, 'status console keeps the product name in the header' ); like( $console_source, qr/F1 Help F2 Diagnostics F3 Logs F12 Shell/, 'status console keeps useful shortcuts in the footer' ); unlike( $console_source, qr/NEWT_KEY_F5|F5 Refresh|Ctrl-L Redraw/, 'status console omits redundant redraw shortcuts' ); like( $console_source, qr/newtFormSetTimer\(screen\.form, 1000\)/, 'status console updates timers once per second' ); like( $console_source, qr/"In stage"/, 'status console labels the stage duration precisely' ); like( $console_source, qr/identity = strcmp\(state->node, "unassigned"\).*?state->local_hostname/s, 'status header falls back to the local hostname before assignment' ); like( $console_source, qr/show_serial = useful_identity\(state->serial\).*?state->serial/s, 'status header includes a useful firmware serial' ); unlike( $console_source, qr/xcat_set_text\(context,\s*sizeof\(context\),[^;]*state->architecture/s, 'status header omits the architecture' ); like( $console_source, qr/newtCenteredWindow\(72, 17, "Genesis status"\)/, 'main status removes unused top and bottom rows' ); like( $console_source, qr/newtCenteredWindow\(72, 19, "Genesis diagnostics"\)/, 'F2 opens diagnostics' ); like( $console_source, qr/xcat\.bootloader.*?"xnba".*?"xNBA".*?"pxelinux".*?"PXELINUX"/s, 'diagnostics recognizes xNBA and PXELINUX markers' ); like( $console_source, qr/PXE \(unknown loader\)/, 'older boot configurations have an honest PXE fallback' ); like( $console_source, qr/"Identity\\n\\n".*"\\nManagement network\\n\\n".*"\\nxCAT\\n\\n".*"\\nRuntime\\n\\n"/s, 'diagnostics group identity, connection, and runtime data' ); like( $console_source, qr/(?:add_row\(&screen, LABEL_SERIAL, 4, "Serial"\)|STATUS_FIELD_SERIAL)/, 'main status includes the hardware serial' ); unlike( $console_source, qr/add_row\(&screen, LABEL_(?:RELEASE|SYSTEM|BOOT|EXTENSIONS|HARDWARE|DEBUG),/, 'inventory and maintenance controls stay off the main status' ); like( $console_source, qr/update_form\(&screen, &(?:state|view), changed\)/, 'stable status updates only the timers' ); like( $console_source, qr/if \(\+\+redraw >= 30\)/, 'serial console limits periodic full redraws' ); like( $console_source, qr/newtFormAddHotKey\(screen\.form, NEWT_KEY_F3\)/, 'F3 opens recent Genesis logs' ); like( $console_source, qr/sd_journal_open\(&journal,/, 'log view reads the journal without a subprocess' ); like( $console_source, qr/LOG_LINE_COUNT = 128/, 'log view keeps a bounded serial-friendly history' ); like( $console_source, qr/newtListbox\(1, 1, LOG_VIEW_HEIGHT, NEWT_FLAG_SCROLL\).*?newtFormSetTimer\(form, 1000\)/s, 'log view is scrollable and refreshes once per second' ); like( $console_source, qr/bool follow = true.*?NEWT_KEY_UP.*?follow = false/s, 'upward log navigation pauses following' ); like( $console_source, qr/NEWT_KEY_END.*?follow = true.*?newtListboxSetCurrentByKey/s, 'End resumes log following' ); like( $console_source, qr/newtFormAddHotKey\(screen\.form, NEWT_KEY_F12\).*?show_maintenance_shell\(\)/s, 'F12 opens the maintenance-shell confirmation' ); like( $console_source, qr/newtWinChoice\(.*?Open a root maintenance shell\?/s, 'maintenance shell requires confirmation' ); like( $console_source, qr{execl\("/usr/libexec/xcat/genesis-maintenance-shell"}, 'maintenance shell uses a fixed packaged executable' ); like( $console_source, qr/FD_CLOEXEC.*?write\(exec_error_pipe\[1\].*?exec_error_size ==/s, 'maintenance shell reports only direct launch failures' ); unlike( $console_source, qr/WEXITSTATUS/, 'maintenance shell does not reinterpret the shell exit status' ); unlike( $console_source, qr/\b(?:system|popen)\s*\(/, 'status console avoids command-string execution' ); my $network_state_service = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-network-state.service' ); like( $network_state_service, qr/^After=NetworkManager\.service$/m, 'network selection follows NetworkManager' ); like( $network_state_service, qr/^Before=xcat-genesis-network-ready\.target$/m, 'network selection gates Genesis readiness' ); my $register_service = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/xcat-genesis-register.service' ); like( $register_service, qr/^Requires=xcat-genesis-network-ready\.target xcat-genesis-extensions\.service$/m, 'registration requires Genesis network readiness' ); like( $register_service, qr/^After=xcat-genesis-network-ready\.target xcat-genesis-extensions\.service$/m, 'registration waits for extension verification' ); unlike( $register_service, qr/network-online\.target/, 'registration avoids generic network readiness' ); my $extension_recipe = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/xcat-genesis-extensions_1.0.bb' ); like( $extension_recipe, qr/^RDEPENDS:\$\{PN\} = "bash coreutils jq openssl-bin systemd xcat-genesis-init"$/m, 'extension verifier dependencies are explicit' ); my $extension_loader = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/files/genesis-sysext' ); like( $extension_loader, qr/openssl pkeyutl -verify -pubin .* -rawin/s, 'extension manifests use Ed25519 verification' ); like( $extension_loader, qr/extension architecture .* does not match/, 'extension architecture mismatches fail closed' ); like( $extension_loader, qr/armv7\*\) printf '%s\\n' armv7hf/, 'ARMv7 runtimes use the armv7hf extension identity' ); like( $extension_loader, qr/extension kernel release does not match/, 'kernel extensions require an exact release' ); like( $extension_loader, qr/systemd-sysext refresh/, 'verified extensions are merged by systemd' ); my $extension_service = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-extensions/files/xcat-genesis-extensions.service' ); like( $extension_service, qr{^ConditionDirectoryNotEmpty=/var/lib/xcat/genesis/extensions$}m, 'extension loading requires staged artifacts' ); like( $extension_service, qr/^Before=xcat-genesis-register\.service$/m, 'extensions load before xCAT registration' ); my $extension_class = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/classes-recipe/xcat-genesis-extension.bbclass' ); like( $extension_class, qr/^inherit sysext-image$/m, 'extensions use the upstream systemd image class' ); like( $extension_class, qr/^IMAGE_FSTYPES = "squashfs-zst"$/m, 'extensions are immutable SquashFS images' ); like( $extension_class, qr/^XCAT_GENESIS_EXTENSION_ARCHITECTURE \?\?= "\$\{XCAT_GENESIS_ARCHITECTURE\}"$/m, 'extension manifests use the canonical machine identity' ); like( $extension_class, qr/"armv7hf"/, 'extension manifests accept the canonical ARMv7 identity' ); unlike( $extension_class, qr/"armv7"/, 'extension manifests reject the ambiguous ARMv7 identity' ); like( $extension_class, qr/XCAT_GENESIS_EXTENSION_KERNEL_RELEASE/, 'extension metadata supports kernel ABI pinning' ); like( $extension_class, qr/Restricted Genesis extensions must set LICENSE_FLAGS/, 'restricted extensions require explicit license acceptance' ); like( $extension_class, qr/"sha256":/, 'extension manifests record the image digest' ); my $smoke_extension = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-extensions/images/xcat-genesis-extension-smoke.bb' ); like( $smoke_extension, qr/^XCAT_GENESIS_EXTENSION_NAME = "xcat-smoke"$/m, 'open smoke extension exercises the build path' ); like( $smoke_extension, qr/^COMPATIBLE_HOST = "x86_64\.\*-linux"$/m, 'smoke extension is restricted to its declared architecture' ); my $preset = read_file( 'xCAT-genesis-builder/oe/meta-xcat-genesis/recipes-core/xcat-genesis-init/files/00-xcat-genesis.preset' ); is( $preset, "disable getty\@.service\n", 'preset disables the unused virtual-terminal getty' ); done_testing();