Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added tests for IOMMU config (New) #1715

Merged
merged 9 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions providers/base/bin/kernel_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
# This file is part of Checkbox.
#
# Copyright 2025 Canonical Ltd.
# Authors:
# Fernando Bravo <[email protected]>
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.
#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
import argparse
import os
import shutil
from packaging import version

from checkbox_support.snap_utils.system import get_kernel_snap
from checkbox_support.snap_utils.system import on_ubuntucore


def get_kernel_config_path():
"""Retrieve the path to the kernel configuration file."""
kernel_version = os.uname().release
if on_ubuntucore():
kernel_snap = get_kernel_snap()
return "/snap/{}/current/config-{}".format(kernel_snap, kernel_version)
return "/boot/config-{}".format(kernel_version)


def get_configuration(output=None):
"""Retrieve and optionally save the kernel configuration."""
kernel_config_path = get_kernel_config_path()
if output:
shutil.copy2(kernel_config_path, output)
else:
with open(kernel_config_path) as config:
print(config.read())


def check_flag(flag, min_version):
"""Check if a specific flag is true in the kernel configuration starting
from a specific version."""

version_parts = os.uname().release.split("-")
kernel_version = "-".join(version_parts[:2])
if min_version and version.parse(kernel_version) < version.parse(
min_version
):
print(
"Kernel version is {}.".format(kernel_version),
"Versions lower than {} don't require ".format(min_version),
"the flag {} to be set.".format(flag),
)
return

kernel_config_path = get_kernel_config_path()
with open(kernel_config_path) as config:
# Check the header and ignore arm architecture configurations
lines = config.readlines()

# Look for the flag in the configuration
for line in lines:
line = line.strip()
if line.startswith("#"):
continue # Ignore commented lines
if "{}=y".format(flag) in line:
print("Flag {} is present and set to 'y'.".format(flag))
return

raise SystemExit("Flag {} not found in the kernel config.".format(flag))


def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"--output", "-o", help="Output file to store the kernel configuration"
)
parser.add_argument(
"--config-flag",
"-c",
help="Check if a specific flag is present in the configuration",
)
parser.add_argument(
"--min-version",
"-m",
help="Minimum kernel version required to check the flag",
)
return parser.parse_args()


def main():
args = parse_args()
if args.config_flag:
check_flag(args.config_flag, args.min_version)
else:
get_configuration(args.output)


if __name__ == "__main__":
main()
212 changes: 212 additions & 0 deletions providers/base/tests/test_kernel_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
# This file is part of Checkbox.
#
# Copyright 2025 Canonical Ltd.
# Authors:
# Fernando Bravo <[email protected]>
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.
#
# Checkbox is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.

from unittest import TestCase
from unittest.mock import patch, MagicMock, mock_open, call

from kernel_config import (
get_kernel_config_path,
get_configuration,
check_flag,
parse_args,
main,
)


class TestKernelConfig(TestCase):
@patch("kernel_config.on_ubuntucore")
@patch("kernel_config.os.uname")
def test_get_kernel_config_path(self, uname_mock, on_core_mock):
on_core_mock.return_value = False
uname_mock.return_value = MagicMock(release="5.4.0-42-generic")

self.assertEqual(
get_kernel_config_path(), "/boot/config-5.4.0-42-generic"
)

@patch("kernel_config.get_kernel_snap")
@patch("kernel_config.on_ubuntucore")
@patch("kernel_config.os.uname")
def test_get_kernel_config_path_ubuntucore(
self, uname_mock, on_core_mock, get_kernel_mock
):
on_core_mock.return_value = True
get_kernel_mock.return_value = "pc-kernel"
uname_mock.return_value = MagicMock(release="5.4.0-42-generic")

self.assertEqual(
get_kernel_config_path(),
"/snap/pc-kernel/current/config-5.4.0-42-generic",
)

@patch("kernel_config.get_kernel_config_path")
@patch("kernel_config.shutil.copy2")
@patch("kernel_config.open", MagicMock())
@patch("kernel_config.print", MagicMock())
def test_get_configuration_output(self, copy2_mock, get_path_mock):
get_path_mock.return_value = "/boot/config-5.4.0-42-generic"
get_configuration("/tmp/config")
copy2_mock.assert_called_once_with(
"/boot/config-5.4.0-42-generic", "/tmp/config"
)

@patch("kernel_config.get_kernel_config_path")
@patch("kernel_config.print")
@patch("kernel_config.shutil.copy2", MagicMock())
def test_get_configuration_print(self, print_mock, get_path_mock):
get_path_mock.return_value = "/boot/config-5.4.0-42-generic"
data = "CONFIG_INTEL_IOMMU=y\nCONFIG_INTEL_IOMMU_DEFAULT_ON=y"
with patch("builtins.open", mock_open(read_data=data)):
get_configuration()
print_mock.assert_called_once_with(data)

@patch("kernel_config.os.uname")
@patch("kernel_config.print")
def test_check_flag_lower_version(self, print_mock, uname_mock):
uname_mock.return_value = MagicMock(release="5.4.0-42-generic")
check_flag("CONFIG_INTEL_IOMMU_DEFAULT_ON", "6.8.0-20")

uname_mock.return_value = MagicMock(release="5.4.0-42-intel-iotg")
check_flag("CONFIG_INTEL_IOMMU_DEFAULT_ON", "6.8.0-20")

uname_mock.return_value = MagicMock(release="6.8.0-11-generic")
check_flag("CONFIG_INTEL_IOMMU_DEFAULT_ON", "6.8.0-20")

print_mock.assert_has_calls(
[
call(
"Kernel version is 5.4.0-42.",
"Versions lower than 6.8.0-20 don't require ",
"the flag CONFIG_INTEL_IOMMU_DEFAULT_ON to be set.",
),
call(
"Kernel version is 5.4.0-42.",
"Versions lower than 6.8.0-20 don't require ",
"the flag CONFIG_INTEL_IOMMU_DEFAULT_ON to be set.",
),
call(
"Kernel version is 6.8.0-11.",
"Versions lower than 6.8.0-20 don't require ",
"the flag CONFIG_INTEL_IOMMU_DEFAULT_ON to be set.",
),
]
)

@patch("kernel_config.os.uname")
@patch("kernel_config.print")
def test_check_flag_present(self, print_mock, uname_mock):
uname_mock.return_value = MagicMock(release="6.8.0-45-generic")
data = (
"#\n"
"# Automatically generated file; DO NOT EDIT.\n"
"# Linux/x86 6.8.12 Kernel Configuration\n"
"#\n"
"CONFIG_INTEL_IOMMU=y\n"
"CONFIG_INTEL_IOMMU_DEFAULT_ON=y\n"
)
with patch("builtins.open", mock_open(read_data=data)):
check_flag("CONFIG_INTEL_IOMMU_DEFAULT_ON", "6.8.0-20")
print_mock.assert_called_once_with(
"Flag CONFIG_INTEL_IOMMU_DEFAULT_ON is present and set to 'y'."
)

@patch("kernel_config.os.uname")
@patch("kernel_config.print", MagicMock())
def test_check_flag_not_present(self, uname_mock):
uname_mock.return_value = MagicMock(release="6.8.0-45-generic")
data = (
"#\n"
"# Automatically generated file; DO NOT EDIT.\n"
"# Linux/x86 6.8.12 Kernel Configuration\n"
"#\n"
"CONFIG_INTEL_IOMMU=y\n"
)
with patch("builtins.open", mock_open(read_data=data)):
with self.assertRaises(SystemExit) as context:
check_flag("CONFIG_INTEL_IOMMU_DEFAULT_ON", "6.8.0-20")
self.assertEqual(
str(context.exception),
"Flag CONFIG_INTEL_IOMMU_DEFAULT_ON not found in the kernel config.",
)

@patch("kernel_config.os.uname")
@patch("kernel_config.print", MagicMock())
def test_check_flag_commented(self, uname_mock):
uname_mock.return_value = MagicMock(release="6.8.0-45-generic")
data = (
"#\n"
"# Automatically generated file; DO NOT EDIT.\n"
"# Linux/x86 6.8.12 Kernel Configuration\n"
"#\n"
"# CONFIG_INTEL_IOMMU_DEFAULT_ON=y\n"
)
with patch("builtins.open", mock_open(read_data=data)):
with self.assertRaises(SystemExit) as context:
check_flag("CONFIG_INTEL_IOMMU_DEFAULT_ON", "6.8.0-20")
self.assertEqual(
str(context.exception),
"Flag CONFIG_INTEL_IOMMU_DEFAULT_ON not found in the kernel config.",
)

@patch("kernel_config.argparse.ArgumentParser.parse_args")
def test_parse_args(self, parse_args_mock):
parse_args_mock.return_value = MagicMock(
output="/tmp/config",
config_flag="CONFIG_INTEL_IOMMU",
min_version="6.8.0-20",
)

args = parse_args()
self.assertEqual(args.output, "/tmp/config")
self.assertEqual(args.config_flag, "CONFIG_INTEL_IOMMU")
self.assertEqual(args.min_version, "6.8.0-20")

@patch("kernel_config.get_configuration")
@patch("kernel_config.check_flag")
@patch("kernel_config.parse_args")
def test_main_check_flag(
self, parse_args_mock, check_flag_mock, get_config_mock
):
parse_args_mock.return_value = MagicMock(
output=None,
config_flag="CONFIG_INTEL_IOMMU",
min_version="6.8.0-20",
)

main()
check_flag_mock.assert_called_once_with(
"CONFIG_INTEL_IOMMU", "6.8.0-20"
)
get_config_mock.assert_not_called()

@patch("kernel_config.get_configuration")
@patch("kernel_config.check_flag")
@patch("kernel_config.parse_args")
def test_main_get_configuration(
self, parse_args_mock, check_flag_mock, get_config_mock
):
parse_args_mock.return_value = MagicMock(
output="/tmp/config",
config_flag=None,
min_version=None,
)

main()
get_config_mock.assert_called_once_with("/tmp/config")
check_flag_mock.assert_not_called()
30 changes: 30 additions & 0 deletions providers/base/units/info/jobs.pxu
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,36 @@ estimated_duration: 0.005
_purpose: Attaches the kernel command line used to boot
_summary: Attach a copy of /proc/cmdline

id: info/kernel_config
plugin: shell
category_id: com.canonical.plainbox::info
command:
kernel_config.py --output "$PLAINBOX_SESSION_SHARE"/kernel_config
estimated_duration: 0.005
_purpose: Gathers the kernel configuration and saves it to a file
_summary: Gather the kernel configuration

id: kernel_config_attachment
plugin: attachment
depends: info/kernel_config
category_id: com.canonical.plainbox::info
command:
[ -f "$PLAINBOX_SESSION_SHARE"/kernel_config ] && cat "$PLAINBOX_SESSION_SHARE"/kernel_config
estimated_duration: 0.005
_purpose: Attaches the kernel configuration
_summary: Attach a copy of the kernel configuration

id: info/kernel-config-iommu-flag
plugin: shell
requires: cpuinfo.platform in ("i386", "x86_64")
category_id: com.canonical.plainbox::info
command:
kernel_config.py --config-flag CONFIG_INTEL_IOMMU_DEFAULT_ON --min-version 6.8.0-20
estimated_duration: 0.005
_purpose: Checks the value of the CONFIG_INTEL_IOMMU_DEFAULT_ON flag in the kernel configuration
_summary: Check if the kernel is compiled with IOMMU support


plugin: shell
category_id: com.canonical.plainbox::info
id: info/systemd-analyze
Expand Down
1 change: 1 addition & 0 deletions providers/base/units/miscellanea/jobs.pxu
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ depends:
lspci_standard_config_json
system_info_json
kernel_cmdline_attachment
kernel_config_attachment
estimated_duration: 1.0
command: true
_summary:
Expand Down
1 change: 1 addition & 0 deletions providers/base/units/submission/test-plan.pxu
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ mandatory_include:
miscellanea/submission-resources
info/systemd-analyze certification-status=non-blocker
info/systemd-analyze-critical-chain certification-status=non-blocker
info/kernel-config-iommu-flag certification-status=non-blocker
Loading