Merge branch 'master' of git://git.denx.de/u-boot-sh
[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     def testFromData(self):
411         dtb2 = fdt.Fdt.FromData(self.dtb.GetContents())
412         self.assertEqual(dtb2.GetContents(), self.dtb.GetContents())
413
414         self.node.AddEmptyProp('empty', 5)
415         self.dtb.Sync(auto_resize=True)
416         self.assertTrue(dtb2.GetContents() != self.dtb.GetContents())
417
418
419 class TestFdtUtil(unittest.TestCase):
420     """Tests for the fdt_util module
421
422     This module will likely be mostly replaced at some point, once upstream
423     libfdt has better Python support. For now, this provides tests for current
424     functionality.
425     """
426     @classmethod
427     def setUpClass(cls):
428         tools.PrepareOutputDir(None)
429
430     def setUp(self):
431         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
432         self.node = self.dtb.GetNode('/spl-test')
433
434     def testGetInt(self):
435         self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
436         self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
437
438         with self.assertRaises(ValueError) as e:
439             self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
440         self.assertIn("property 'intarray' has list value: expecting a single "
441                       'integer', str(e.exception))
442
443     def testGetString(self):
444         self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
445         self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
446                                                     'test'))
447
448         with self.assertRaises(ValueError) as e:
449             self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
450         self.assertIn("property 'stringarray' has list value: expecting a "
451                       'single string', str(e.exception))
452
453     def testGetBool(self):
454         self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
455         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
456         self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
457         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
458
459     def testGetByte(self):
460         self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
461         self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
462
463         with self.assertRaises(ValueError) as e:
464             fdt_util.GetByte(self.node, 'longbytearray')
465         self.assertIn("property 'longbytearray' has list value: expecting a "
466                       'single byte', str(e.exception))
467
468         with self.assertRaises(ValueError) as e:
469             fdt_util.GetByte(self.node, 'intval')
470         self.assertIn("property 'intval' has length 4, expecting 1",
471                       str(e.exception))
472
473     def testGetPhandleList(self):
474         dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
475         node = dtb.GetNode('/phandle-source2')
476         self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks'))
477         node = dtb.GetNode('/phandle-source')
478         self.assertEqual([1, 2, 11, 3, 12, 13, 1],
479                          fdt_util.GetPhandleList(node, 'clocks'))
480         self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing'))
481
482     def testGetDataType(self):
483         self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
484         self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
485                                                          str))
486         with self.assertRaises(ValueError) as e:
487             self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
488                                                      bool))
489     def testFdtCellsToCpu(self):
490         val = self.node.props['intarray'].value
491         self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
492         self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
493
494         dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
495         node2 = dtb2.GetNode('/test1')
496         val = node2.props['reg'].value
497         self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
498
499     def testEnsureCompiled(self):
500         """Test a degenerate case of this function"""
501         dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
502         self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
503
504     def testGetPlainBytes(self):
505         self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
506
507
508 def RunTestCoverage():
509     """Run the tests and check that we get 100% coverage"""
510     test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
511             ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
512
513
514 def RunTests(args):
515     """Run all the test we have for the fdt model
516
517     Args:
518         args: List of positional args provided to fdt. This can hold a test
519             name to execute (as in 'fdt -t testFdt', for example)
520     """
521     result = unittest.TestResult()
522     sys.argv = [sys.argv[0]]
523     test_name = args and args[0] or None
524     for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
525         if test_name:
526             try:
527                 suite = unittest.TestLoader().loadTestsFromName(test_name, module)
528             except AttributeError:
529                 continue
530         else:
531             suite = unittest.TestLoader().loadTestsFromTestCase(module)
532         suite.run(result)
533
534     print result
535     for _, err in result.errors:
536         print err
537     for _, err in result.failures:
538         print err
539
540 if __name__ != '__main__':
541     sys.exit(1)
542
543 parser = OptionParser()
544 parser.add_option('-B', '--build-dir', type='string', default='b',
545         help='Directory containing the build output')
546 parser.add_option('-t', '--test', action='store_true', dest='test',
547                   default=False, help='run tests')
548 parser.add_option('-T', '--test-coverage', action='store_true',
549                 default=False, help='run tests and check for 100% coverage')
550 (options, args) = parser.parse_args()
551
552 # Run our meagre tests
553 if options.test:
554     RunTests(args)
555 elif options.test_coverage:
556     RunTestCoverage()