diff --git a/checkbox-ng/checkbox_ng/support/lib/dmi.py b/checkbox-ng/checkbox_ng/support/lib/dmi.py index 75d1b81856..54f2d83035 100644 --- a/checkbox-ng/checkbox_ng/support/lib/dmi.py +++ b/checkbox-ng/checkbox_ng/support/lib/dmi.py @@ -19,47 +19,50 @@ from checkbox_ng.support.lib.conversion import string_to_type -# See also 3.3.4.1 of the "System Management BIOS Reference Specification, -# Version 2.6.1" (Preliminary Standard) document, available from -# http://www.dmtf.org/standards/smbios. +# See also 7.4.1 "System Enclosure or Chassis Types" of the "System Management +# BIOS Specification, Version 3.9.0" (DSP0134), available +# from https://www.dmtf.org/standards/smbios. class Dmi: - chassis = ( - ("Undefined", "unknown"), # 0x00 - ("Other", "unknown"), - ("Unknown", "unknown"), - ("Desktop", "desktop"), - ("Low Profile Desktop", "desktop"), - ("Pizza Box", "server"), - ("Mini Tower", "desktop"), - ("Tower", "desktop"), - ("Portable", "laptop"), - ("Laptop", "laptop"), - ("Notebook", "laptop"), - ("Hand Held", "handheld"), - ("Docking Station", "laptop"), - ("All In One", "unknown"), - ("Sub Notebook", "laptop"), - ("Space-saving", "desktop"), - ("Lunch Box", "unknown"), - ("Main Server Chassis", "server"), - ("Expansion Chassis", "unknown"), - ("Sub Chassis", "unknown"), - ("Bus Expansion Chassis", "unknown"), - ("Peripheral Chassis", "unknown"), - ("RAID Chassis", "unknown"), - ("Rack Mount Chassis", "unknown"), - ("Sealed-case PC", "unknown"), - ("Multi-system", "unknown"), - ("CompactPCI", "unknonw"), - ("AdvancedTCA", "unknown"), - ("Blade", "server"), - ("Blade Enclosure", "unknown"), + chassis_types = ( + "Undefined", # 0x00 placeholder for SMBIOS alignment + "Other", + "Unknown", + "Desktop", + "Low Profile Desktop", + "Pizza Box", + "Mini Tower", + "Tower", + "Portable", + "Laptop", + "Notebook", + "Hand Held", + "Docking Station", + "All In One", + "Sub Notebook", + "Space-saving", + "Lunch Box", + "Main Server Chassis", + "Expansion Chassis", + "Sub Chassis", + "Bus Expansion Chassis", + "Peripheral Chassis", + "RAID Chassis", + "Rack Mount Chassis", + "Sealed-case PC", + "Multi-system", + "Compact PCI", + "Advanced TCA", + "Blade", + "Blade Enclosure", + "Tablet", + "Convertible", + "Detachable", + "IoT Gateway", + "Embedded PC", + "Mini PC", + "Stick PC", ) - chassis_names = tuple(c[0] for c in chassis) - chassis_types = tuple(c[1] for c in chassis) - chassis_name_to_type = dict(chassis) - type_names = ( "BIOS", # 0x00 "System", @@ -185,9 +188,11 @@ def product(self): type_string = self._attributes.get("chassis_type", "0") try: type_index = int(type_string) - return Dmi.chassis_names[type_index] except ValueError: return type_string + if 0 <= type_index < len(Dmi.chassis_types): + return Dmi.chassis_types[type_index] + return "Unknown" for name in "name", "version": attribute = "%s_%s" % (self.category.lower(), name) diff --git a/checkbox-ng/checkbox_ng/support/parsers/tests/test_dmidecode.py b/checkbox-ng/checkbox_ng/support/parsers/tests/test_dmidecode.py index 354d9ae975..7a5337709e 100644 --- a/checkbox-ng/checkbox_ng/support/parsers/tests/test_dmidecode.py +++ b/checkbox-ng/checkbox_ng/support/parsers/tests/test_dmidecode.py @@ -33,6 +33,7 @@ def resource_filename(name, path): except ImportError: from pkg_resources import resource_filename +from checkbox_ng.support.lib.dmi import Dmi, DmiDevice from checkbox_ng.support.parsers.dmidecode import DmidecodeParser from checkbox_ng.support.parsers.tests.test_dmi import TestDmiMixin, DmiResult @@ -206,3 +207,17 @@ def test_system_product_name(self): "family": "System X", } self.assertDictEqual(dmi_device.raw_attributes, correct_values) + + +class TestChassisProduct(TestCase): + def make_chassis(self, chassis_type): + return DmiDevice({"chassis_type": chassis_type}, "CHASSIS") + + def test_out_of_range_index(self): + """Test that an index beyond the known list returns unknown.""" + index = str(len(Dmi.chassis_types)) + self.assertEqual(self.make_chassis(index).product, "Unknown") + + def test_non_numeric_type(self): + """Test that a non-numeric type returns the type string.""" + self.assertEqual(self.make_chassis("Notebook").product, "Notebook") diff --git a/checkbox-ng/plainbox/impl/providers/exporters/data/checkbox.json b/checkbox-ng/plainbox/impl/providers/exporters/data/checkbox.json index aba3ca3f20..986a7eb825 100644 --- a/checkbox-ng/plainbox/impl/providers/exporters/data/checkbox.json +++ b/checkbox-ng/plainbox/impl/providers/exporters/data/checkbox.json @@ -49,6 +49,12 @@ "{{ key }}": "{{ lsb_resource[key] }}"{%- if not loop.last -%},{%- endif %} {%- endfor %} }, +{%- endif %} +{%- if ns ~ 'dmi' in state.resource_map %} +{%- set chassis_resource = state.resource_map[ns ~ 'dmi'] | selectattr('chassis') | first %} +{%- if chassis_resource %} + "chassis": "{{ chassis_resource.chassis }}", +{%- endif %} {%- endif %} "results": [ {%- for job_id, job_state in job_state_map|dictsort if job_state.result.outcome != None and job_state.job.plugin != "attachment" %} diff --git a/providers/resource/bin/dmi_resource.py b/providers/resource/bin/dmi_resource.py index 80babd74d4..9fa29e175a 100755 --- a/providers/resource/bin/dmi_resource.py +++ b/providers/resource/bin/dmi_resource.py @@ -104,6 +104,8 @@ def addDmiDevice(self, device): if attribute == "product" and value: print("{}: {}".format("sane_product", sane_product(value))) print("{}: {}".format("display_type", display_type(value))) + if device.category == "CHASSIS": + print("{}: {}".format("chassis", value)) print() diff --git a/providers/resource/tests/test_dmi_resource.py b/providers/resource/tests/test_dmi_resource.py index a0dfb3974b..1145bf0ec2 100644 --- a/providers/resource/tests/test_dmi_resource.py +++ b/providers/resource/tests/test_dmi_resource.py @@ -17,9 +17,12 @@ # You should have received a copy of the GNU General Public License # along with Checkbox. If not, see . -import unittest from unittest import TestCase -from unittest.mock import MagicMock + +import contextlib +import io + +from checkbox_ng.support.lib.dmi import DmiDevice import dmi_resource @@ -86,3 +89,21 @@ def test_display_type_unknown(self): products = ["strange-iot-product"] category = set(map(dmi_resource.display_type, products)) self.assertEqual(category, {"external"}) + + +class TestDmiResultChassis(TestCase): + def output_for(self, device): + stream = io.StringIO() + with contextlib.redirect_stdout(stream): + dmi_resource.DmiResult().addDmiDevice(device) + return stream.getvalue() + + def test_chassis_device_outputs_mapped_chassis_type(self): + """Test that a CHASSIS device outputs its mapped chassis type.""" + device = DmiDevice({"chassis_type": "10"}, "CHASSIS") + self.assertIn("chassis: Notebook", self.output_for(device)) + + def test_chassis_field_only_outputs_for_chassis_devices(self): + """Test that the chassis field is only included for CHASSIS devices.""" + device = DmiDevice({"system_name": "20AMOS3"}, "SYSTEM") + self.assertNotIn("chassis:", self.output_for(device)) diff --git a/submission-schema/schema.json b/submission-schema/schema.json index 17c35f2267..4575979cf1 100644 --- a/submission-schema/schema.json +++ b/submission-schema/schema.json @@ -218,6 +218,9 @@ }, "buildstamp": { "type": "string" + }, + "chassis": { + "type": "string" } }, "required": [