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 testFdtCellsToCpu(self): 384 val = self.node.props['intarray'].value 385 self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0)) 386 self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1)) 387 388 dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts') 389 node2 = dtb2.GetNode('/test1') 390 val = node2.props['reg'].value 391 self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2)) 392 393 def testEnsureCompiled(self): 394 """Test a degenerate case of this function""" 395 dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts') 396 self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb)) 397 398 def testGetPlainBytes(self): 399 self.assertEqual('fred', fdt_util.get_plain_bytes('fred')) 400 401 402def RunTestCoverage(): 403 """Run the tests and check that we get 100% coverage""" 404 test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None, 405 ['tools/patman/*.py', '*test_fdt.py'], options.build_dir) 406 407 408def RunTests(args): 409 """Run all the test we have for the fdt model 410 411 Args: 412 args: List of positional args provided to fdt. This can hold a test 413 name to execute (as in 'fdt -t testFdt', for example) 414 """ 415 result = unittest.TestResult() 416 sys.argv = [sys.argv[0]] 417 test_name = args and args[0] or None 418 for module in (TestFdt, TestNode, TestProp, TestFdtUtil): 419 if test_name: 420 try: 421 suite = unittest.TestLoader().loadTestsFromName(test_name, module) 422 except AttributeError: 423 continue 424 else: 425 suite = unittest.TestLoader().loadTestsFromTestCase(module) 426 suite.run(result) 427 428 print result 429 for _, err in result.errors: 430 print err 431 for _, err in result.failures: 432 print err 433 434if __name__ != '__main__': 435 sys.exit(1) 436 437parser = OptionParser() 438parser.add_option('-B', '--build-dir', type='string', default='b', 439 help='Directory containing the build output') 440parser.add_option('-t', '--test', action='store_true', dest='test', 441 default=False, help='run tests') 442parser.add_option('-T', '--test-coverage', action='store_true', 443 default=False, help='run tests and check for 100% coverage') 444(options, args) = parser.parse_args() 445 446# Run our meagre tests 447if options.test: 448 RunTests(args) 449elif options.test_coverage: 450 RunTestCoverage() 451