#!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0+ """ Test for hwids-to-dtsi.py script Validates that the HWIDS to devicetree conversion script correctly parses hardware ID files and generates proper devicetree-source output """ import os import sys import tempfile import unittest import uuid from io import StringIO # Add the scripts directory to the path script_dir = os.path.join(os.path.dirname(__file__), '..', '..', 'scripts') sys.path.insert(0, script_dir) # pylint: disable=wrong-import-position,import-error from hwids_to_dtsi import ( load_compatible_map, parse_hwids_file, generate_dtsi, parse_variant_description, VERSION_FIELDS, HEX_ENCLOSURE_FIELD, _finalise_combined_dtsi ) class TestHwidsToDeviceTree(unittest.TestCase): """Test cases for HWIDS to devicetree conversion""" def setUp(self): """Set up test fixtures""" self.test_hwids_content = """Computer Information -------------------- BiosVendor: Insyde Corp. BiosVersion: V1.24 BiosMajorRelease: 0 BiosMinorRelease: 0 FirmwareMajorRelease: 01 FirmwareMinorRelease: 15 Manufacturer: Acer Family: Swift 14 AI ProductName: Swift SF14-11 ProductSku: EnclosureKind: a BaseboardManufacturer: SX1 BaseboardProduct: Bluetang_SX1 Hardware IDs ------------ {27d2dba8-e6f1-5c19-ba1c-c25a4744c161} <- Manufacturer + Family + ProductName + ProductSku + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease {676172cd-d185-53ed-aac6-245d0caa02c4} <- Manufacturer + Family + ProductName + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease {20c2cf2f-231c-5d02-ae9b-c837ab5653ed} <- Manufacturer + ProductName + BiosVendor + BiosVersion + BiosMajorRelease + BiosMinorRelease {f2ea7095-999d-5e5b-8f2a-4b636a1e399f} <- Manufacturer + Family + ProductName + ProductSku + BaseboardManufacturer + BaseboardProduct {331d7526-8b88-5923-bf98-450cf3ea82a4} <- Manufacturer + Family + ProductName + ProductSku {98ad068a-f812-5f13-920c-3ff3d34d263f} <- Manufacturer + Family + ProductName {3f49141c-d8fb-5a6f-8b4a-074a2397874d} <- Manufacturer + ProductSku + BaseboardManufacturer + BaseboardProduct {7c107a7f-2d77-51aa-aef8-8d777e26ffbc} <- Manufacturer + ProductSku {6a12c9bc-bcfa-5448-9f66-4159dbe8c326} <- Manufacturer + ProductName + BaseboardManufacturer + BaseboardProduct {f55122fb-303f-58bc-b342-6ef653956d1d} <- Manufacturer + ProductName {ee8fa049-e5f4-51e4-89d8-89a0140b8f38} <- Manufacturer + Family + BaseboardManufacturer + BaseboardProduct {4cdff732-fd0c-5bac-b33e-9002788ea557} <- Manufacturer + Family {92dcc94d-48f7-5ee8-b9ec-a6393fb7a484} <- Manufacturer + EnclosureKind {32f83b0f-1fad-5be2-88be-5ab020e7a70e} <- Manufacturer + BaseboardManufacturer + BaseboardProduct {1e301734-5d49-5df4-9ed2-aa1c0a9dddda} <- Manufacturer Extra Hardware IDs ------------------ {058c0739-1843-5a10-bab7-fae8aaf30add} <- Manufacturer + Family + ProductName + ProductSku + BiosVendor {100917f4-9c0a-5ac3-a297-794222da9bc9} <- Manufacturer + Family + ProductName + BiosVendor {86654360-65f0-5935-bc87-81102c6a022b} <- Manufacturer + BiosVendor """ self.test_compatible_map = """# SPDX-License-Identifier: GPL-2.0+ # compatible map test-device: test,example-device """ def test_parse_hwids_file(self): """Test parsing of HWIDS file content""" with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as outf: outf.write(self.test_hwids_content) outf.flush() try: info, hardware_ids = parse_hwids_file(outf.name) expected_info = { 'BiosVendor': 'Insyde Corp.', 'BiosVersion': 'V1.24', 'BiosMajorRelease': '0', 'BiosMinorRelease': '0', 'FirmwareMajorRelease': '01', 'FirmwareMinorRelease': '15', 'Manufacturer': 'Acer', 'Family': 'Swift 14 AI', 'ProductName': 'Swift SF14-11', 'ProductSku': '', 'EnclosureKind': 'a', 'BaseboardManufacturer': 'SX1', 'BaseboardProduct': 'Bluetang_SX1' } self.assertEqual(info, expected_info) # Check hardware IDs (now tuples with variant info and bitmask) expected_ids = [ # Variant 0: All main fields ('27d2dba8-e6f1-5c19-ba1c-c25a4744c161', 0, 0x3cf), # Variant 1: Without SKU ('676172cd-d185-53ed-aac6-245d0caa02c4', 1, 0x3c7), # Variant 2: Without family ('20c2cf2f-231c-5d02-ae9b-c837ab5653ed', 2, 0x3c5), # Variant 3: With baseboard, no BIOS version ('f2ea7095-999d-5e5b-8f2a-4b636a1e399f', 3, 0x3f), # Variant 4: Basic product ID ('331d7526-8b88-5923-bf98-450cf3ea82a4', 4, 0xf), # Variant 5: Without SKU ('98ad068a-f812-5f13-920c-3ff3d34d263f', 5, 0x7), # Variant 6: SKU with baseboard ('3f49141c-d8fb-5a6f-8b4a-074a2397874d', 6, 0x39), # Variant 7: Manufacturer and SKU ('7c107a7f-2d77-51aa-aef8-8d777e26ffbc', 7, 0x9), # Variant 8: Product name with baseboard ('6a12c9bc-bcfa-5448-9f66-4159dbe8c326', 8, 0x35), # Variant 9: Manufacturer and product name ('f55122fb-303f-58bc-b342-6ef653956d1d', 9, 0x5), # Variant 10: Family with baseboard ('ee8fa049-e5f4-51e4-89d8-89a0140b8f38', 10, 0x33), # Variant 11: Manufacturer and family ('4cdff732-fd0c-5bac-b33e-9002788ea557', 11, 0x3), # Variant 12: Manufacturer and enclosure ('92dcc94d-48f7-5ee8-b9ec-a6393fb7a484', 12, 0x401), # Variant 13: Manufacturer with baseboard ('32f83b0f-1fad-5be2-88be-5ab020e7a70e', 13, 0x31), # Variant 14: Manufacturer only ('1e301734-5d49-5df4-9ed2-aa1c0a9dddda', 14, 0x1), # Extra Hardware IDs (non-standard variants) # Extra: Manufacturer + Family + ProductName + ProductSku + BiosVendor ('058c0739-1843-5a10-bab7-fae8aaf30add', None, 0x4f), # Extra: Manufacturer + Family + ProductName + BiosVendor ('100917f4-9c0a-5ac3-a297-794222da9bc9', None, 0x47), # Extra: Manufacturer + BiosVendor ('86654360-65f0-5935-bc87-81102c6a022b', None, 0x41), ] self.assertEqual(hardware_ids, expected_ids) finally: os.unlink(outf.name) def test_load_compatible_map(self): """Test loading compatible string mapping""" with tempfile.TemporaryDirectory() as tmpdir: map_file = os.path.join(tmpdir, 'compatible-map') with open(map_file, 'w', encoding='utf-8') as f: f.write(self.test_compatible_map) compatible_map = load_compatible_map(tmpdir) self.assertEqual(compatible_map['test-device'], 'test,example-device') def test_guid_to_binary(self): """Test GUID to binary conversion""" test_guid = '810e34c6-cc69-5e36-8675-2f6e354272d3' guid_obj = uuid.UUID(test_guid) binary_data = guid_obj.bytes # Should be 16 bytes self.assertEqual(len(binary_data), 16) # Test known conversion (raw bytes in string order) expected = bytearray([ 0x81, 0x0e, 0x34, 0xc6, # time_low (raw bytes) 0xcc, 0x69, # time_mid (raw bytes) 0x5e, 0x36, # time_hi (raw bytes) 0x86, 0x75, # clock_seq (raw bytes) 0x2f, 0x6e, 0x35, 0x42, 0x72, 0xd3 # node (raw bytes) ]) self.assertEqual(binary_data, bytes(expected)) def test_generate_dtsi(self): """Test devicetree source generation""" info = { 'Manufacturer': 'LENOVO', 'ProductName': '21BXCTO1WW', 'BiosMajorRelease': '1', 'EnclosureKind': 'a' } hardware_ids = [('810e34c6-cc69-5e36-8675-2f6e354272d3', 0, 0x3cf)] content = generate_dtsi('test-device', 'test,example-device', info, hardware_ids) self.assertIn('// SPDX-License-Identifier: GPL-2.0+', content) self.assertIn('test-device {', content) self.assertIn('compatible = "test,example-device";', content) self.assertIn('manufacturer = "LENOVO";', content) self.assertIn('product-name = "21BXCTO1WW";', content) self.assertIn('bios-major-release = <1>;', content) self.assertIn('enclosure-kind = <0xa>;', content) self.assertIn('// Hardware IDs (CHIDs)', content) self.assertIn('hardware-id-00 {', content) self.assertIn('variant = <0>;', content) self.assertIn('fields = <0x3cf>;', content) self.assertIn( 'chid = [81 0e 34 c6 cc 69 5e 36 86 75 2f 6e 35 42 72 d3];', content) def test_invalid_guid_format(self): """Test error handling for invalid GUID format""" with self.assertRaises(ValueError): uuid.UUID('invalid-guid-format') def test_missing_compatible_map(self): """Test behavior when compatible-map file is missing""" with tempfile.TemporaryDirectory() as tmpdir: compatible_map = load_compatible_map(tmpdir) self.assertEqual(compatible_map, {}) def test_enclosure_kind_conversion(self): """Test enclosure kind hex conversion""" info = {'EnclosureKind': 'a'} hardware_ids = [] content = generate_dtsi('test', 'test,device', info, hardware_ids) self.assertIn('enclosure-kind = <0xa>;', content) # Test numeric enclosure kind ('10' is interpreted as hex 0x10) info = {'EnclosureKind': '10'} content = generate_dtsi('test', 'test,device', info, hardware_ids) self.assertIn('enclosure-kind = <0x10>;', content) def test_empty_hardware_ids(self): """Test handling of empty hardware IDs list""" info = {'Manufacturer': 'TEST'} hardware_ids = [] content = generate_dtsi('test', 'test,device', info, hardware_ids) self.assertIn('// Hardware IDs (CHIDs)', content) # Should have no hardware-id-XX or extra-X nodes self.assertNotIn('hardware-id-', content) self.assertNotIn('extra-', content) def test_parse_variant_from_field_description(self): """Test parsing variant ID from field descriptions""" # Test variant 0 - most specific desc = ('Manufacturer + Family + ProductName + ProductSku + ' 'BiosVendor + BiosVersion + BiosMajorRelease + ' 'BiosMinorRelease') variant_id, fields_mask = parse_variant_description(desc) self.assertEqual(variant_id, 0) self.assertEqual(fields_mask, 0x3cf) # Test variant 14 - least specific (manufacturer only) desc = 'Manufacturer' variant_id, fields_mask = parse_variant_description(desc) self.assertEqual(variant_id, 14) self.assertEqual(fields_mask, 0x1) # Test variant 5 - manufacturer, family, product name desc = 'Manufacturer + Family + ProductName' variant_id, fields_mask = parse_variant_description(desc) self.assertEqual(variant_id, 5) self.assertEqual(fields_mask, 0x7) def test_constants_usage(self): """Test that magic number constants are used correctly""" # Test GUID_LENGTH constant in regex pattern test_guid = '12345678-1234-5678-9abc-123456789abc' content = f'''Computer Information -------------------- Manufacturer: Test Hardware IDs ------------ {{{test_guid}}} <- Manufacturer ''' with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f: f.write(content) f.flush() try: _info, hardware_ids = parse_hwids_file(f.name) # Should successfully parse the GUID self.assertEqual(len(hardware_ids), 1) self.assertEqual(hardware_ids[0][0], test_guid) finally: os.unlink(f.name) def test_version_fields_constants(self): """Test that VERSION_FIELDS constant is used correctly""" # Test that all expected version fields are in the constant expected_fields = {'BiosMajorRelease', 'BiosMinorRelease', 'FirmwareMajorRelease', 'FirmwareMinorRelease'} self.assertTrue(expected_fields.issubset(VERSION_FIELDS)) # Test HEX_ENCLOSURE_FIELD constant self.assertEqual(HEX_ENCLOSURE_FIELD, 'EnclosureKind') if __name__ == '__main__': unittest.main()