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