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 119class TestNode(unittest.TestCase): 120 """Test operation of the Node class""" 121 122 @classmethod 123 def setUpClass(cls): 124 tools.PrepareOutputDir(None) 125 126 @classmethod 127 def tearDownClass(cls): 128 tools._FinaliseForTest() 129 130 def setUp(self): 131 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 132 self.node = self.dtb.GetNode('/spl-test') 133 134 def testOffset(self): 135 """Tests that we can obtain the offset of a node""" 136 self.assertTrue(self.node.Offset() > 0) 137 138 def testDelete(self): 139 """Tests that we can delete a property""" 140 node2 = self.dtb.GetNode('/spl-test2') 141 offset1 = node2.Offset() 142 self.node.DeleteProp('intval') 143 offset2 = node2.Offset() 144 self.assertTrue(offset2 < offset1) 145 self.node.DeleteProp('intarray') 146 offset3 = node2.Offset() 147 self.assertTrue(offset3 < offset2) 148 with self.assertRaises(libfdt.FdtException): 149 self.node.DeleteProp('missing') 150 151 def testDeleteGetOffset(self): 152 """Test that property offset update when properties are deleted""" 153 self.node.DeleteProp('intval') 154 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray') 155 self.assertEqual(prop.value, value) 156 157 def testFindNode(self): 158 """Tests that we can find a node using the _FindNode() functoin""" 159 node = self.dtb.GetRoot()._FindNode('i2c@0') 160 self.assertEqual('i2c@0', node.name) 161 subnode = node._FindNode('pmic@9') 162 self.assertEqual('pmic@9', subnode.name) 163 self.assertEqual(None, node._FindNode('missing')) 164 165 def testRefreshMissingNode(self): 166 """Test refreshing offsets when an extra node is present in dtb""" 167 # Delete it from our tables, not the device tree 168 del self.dtb._root.subnodes[-1] 169 with self.assertRaises(ValueError) as e: 170 self.dtb.Refresh() 171 self.assertIn('Internal error, offset', str(e.exception)) 172 173 def testRefreshExtraNode(self): 174 """Test refreshing offsets when an expected node is missing""" 175 # Delete it from the device tre, not our tables 176 self.dtb.GetFdtObj().del_node(self.node.Offset()) 177 with self.assertRaises(ValueError) as e: 178 self.dtb.Refresh() 179 self.assertIn('Internal error, node name mismatch ' 180 'spl-test != spl-test2', str(e.exception)) 181 182 def testRefreshMissingProp(self): 183 """Test refreshing offsets when an extra property is present in dtb""" 184 # Delete it from our tables, not the device tree 185 del self.node.props['notstring'] 186 with self.assertRaises(ValueError) as e: 187 self.dtb.Refresh() 188 self.assertIn("Internal error, property 'notstring' missing, offset ", 189 str(e.exception)) 190 191 192class TestProp(unittest.TestCase): 193 """Test operation of the Prop class""" 194 195 @classmethod 196 def setUpClass(cls): 197 tools.PrepareOutputDir(None) 198 199 @classmethod 200 def tearDownClass(cls): 201 tools._FinaliseForTest() 202 203 def setUp(self): 204 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 205 self.node = self.dtb.GetNode('/spl-test') 206 self.fdt = self.dtb.GetFdtObj() 207 208 def testMissingNode(self): 209 self.assertEqual(None, self.dtb.GetNode('missing')) 210 211 def testPhandle(self): 212 dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') 213 node = dtb.GetNode('/phandle-source2') 214 prop = node.props['clocks'] 215 self.assertTrue(fdt32_to_cpu(prop.value) > 0) 216 217 def _ConvertProp(self, prop_name): 218 """Helper function to look up a property in self.node and return it 219 220 Args: 221 Property name to find 222 223 Return fdt.Prop object for this property 224 """ 225 p = self.fdt.get_property(self.node.Offset(), prop_name) 226 return fdt.Prop(self.node, -1, prop_name, p) 227 228 def testMakeProp(self): 229 """Test we can convert all the the types that are supported""" 230 prop = self._ConvertProp('boolval') 231 self.assertEqual(fdt.TYPE_BOOL, prop.type) 232 self.assertEqual(True, prop.value) 233 234 prop = self._ConvertProp('intval') 235 self.assertEqual(fdt.TYPE_INT, prop.type) 236 self.assertEqual(1, fdt32_to_cpu(prop.value)) 237 238 prop = self._ConvertProp('intarray') 239 self.assertEqual(fdt.TYPE_INT, prop.type) 240 val = [fdt32_to_cpu(val) for val in prop.value] 241 self.assertEqual([2, 3, 4], val) 242 243 prop = self._ConvertProp('byteval') 244 self.assertEqual(fdt.TYPE_BYTE, prop.type) 245 self.assertEqual(5, ord(prop.value)) 246 247 prop = self._ConvertProp('longbytearray') 248 self.assertEqual(fdt.TYPE_BYTE, prop.type) 249 val = [ord(val) for val in prop.value] 250 self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val) 251 252 prop = self._ConvertProp('stringval') 253 self.assertEqual(fdt.TYPE_STRING, prop.type) 254 self.assertEqual('message', prop.value) 255 256 prop = self._ConvertProp('stringarray') 257 self.assertEqual(fdt.TYPE_STRING, prop.type) 258 self.assertEqual(['multi-word', 'message'], prop.value) 259 260 prop = self._ConvertProp('notstring') 261 self.assertEqual(fdt.TYPE_BYTE, prop.type) 262 val = [ord(val) for val in prop.value] 263 self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val) 264 265 def testGetEmpty(self): 266 """Tests the GetEmpty() function for the various supported types""" 267 self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL)) 268 self.assertEqual(chr(0), fdt.Prop.GetEmpty(fdt.TYPE_BYTE)) 269 self.assertEqual(chr(0) * 4, fdt.Prop.GetEmpty(fdt.TYPE_INT)) 270 self.assertEqual('', fdt.Prop.GetEmpty(fdt.TYPE_STRING)) 271 272 def testGetOffset(self): 273 """Test we can get the offset of a property""" 274 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray') 275 self.assertEqual(prop.value, value) 276 277 def testWiden(self): 278 """Test widening of values""" 279 node2 = self.dtb.GetNode('/spl-test2') 280 prop = self.node.props['intval'] 281 282 # No action 283 prop2 = node2.props['intval'] 284 prop.Widen(prop2) 285 self.assertEqual(fdt.TYPE_INT, prop.type) 286 self.assertEqual(1, fdt32_to_cpu(prop.value)) 287 288 # Convert singla value to array 289 prop2 = self.node.props['intarray'] 290 prop.Widen(prop2) 291 self.assertEqual(fdt.TYPE_INT, prop.type) 292 self.assertTrue(isinstance(prop.value, list)) 293 294 # A 4-byte array looks like a single integer. When widened by a longer 295 # byte array, it should turn into an array. 296 prop = self.node.props['longbytearray'] 297 prop2 = node2.props['longbytearray'] 298 self.assertFalse(isinstance(prop2.value, list)) 299 self.assertEqual(4, len(prop2.value)) 300 prop2.Widen(prop) 301 self.assertTrue(isinstance(prop2.value, list)) 302 self.assertEqual(9, len(prop2.value)) 303 304 # Similarly for a string array 305 prop = self.node.props['stringval'] 306 prop2 = node2.props['stringarray'] 307 self.assertFalse(isinstance(prop.value, list)) 308 self.assertEqual(7, len(prop.value)) 309 prop.Widen(prop2) 310 self.assertTrue(isinstance(prop.value, list)) 311 self.assertEqual(3, len(prop.value)) 312 313 # Enlarging an existing array 314 prop = self.node.props['stringarray'] 315 prop2 = node2.props['stringarray'] 316 self.assertTrue(isinstance(prop.value, list)) 317 self.assertEqual(2, len(prop.value)) 318 prop.Widen(prop2) 319 self.assertTrue(isinstance(prop.value, list)) 320 self.assertEqual(3, len(prop.value)) 321 322 def testAdd(self): 323 """Test adding properties""" 324 self.fdt.pack() 325 # This function should automatically expand the device tree 326 self.node.AddZeroProp('one') 327 self.node.AddZeroProp('two') 328 self.node.AddZeroProp('three') 329 330 # Updating existing properties should be OK, since the device-tree size 331 # does not change 332 self.fdt.pack() 333 self.node.SetInt('one', 1) 334 self.node.SetInt('two', 2) 335 self.node.SetInt('three', 3) 336 337 # This should fail since it would need to increase the device-tree size 338 with self.assertRaises(libfdt.FdtException) as e: 339 self.node.SetInt('four', 4) 340 self.assertIn('FDT_ERR_NOSPACE', str(e.exception)) 341 342 343class TestFdtUtil(unittest.TestCase): 344 """Tests for the fdt_util module 345 346 This module will likely be mostly replaced at some point, once upstream 347 libfdt has better Python support. For now, this provides tests for current 348 functionality. 349 """ 350 @classmethod 351 def setUpClass(cls): 352 tools.PrepareOutputDir(None) 353 354 def setUp(self): 355 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 356 self.node = self.dtb.GetNode('/spl-test') 357 358 def testGetInt(self): 359 self.assertEqual(1, fdt_util.GetInt(self.node, 'intval')) 360 self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3)) 361 362 with self.assertRaises(ValueError) as e: 363 self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray')) 364 self.assertIn("property 'intarray' has list value: expecting a single " 365 'integer', str(e.exception)) 366 367 def testGetString(self): 368 self.assertEqual('message', fdt_util.GetString(self.node, 'stringval')) 369 self.assertEqual('test', fdt_util.GetString(self.node, 'missing', 370 'test')) 371 372 with self.assertRaises(ValueError) as e: 373 self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray')) 374 self.assertIn("property 'stringarray' has list value: expecting a " 375 'single string', str(e.exception)) 376 377 def testGetBool(self): 378 self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval')) 379 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing')) 380 self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True)) 381 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False)) 382 383 def testGetByte(self): 384 self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval')) 385 self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3)) 386 387 with self.assertRaises(ValueError) as e: 388 fdt_util.GetByte(self.node, 'longbytearray') 389 self.assertIn("property 'longbytearray' has list value: expecting a " 390 'single byte', str(e.exception)) 391 392 with self.assertRaises(ValueError) as e: 393 fdt_util.GetByte(self.node, 'intval') 394 self.assertIn("property 'intval' has length 4, expecting 1", 395 str(e.exception)) 396 397 def testGetDataType(self): 398 self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int)) 399 self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval', 400 str)) 401 with self.assertRaises(ValueError) as e: 402 self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval', 403 bool)) 404 def testFdtCellsToCpu(self): 405 val = self.node.props['intarray'].value 406 self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0)) 407 self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1)) 408 409 dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts') 410 node2 = dtb2.GetNode('/test1') 411 val = node2.props['reg'].value 412 self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2)) 413 414 def testEnsureCompiled(self): 415 """Test a degenerate case of this function""" 416 dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts') 417 self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb)) 418 419 def testGetPlainBytes(self): 420 self.assertEqual('fred', fdt_util.get_plain_bytes('fred')) 421 422 423def RunTestCoverage(): 424 """Run the tests and check that we get 100% coverage""" 425 test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None, 426 ['tools/patman/*.py', '*test_fdt.py'], options.build_dir) 427 428 429def RunTests(args): 430 """Run all the test we have for the fdt model 431 432 Args: 433 args: List of positional args provided to fdt. This can hold a test 434 name to execute (as in 'fdt -t testFdt', for example) 435 """ 436 result = unittest.TestResult() 437 sys.argv = [sys.argv[0]] 438 test_name = args and args[0] or None 439 for module in (TestFdt, TestNode, TestProp, TestFdtUtil): 440 if test_name: 441 try: 442 suite = unittest.TestLoader().loadTestsFromName(test_name, module) 443 except AttributeError: 444 continue 445 else: 446 suite = unittest.TestLoader().loadTestsFromTestCase(module) 447 suite.run(result) 448 449 print result 450 for _, err in result.errors: 451 print err 452 for _, err in result.failures: 453 print err 454 455if __name__ != '__main__': 456 sys.exit(1) 457 458parser = OptionParser() 459parser.add_option('-B', '--build-dir', type='string', default='b', 460 help='Directory containing the build output') 461parser.add_option('-t', '--test', action='store_true', dest='test', 462 default=False, help='run tests') 463parser.add_option('-T', '--test-coverage', action='store_true', 464 default=False, help='run tests and check for 100% coverage') 465(options, args) = parser.parse_args() 466 467# Run our meagre tests 468if options.test: 469 RunTests(args) 470elif options.test_coverage: 471 RunTestCoverage() 472