1#!/usr/bin/python 2# SPDX-License-Identifier: GPL-2.0+ 3# Copyright (c) 2018 Google, Inc 4# Written by Simon Glass <sjg@chromium.org> 5# 6 7from optparse import OptionParser 8import glob 9import os 10import sys 11import unittest 12 13# Bring in the patman libraries 14our_path = os.path.dirname(os.path.realpath(__file__)) 15for dirname in ['../patman', '..']: 16 sys.path.insert(0, os.path.join(our_path, dirname)) 17 18import command 19import fdt 20from fdt import TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL 21import fdt_util 22from fdt_util import fdt32_to_cpu 23import libfdt 24import test_util 25import tools 26 27def _GetPropertyValue(dtb, node, prop_name): 28 """Low-level function to get the property value based on its offset 29 30 This looks directly in the device tree at the property's offset to find 31 its value. It is useful as a check that the property is in the correct 32 place. 33 34 Args: 35 node: Node to look in 36 prop_name: Property name to find 37 38 Returns: 39 Tuple: 40 Prop object found 41 Value of property as a string (found using property offset) 42 """ 43 prop = node.props[prop_name] 44 45 # Add 12, which is sizeof(struct fdt_property), to get to start of data 46 offset = prop.GetOffset() + 12 47 data = dtb.GetContents()[offset:offset + len(prop.value)] 48 return prop, [chr(x) for x in data] 49 50 51class TestFdt(unittest.TestCase): 52 """Tests for the Fdt module 53 54 This includes unit tests for some functions and functional tests for the fdt 55 module. 56 """ 57 @classmethod 58 def setUpClass(cls): 59 tools.PrepareOutputDir(None) 60 61 @classmethod 62 def tearDownClass(cls): 63 tools._FinaliseForTest() 64 65 def setUp(self): 66 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 67 68 def testFdt(self): 69 """Test that we can open an Fdt""" 70 self.dtb.Scan() 71 root = self.dtb.GetRoot() 72 self.assertTrue(isinstance(root, fdt.Node)) 73 74 def testGetNode(self): 75 """Test the GetNode() method""" 76 node = self.dtb.GetNode('/spl-test') 77 self.assertTrue(isinstance(node, fdt.Node)) 78 node = self.dtb.GetNode('/i2c@0/pmic@9') 79 self.assertTrue(isinstance(node, fdt.Node)) 80 self.assertEqual('pmic@9', node.name) 81 self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing')) 82 83 def testFlush(self): 84 """Check that we can flush the device tree out to its file""" 85 fname = self.dtb._fname 86 with open(fname) as fd: 87 data = fd.read() 88 os.remove(fname) 89 with self.assertRaises(IOError): 90 open(fname) 91 self.dtb.Flush() 92 with open(fname) as fd: 93 data = fd.read() 94 95 def testPack(self): 96 """Test that packing a device tree works""" 97 self.dtb.Pack() 98 99 def testGetFdt(self): 100 """Tetst that we can access the raw device-tree data""" 101 self.assertTrue(isinstance(self.dtb.GetContents(), bytearray)) 102 103 def testGetProps(self): 104 """Tests obtaining a list of properties""" 105 node = self.dtb.GetNode('/spl-test') 106 props = self.dtb.GetProps(node) 107 self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible', 108 'intarray', 'intval', 'longbytearray', 'notstring', 109 'stringarray', 'stringval', 'u-boot,dm-pre-reloc'], 110 sorted(props.keys())) 111 112 def testCheckError(self): 113 """Tests the ChecKError() function""" 114 with self.assertRaises(ValueError) as e: 115 fdt.CheckErr(-libfdt.NOTFOUND, 'hello') 116 self.assertIn('FDT_ERR_NOTFOUND: hello', str(e.exception)) 117 118 def testGetFdt(self): 119 node = self.dtb.GetNode('/spl-test') 120 self.assertEqual(self.dtb, node.GetFdt()) 121 122class TestNode(unittest.TestCase): 123 """Test operation of the Node class""" 124 125 @classmethod 126 def setUpClass(cls): 127 tools.PrepareOutputDir(None) 128 129 @classmethod 130 def tearDownClass(cls): 131 tools._FinaliseForTest() 132 133 def setUp(self): 134 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 135 self.node = self.dtb.GetNode('/spl-test') 136 137 def testOffset(self): 138 """Tests that we can obtain the offset of a node""" 139 self.assertTrue(self.node.Offset() > 0) 140 141 def testDelete(self): 142 """Tests that we can delete a property""" 143 node2 = self.dtb.GetNode('/spl-test2') 144 offset1 = node2.Offset() 145 self.node.DeleteProp('intval') 146 offset2 = node2.Offset() 147 self.assertTrue(offset2 < offset1) 148 self.node.DeleteProp('intarray') 149 offset3 = node2.Offset() 150 self.assertTrue(offset3 < offset2) 151 with self.assertRaises(libfdt.FdtException): 152 self.node.DeleteProp('missing') 153 154 def testDeleteGetOffset(self): 155 """Test that property offset update when properties are deleted""" 156 self.node.DeleteProp('intval') 157 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray') 158 self.assertEqual(prop.value, value) 159 160 def testFindNode(self): 161 """Tests that we can find a node using the FindNode() functoin""" 162 node = self.dtb.GetRoot().FindNode('i2c@0') 163 self.assertEqual('i2c@0', node.name) 164 subnode = node.FindNode('pmic@9') 165 self.assertEqual('pmic@9', subnode.name) 166 self.assertEqual(None, node.FindNode('missing')) 167 168 def testRefreshMissingNode(self): 169 """Test refreshing offsets when an extra node is present in dtb""" 170 # Delete it from our tables, not the device tree 171 del self.dtb._root.subnodes[-1] 172 with self.assertRaises(ValueError) as e: 173 self.dtb.Refresh() 174 self.assertIn('Internal error, offset', str(e.exception)) 175 176 def testRefreshExtraNode(self): 177 """Test refreshing offsets when an expected node is missing""" 178 # Delete it from the device tre, not our tables 179 self.dtb.GetFdtObj().del_node(self.node.Offset()) 180 with self.assertRaises(ValueError) as e: 181 self.dtb.Refresh() 182 self.assertIn('Internal error, node name mismatch ' 183 'spl-test != spl-test2', str(e.exception)) 184 185 def testRefreshMissingProp(self): 186 """Test refreshing offsets when an extra property is present in dtb""" 187 # Delete it from our tables, not the device tree 188 del self.node.props['notstring'] 189 with self.assertRaises(ValueError) as e: 190 self.dtb.Refresh() 191 self.assertIn("Internal error, property 'notstring' missing, offset ", 192 str(e.exception)) 193 194 def testLookupPhandle(self): 195 """Test looking up a single phandle""" 196 dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') 197 node = dtb.GetNode('/phandle-source2') 198 prop = node.props['clocks'] 199 target = dtb.GetNode('/phandle-target') 200 self.assertEqual(target, dtb.LookupPhandle(fdt32_to_cpu(prop.value))) 201 202 203class TestProp(unittest.TestCase): 204 """Test operation of the Prop class""" 205 206 @classmethod 207 def setUpClass(cls): 208 tools.PrepareOutputDir(None) 209 210 @classmethod 211 def tearDownClass(cls): 212 tools._FinaliseForTest() 213 214 def setUp(self): 215 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 216 self.node = self.dtb.GetNode('/spl-test') 217 self.fdt = self.dtb.GetFdtObj() 218 219 def testMissingNode(self): 220 self.assertEqual(None, self.dtb.GetNode('missing')) 221 222 def testPhandle(self): 223 dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') 224 node = dtb.GetNode('/phandle-source2') 225 prop = node.props['clocks'] 226 self.assertTrue(fdt32_to_cpu(prop.value) > 0) 227 228 def _ConvertProp(self, prop_name): 229 """Helper function to look up a property in self.node and return it 230 231 Args: 232 Property name to find 233 234 Return fdt.Prop object for this property 235 """ 236 p = self.fdt.getprop(self.node.Offset(), prop_name) 237 return fdt.Prop(self.node, -1, prop_name, p) 238 239 def testMakeProp(self): 240 """Test we can convert all the the types that are supported""" 241 prop = self._ConvertProp('boolval') 242 self.assertEqual(fdt.TYPE_BOOL, prop.type) 243 self.assertEqual(True, prop.value) 244 245 prop = self._ConvertProp('intval') 246 self.assertEqual(fdt.TYPE_INT, prop.type) 247 self.assertEqual(1, fdt32_to_cpu(prop.value)) 248 249 prop = self._ConvertProp('intarray') 250 self.assertEqual(fdt.TYPE_INT, prop.type) 251 val = [fdt32_to_cpu(val) for val in prop.value] 252 self.assertEqual([2, 3, 4], val) 253 254 prop = self._ConvertProp('byteval') 255 self.assertEqual(fdt.TYPE_BYTE, prop.type) 256 self.assertEqual(5, ord(prop.value)) 257 258 prop = self._ConvertProp('longbytearray') 259 self.assertEqual(fdt.TYPE_BYTE, prop.type) 260 val = [ord(val) for val in prop.value] 261 self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val) 262 263 prop = self._ConvertProp('stringval') 264 self.assertEqual(fdt.TYPE_STRING, prop.type) 265 self.assertEqual('message', prop.value) 266 267 prop = self._ConvertProp('stringarray') 268 self.assertEqual(fdt.TYPE_STRING, prop.type) 269 self.assertEqual(['multi-word', 'message'], prop.value) 270 271 prop = self._ConvertProp('notstring') 272 self.assertEqual(fdt.TYPE_BYTE, prop.type) 273 val = [ord(val) for val in prop.value] 274 self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val) 275 276 def testGetEmpty(self): 277 """Tests the GetEmpty() function for the various supported types""" 278 self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL)) 279 self.assertEqual(chr(0), fdt.Prop.GetEmpty(fdt.TYPE_BYTE)) 280 self.assertEqual(chr(0) * 4, fdt.Prop.GetEmpty(fdt.TYPE_INT)) 281 self.assertEqual('', fdt.Prop.GetEmpty(fdt.TYPE_STRING)) 282 283 def testGetOffset(self): 284 """Test we can get the offset of a property""" 285 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray') 286 self.assertEqual(prop.value, value) 287 288 def testWiden(self): 289 """Test widening of values""" 290 node2 = self.dtb.GetNode('/spl-test2') 291 prop = self.node.props['intval'] 292 293 # No action 294 prop2 = node2.props['intval'] 295 prop.Widen(prop2) 296 self.assertEqual(fdt.TYPE_INT, prop.type) 297 self.assertEqual(1, fdt32_to_cpu(prop.value)) 298 299 # Convert singla value to array 300 prop2 = self.node.props['intarray'] 301 prop.Widen(prop2) 302 self.assertEqual(fdt.TYPE_INT, prop.type) 303 self.assertTrue(isinstance(prop.value, list)) 304 305 # A 4-byte array looks like a single integer. When widened by a longer 306 # byte array, it should turn into an array. 307 prop = self.node.props['longbytearray'] 308 prop2 = node2.props['longbytearray'] 309 self.assertFalse(isinstance(prop2.value, list)) 310 self.assertEqual(4, len(prop2.value)) 311 prop2.Widen(prop) 312 self.assertTrue(isinstance(prop2.value, list)) 313 self.assertEqual(9, len(prop2.value)) 314 315 # Similarly for a string array 316 prop = self.node.props['stringval'] 317 prop2 = node2.props['stringarray'] 318 self.assertFalse(isinstance(prop.value, list)) 319 self.assertEqual(7, len(prop.value)) 320 prop.Widen(prop2) 321 self.assertTrue(isinstance(prop.value, list)) 322 self.assertEqual(3, len(prop.value)) 323 324 # Enlarging an existing array 325 prop = self.node.props['stringarray'] 326 prop2 = node2.props['stringarray'] 327 self.assertTrue(isinstance(prop.value, list)) 328 self.assertEqual(2, len(prop.value)) 329 prop.Widen(prop2) 330 self.assertTrue(isinstance(prop.value, list)) 331 self.assertEqual(3, len(prop.value)) 332 333 def testAdd(self): 334 """Test adding properties""" 335 self.fdt.pack() 336 # This function should automatically expand the device tree 337 self.node.AddZeroProp('one') 338 self.node.AddZeroProp('two') 339 self.node.AddZeroProp('three') 340 self.dtb.Sync(auto_resize=True) 341 342 # Updating existing properties should be OK, since the device-tree size 343 # does not change 344 self.fdt.pack() 345 self.node.SetInt('one', 1) 346 self.node.SetInt('two', 2) 347 self.node.SetInt('three', 3) 348 self.dtb.Sync(auto_resize=False) 349 350 # This should fail since it would need to increase the device-tree size 351 self.node.AddZeroProp('four') 352 with self.assertRaises(libfdt.FdtException) as e: 353 self.dtb.Sync(auto_resize=False) 354 self.assertIn('FDT_ERR_NOSPACE', str(e.exception)) 355 356 def testAddNode(self): 357 self.fdt.pack() 358 self.node.AddSubnode('subnode') 359 with self.assertRaises(libfdt.FdtException) as e: 360 self.dtb.Sync(auto_resize=False) 361 self.assertIn('FDT_ERR_NOSPACE', str(e.exception)) 362 363 self.dtb.Sync(auto_resize=True) 364 offset = self.fdt.path_offset('/spl-test/subnode') 365 self.assertTrue(offset > 0) 366 367 368class TestFdtUtil(unittest.TestCase): 369 """Tests for the fdt_util module 370 371 This module will likely be mostly replaced at some point, once upstream 372 libfdt has better Python support. For now, this provides tests for current 373 functionality. 374 """ 375 @classmethod 376 def setUpClass(cls): 377 tools.PrepareOutputDir(None) 378 379 def setUp(self): 380 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 381 self.node = self.dtb.GetNode('/spl-test') 382 383 def testGetInt(self): 384 self.assertEqual(1, fdt_util.GetInt(self.node, 'intval')) 385 self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3)) 386 387 with self.assertRaises(ValueError) as e: 388 self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray')) 389 self.assertIn("property 'intarray' has list value: expecting a single " 390 'integer', str(e.exception)) 391 392 def testGetString(self): 393 self.assertEqual('message', fdt_util.GetString(self.node, 'stringval')) 394 self.assertEqual('test', fdt_util.GetString(self.node, 'missing', 395 'test')) 396 397 with self.assertRaises(ValueError) as e: 398 self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray')) 399 self.assertIn("property 'stringarray' has list value: expecting a " 400 'single string', str(e.exception)) 401 402 def testGetBool(self): 403 self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval')) 404 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing')) 405 self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True)) 406 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False)) 407 408 def testGetByte(self): 409 self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval')) 410 self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3)) 411 412 with self.assertRaises(ValueError) as e: 413 fdt_util.GetByte(self.node, 'longbytearray') 414 self.assertIn("property 'longbytearray' has list value: expecting a " 415 'single byte', str(e.exception)) 416 417 with self.assertRaises(ValueError) as e: 418 fdt_util.GetByte(self.node, 'intval') 419 self.assertIn("property 'intval' has length 4, expecting 1", 420 str(e.exception)) 421 422 def testGetPhandleList(self): 423 dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') 424 node = dtb.GetNode('/phandle-source2') 425 self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks')) 426 node = dtb.GetNode('/phandle-source') 427 self.assertEqual([1, 2, 11, 3, 12, 13, 1], 428 fdt_util.GetPhandleList(node, 'clocks')) 429 self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing')) 430 431 def testGetDataType(self): 432 self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int)) 433 self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval', 434 str)) 435 with self.assertRaises(ValueError) as e: 436 self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval', 437 bool)) 438 def testFdtCellsToCpu(self): 439 val = self.node.props['intarray'].value 440 self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0)) 441 self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1)) 442 443 dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts') 444 node2 = dtb2.GetNode('/test1') 445 val = node2.props['reg'].value 446 self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2)) 447 448 def testEnsureCompiled(self): 449 """Test a degenerate case of this function""" 450 dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts') 451 self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb)) 452 453 def testGetPlainBytes(self): 454 self.assertEqual('fred', fdt_util.get_plain_bytes('fred')) 455 456 457def RunTestCoverage(): 458 """Run the tests and check that we get 100% coverage""" 459 test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None, 460 ['tools/patman/*.py', '*test_fdt.py'], options.build_dir) 461 462 463def RunTests(args): 464 """Run all the test we have for the fdt model 465 466 Args: 467 args: List of positional args provided to fdt. This can hold a test 468 name to execute (as in 'fdt -t testFdt', for example) 469 """ 470 result = unittest.TestResult() 471 sys.argv = [sys.argv[0]] 472 test_name = args and args[0] or None 473 for module in (TestFdt, TestNode, TestProp, TestFdtUtil): 474 if test_name: 475 try: 476 suite = unittest.TestLoader().loadTestsFromName(test_name, module) 477 except AttributeError: 478 continue 479 else: 480 suite = unittest.TestLoader().loadTestsFromTestCase(module) 481 suite.run(result) 482 483 print result 484 for _, err in result.errors: 485 print err 486 for _, err in result.failures: 487 print err 488 489if __name__ != '__main__': 490 sys.exit(1) 491 492parser = OptionParser() 493parser.add_option('-B', '--build-dir', type='string', default='b', 494 help='Directory containing the build output') 495parser.add_option('-t', '--test', action='store_true', dest='test', 496 default=False, help='run tests') 497parser.add_option('-T', '--test-coverage', action='store_true', 498 default=False, help='run tests and check for 100% coverage') 499(options, args) = parser.parse_args() 500 501# Run our meagre tests 502if options.test: 503 RunTests(args) 504elif options.test_coverage: 505 RunTestCoverage() 506