22a075d6c5710c753141b66c244f20a40608a15a
[platform/kernel/u-boot.git] / tools / dtoc / test_fdt.py
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
7 from optparse import OptionParser
8 import glob
9 import os
10 import sys
11 import unittest
12
13 # Bring in the patman libraries
14 our_path = os.path.dirname(os.path.realpath(__file__))
15 for dirname in ['../patman', '..']:
16     sys.path.insert(0, os.path.join(our_path, dirname))
17
18 import command
19 import fdt
20 from fdt import TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL
21 import fdt_util
22 from fdt_util import fdt32_to_cpu
23 import libfdt
24 import test_util
25 import tools
26
27 def _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
51 class 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
122 class 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
203 class 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         self.dtb.Sync(auto_resize=True)
356
357     def testAddNode(self):
358         self.fdt.pack()
359         self.node.AddSubnode('subnode')
360         with self.assertRaises(libfdt.FdtException) as e:
361             self.dtb.Sync(auto_resize=False)
362         self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
363
364         self.dtb.Sync(auto_resize=True)
365         offset = self.fdt.path_offset('/spl-test/subnode')
366         self.assertTrue(offset > 0)
367
368     def testAddMore(self):
369         """Test various other methods for adding and setting properties"""
370         self.node.AddZeroProp('one')
371         self.dtb.Sync(auto_resize=True)
372         data = self.fdt.getprop(self.node.Offset(), 'one')
373         self.assertEqual(0, fdt32_to_cpu(data))
374
375         self.node.SetInt('one', 1)
376         self.dtb.Sync(auto_resize=False)
377         data = self.fdt.getprop(self.node.Offset(), 'one')
378         self.assertEqual(1, fdt32_to_cpu(data))
379
380         val = '123' + chr(0) + '456'
381         self.node.AddString('string', val)
382         self.dtb.Sync(auto_resize=True)
383         data = self.fdt.getprop(self.node.Offset(), 'string')
384         self.assertEqual(val + '\0', data)
385
386         self.fdt.pack()
387         self.node.SetString('string', val + 'x')
388         with self.assertRaises(libfdt.FdtException) as e:
389             self.dtb.Sync(auto_resize=False)
390         self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
391         self.node.SetString('string', val[:-1])
392
393         prop = self.node.props['string']
394         prop.SetData(val)
395         self.dtb.Sync(auto_resize=False)
396         data = self.fdt.getprop(self.node.Offset(), 'string')
397         self.assertEqual(val, data)
398
399         self.node.AddEmptyProp('empty', 5)
400         self.dtb.Sync(auto_resize=True)
401         prop = self.node.props['empty']
402         prop.SetData(val)
403         self.dtb.Sync(auto_resize=False)
404         data = self.fdt.getprop(self.node.Offset(), 'empty')
405         self.assertEqual(val, data)
406
407         self.node.SetData('empty', '123')
408         self.assertEqual('123', prop.bytes)
409
410
411 class TestFdtUtil(unittest.TestCase):
412     """Tests for the fdt_util module
413
414     This module will likely be mostly replaced at some point, once upstream
415     libfdt has better Python support. For now, this provides tests for current
416     functionality.
417     """
418     @classmethod
419     def setUpClass(cls):
420         tools.PrepareOutputDir(None)
421
422     def setUp(self):
423         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
424         self.node = self.dtb.GetNode('/spl-test')
425
426     def testGetInt(self):
427         self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
428         self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
429
430         with self.assertRaises(ValueError) as e:
431             self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
432         self.assertIn("property 'intarray' has list value: expecting a single "
433                       'integer', str(e.exception))
434
435     def testGetString(self):
436         self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
437         self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
438                                                     'test'))
439
440         with self.assertRaises(ValueError) as e:
441             self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
442         self.assertIn("property 'stringarray' has list value: expecting a "
443                       'single string', str(e.exception))
444
445     def testGetBool(self):
446         self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
447         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
448         self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
449         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
450
451     def testGetByte(self):
452         self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
453         self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
454
455         with self.assertRaises(ValueError) as e:
456             fdt_util.GetByte(self.node, 'longbytearray')
457         self.assertIn("property 'longbytearray' has list value: expecting a "
458                       'single byte', str(e.exception))
459
460         with self.assertRaises(ValueError) as e:
461             fdt_util.GetByte(self.node, 'intval')
462         self.assertIn("property 'intval' has length 4, expecting 1",
463                       str(e.exception))
464
465     def testGetPhandleList(self):
466         dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
467         node = dtb.GetNode('/phandle-source2')
468         self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks'))
469         node = dtb.GetNode('/phandle-source')
470         self.assertEqual([1, 2, 11, 3, 12, 13, 1],
471                          fdt_util.GetPhandleList(node, 'clocks'))
472         self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing'))
473
474     def testGetDataType(self):
475         self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
476         self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
477                                                          str))
478         with self.assertRaises(ValueError) as e:
479             self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
480                                                      bool))
481     def testFdtCellsToCpu(self):
482         val = self.node.props['intarray'].value
483         self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
484         self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
485
486         dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
487         node2 = dtb2.GetNode('/test1')
488         val = node2.props['reg'].value
489         self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
490
491     def testEnsureCompiled(self):
492         """Test a degenerate case of this function"""
493         dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
494         self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
495
496     def testGetPlainBytes(self):
497         self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
498
499
500 def RunTestCoverage():
501     """Run the tests and check that we get 100% coverage"""
502     test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
503             ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
504
505
506 def RunTests(args):
507     """Run all the test we have for the fdt model
508
509     Args:
510         args: List of positional args provided to fdt. This can hold a test
511             name to execute (as in 'fdt -t testFdt', for example)
512     """
513     result = unittest.TestResult()
514     sys.argv = [sys.argv[0]]
515     test_name = args and args[0] or None
516     for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
517         if test_name:
518             try:
519                 suite = unittest.TestLoader().loadTestsFromName(test_name, module)
520             except AttributeError:
521                 continue
522         else:
523             suite = unittest.TestLoader().loadTestsFromTestCase(module)
524         suite.run(result)
525
526     print result
527     for _, err in result.errors:
528         print err
529     for _, err in result.failures:
530         print err
531
532 if __name__ != '__main__':
533     sys.exit(1)
534
535 parser = OptionParser()
536 parser.add_option('-B', '--build-dir', type='string', default='b',
537         help='Directory containing the build output')
538 parser.add_option('-t', '--test', action='store_true', dest='test',
539                   default=False, help='run tests')
540 parser.add_option('-T', '--test-coverage', action='store_true',
541                 default=False, help='run tests and check for 100% coverage')
542 (options, args) = parser.parse_args()
543
544 # Run our meagre tests
545 if options.test:
546     RunTests(args)
547 elif options.test_coverage:
548     RunTestCoverage()