binman: Show a helpful error when a DT property is missing
[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 __future__ import print_function
8
9 from optparse import OptionParser
10 import glob
11 import os
12 import sys
13 import unittest
14
15 # Bring in the patman libraries
16 our_path = os.path.dirname(os.path.realpath(__file__))
17 for dirname in ['../patman', '..']:
18     sys.path.insert(0, os.path.join(our_path, dirname))
19
20 import command
21 import fdt
22 from fdt import TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, BytesToValue
23 import fdt_util
24 from fdt_util import fdt32_to_cpu
25 import libfdt
26 import test_util
27 import tools
28
29 def _GetPropertyValue(dtb, node, prop_name):
30     """Low-level function to get the property value based on its offset
31
32     This looks directly in the device tree at the property's offset to find
33     its value. It is useful as a check that the property is in the correct
34     place.
35
36     Args:
37         node: Node to look in
38         prop_name: Property name to find
39
40     Returns:
41         Tuple:
42             Prop object found
43             Value of property as a string (found using property offset)
44     """
45     prop = node.props[prop_name]
46
47     # Add 12, which is sizeof(struct fdt_property), to get to start of data
48     offset = prop.GetOffset() + 12
49     data = dtb.GetContents()[offset:offset + len(prop.value)]
50     return prop, [tools.ToChar(x) for x in data]
51
52
53 class TestFdt(unittest.TestCase):
54     """Tests for the Fdt module
55
56     This includes unit tests for some functions and functional tests for the fdt
57     module.
58     """
59     @classmethod
60     def setUpClass(cls):
61         tools.PrepareOutputDir(None)
62
63     @classmethod
64     def tearDownClass(cls):
65         tools.FinaliseOutputDir()
66
67     def setUp(self):
68         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
69
70     def testFdt(self):
71         """Test that we can open an Fdt"""
72         self.dtb.Scan()
73         root = self.dtb.GetRoot()
74         self.assertTrue(isinstance(root, fdt.Node))
75
76     def testGetNode(self):
77         """Test the GetNode() method"""
78         node = self.dtb.GetNode('/spl-test')
79         self.assertTrue(isinstance(node, fdt.Node))
80         node = self.dtb.GetNode('/i2c@0/pmic@9')
81         self.assertTrue(isinstance(node, fdt.Node))
82         self.assertEqual('pmic@9', node.name)
83         self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing'))
84
85     def testFlush(self):
86         """Check that we can flush the device tree out to its file"""
87         fname = self.dtb._fname
88         with open(fname, 'rb') as fd:
89             data = fd.read()
90         os.remove(fname)
91         with self.assertRaises(IOError):
92             open(fname, 'rb')
93         self.dtb.Flush()
94         with open(fname, 'rb') as fd:
95             data = fd.read()
96
97     def testPack(self):
98         """Test that packing a device tree works"""
99         self.dtb.Pack()
100
101     def testGetFdt(self):
102         """Tetst that we can access the raw device-tree data"""
103         self.assertTrue(isinstance(self.dtb.GetContents(), bytearray))
104
105     def testGetProps(self):
106         """Tests obtaining a list of properties"""
107         node = self.dtb.GetNode('/spl-test')
108         props = self.dtb.GetProps(node)
109         self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible',
110                           'intarray', 'intval', 'longbytearray', 'notstring',
111                           'stringarray', 'stringval', 'u-boot,dm-pre-reloc'],
112                          sorted(props.keys()))
113
114     def testCheckError(self):
115         """Tests the ChecKError() function"""
116         with self.assertRaises(ValueError) as e:
117             fdt.CheckErr(-libfdt.NOTFOUND, 'hello')
118         self.assertIn('FDT_ERR_NOTFOUND: hello', str(e.exception))
119
120     def testGetFdt(self):
121         node = self.dtb.GetNode('/spl-test')
122         self.assertEqual(self.dtb, node.GetFdt())
123
124     def testBytesToValue(self):
125         self.assertEqual(BytesToValue(b'this\0is\0'),
126                          (TYPE_STRING, ['this', 'is']))
127
128 class TestNode(unittest.TestCase):
129     """Test operation of the Node class"""
130
131     @classmethod
132     def setUpClass(cls):
133         tools.PrepareOutputDir(None)
134
135     @classmethod
136     def tearDownClass(cls):
137         tools.FinaliseOutputDir()
138
139     def setUp(self):
140         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
141         self.node = self.dtb.GetNode('/spl-test')
142
143     def testOffset(self):
144         """Tests that we can obtain the offset of a node"""
145         self.assertTrue(self.node.Offset() > 0)
146
147     def testDelete(self):
148         """Tests that we can delete a property"""
149         node2 = self.dtb.GetNode('/spl-test2')
150         offset1 = node2.Offset()
151         self.node.DeleteProp('intval')
152         offset2 = node2.Offset()
153         self.assertTrue(offset2 < offset1)
154         self.node.DeleteProp('intarray')
155         offset3 = node2.Offset()
156         self.assertTrue(offset3 < offset2)
157         with self.assertRaises(libfdt.FdtException):
158             self.node.DeleteProp('missing')
159
160     def testDeleteGetOffset(self):
161         """Test that property offset update when properties are deleted"""
162         self.node.DeleteProp('intval')
163         prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
164         self.assertEqual(prop.value, value)
165
166     def testFindNode(self):
167         """Tests that we can find a node using the FindNode() functoin"""
168         node = self.dtb.GetRoot().FindNode('i2c@0')
169         self.assertEqual('i2c@0', node.name)
170         subnode = node.FindNode('pmic@9')
171         self.assertEqual('pmic@9', subnode.name)
172         self.assertEqual(None, node.FindNode('missing'))
173
174     def testRefreshMissingNode(self):
175         """Test refreshing offsets when an extra node is present in dtb"""
176         # Delete it from our tables, not the device tree
177         del self.dtb._root.subnodes[-1]
178         with self.assertRaises(ValueError) as e:
179             self.dtb.Refresh()
180         self.assertIn('Internal error, offset', str(e.exception))
181
182     def testRefreshExtraNode(self):
183         """Test refreshing offsets when an expected node is missing"""
184         # Delete it from the device tre, not our tables
185         self.dtb.GetFdtObj().del_node(self.node.Offset())
186         with self.assertRaises(ValueError) as e:
187             self.dtb.Refresh()
188         self.assertIn('Internal error, node name mismatch '
189                       'spl-test != spl-test2', str(e.exception))
190
191     def testRefreshMissingProp(self):
192         """Test refreshing offsets when an extra property is present in dtb"""
193         # Delete it from our tables, not the device tree
194         del self.node.props['notstring']
195         with self.assertRaises(ValueError) as e:
196             self.dtb.Refresh()
197         self.assertIn("Internal error, property 'notstring' missing, offset ",
198                       str(e.exception))
199
200     def testLookupPhandle(self):
201         """Test looking up a single phandle"""
202         dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
203         node = dtb.GetNode('/phandle-source2')
204         prop = node.props['clocks']
205         target = dtb.GetNode('/phandle-target')
206         self.assertEqual(target, dtb.LookupPhandle(fdt32_to_cpu(prop.value)))
207
208
209 class TestProp(unittest.TestCase):
210     """Test operation of the Prop class"""
211
212     @classmethod
213     def setUpClass(cls):
214         tools.PrepareOutputDir(None)
215
216     @classmethod
217     def tearDownClass(cls):
218         tools.FinaliseOutputDir()
219
220     def setUp(self):
221         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
222         self.node = self.dtb.GetNode('/spl-test')
223         self.fdt = self.dtb.GetFdtObj()
224
225     def testMissingNode(self):
226         self.assertEqual(None, self.dtb.GetNode('missing'))
227
228     def testPhandle(self):
229         dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
230         node = dtb.GetNode('/phandle-source2')
231         prop = node.props['clocks']
232         self.assertTrue(fdt32_to_cpu(prop.value) > 0)
233
234     def _ConvertProp(self, prop_name):
235         """Helper function to look up a property in self.node and return it
236
237         Args:
238             Property name to find
239
240         Return fdt.Prop object for this property
241         """
242         p = self.fdt.getprop(self.node.Offset(), prop_name)
243         return fdt.Prop(self.node, -1, prop_name, p)
244
245     def testMakeProp(self):
246         """Test we can convert all the the types that are supported"""
247         prop = self._ConvertProp('boolval')
248         self.assertEqual(fdt.TYPE_BOOL, prop.type)
249         self.assertEqual(True, prop.value)
250
251         prop = self._ConvertProp('intval')
252         self.assertEqual(fdt.TYPE_INT, prop.type)
253         self.assertEqual(1, fdt32_to_cpu(prop.value))
254
255         prop = self._ConvertProp('intarray')
256         self.assertEqual(fdt.TYPE_INT, prop.type)
257         val = [fdt32_to_cpu(val) for val in prop.value]
258         self.assertEqual([2, 3, 4], val)
259
260         prop = self._ConvertProp('byteval')
261         self.assertEqual(fdt.TYPE_BYTE, prop.type)
262         self.assertEqual(5, ord(prop.value))
263
264         prop = self._ConvertProp('longbytearray')
265         self.assertEqual(fdt.TYPE_BYTE, prop.type)
266         val = [ord(val) for val in prop.value]
267         self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val)
268
269         prop = self._ConvertProp('stringval')
270         self.assertEqual(fdt.TYPE_STRING, prop.type)
271         self.assertEqual('message', prop.value)
272
273         prop = self._ConvertProp('stringarray')
274         self.assertEqual(fdt.TYPE_STRING, prop.type)
275         self.assertEqual(['multi-word', 'message'], prop.value)
276
277         prop = self._ConvertProp('notstring')
278         self.assertEqual(fdt.TYPE_BYTE, prop.type)
279         val = [ord(val) for val in prop.value]
280         self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val)
281
282     def testGetEmpty(self):
283         """Tests the GetEmpty() function for the various supported types"""
284         self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL))
285         self.assertEqual(chr(0), fdt.Prop.GetEmpty(fdt.TYPE_BYTE))
286         self.assertEqual(tools.GetBytes(0, 4), fdt.Prop.GetEmpty(fdt.TYPE_INT))
287         self.assertEqual('', fdt.Prop.GetEmpty(fdt.TYPE_STRING))
288
289     def testGetOffset(self):
290         """Test we can get the offset of a property"""
291         prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
292         self.assertEqual(prop.value, value)
293
294     def testWiden(self):
295         """Test widening of values"""
296         node2 = self.dtb.GetNode('/spl-test2')
297         prop = self.node.props['intval']
298
299         # No action
300         prop2 = node2.props['intval']
301         prop.Widen(prop2)
302         self.assertEqual(fdt.TYPE_INT, prop.type)
303         self.assertEqual(1, fdt32_to_cpu(prop.value))
304
305         # Convert singla value to array
306         prop2 = self.node.props['intarray']
307         prop.Widen(prop2)
308         self.assertEqual(fdt.TYPE_INT, prop.type)
309         self.assertTrue(isinstance(prop.value, list))
310
311         # A 4-byte array looks like a single integer. When widened by a longer
312         # byte array, it should turn into an array.
313         prop = self.node.props['longbytearray']
314         prop2 = node2.props['longbytearray']
315         self.assertFalse(isinstance(prop2.value, list))
316         self.assertEqual(4, len(prop2.value))
317         prop2.Widen(prop)
318         self.assertTrue(isinstance(prop2.value, list))
319         self.assertEqual(9, len(prop2.value))
320
321         # Similarly for a string array
322         prop = self.node.props['stringval']
323         prop2 = node2.props['stringarray']
324         self.assertFalse(isinstance(prop.value, list))
325         self.assertEqual(7, len(prop.value))
326         prop.Widen(prop2)
327         self.assertTrue(isinstance(prop.value, list))
328         self.assertEqual(3, len(prop.value))
329
330         # Enlarging an existing array
331         prop = self.node.props['stringarray']
332         prop2 = node2.props['stringarray']
333         self.assertTrue(isinstance(prop.value, list))
334         self.assertEqual(2, len(prop.value))
335         prop.Widen(prop2)
336         self.assertTrue(isinstance(prop.value, list))
337         self.assertEqual(3, len(prop.value))
338
339     def testAdd(self):
340         """Test adding properties"""
341         self.fdt.pack()
342         # This function should automatically expand the device tree
343         self.node.AddZeroProp('one')
344         self.node.AddZeroProp('two')
345         self.node.AddZeroProp('three')
346         self.dtb.Sync(auto_resize=True)
347
348         # Updating existing properties should be OK, since the device-tree size
349         # does not change
350         self.fdt.pack()
351         self.node.SetInt('one', 1)
352         self.node.SetInt('two', 2)
353         self.node.SetInt('three', 3)
354         self.dtb.Sync(auto_resize=False)
355
356         # This should fail since it would need to increase the device-tree size
357         self.node.AddZeroProp('four')
358         with self.assertRaises(libfdt.FdtException) as e:
359             self.dtb.Sync(auto_resize=False)
360         self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
361         self.dtb.Sync(auto_resize=True)
362
363     def testAddNode(self):
364         self.fdt.pack()
365         self.node.AddSubnode('subnode')
366         with self.assertRaises(libfdt.FdtException) as e:
367             self.dtb.Sync(auto_resize=False)
368         self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
369
370         self.dtb.Sync(auto_resize=True)
371         offset = self.fdt.path_offset('/spl-test/subnode')
372         self.assertTrue(offset > 0)
373
374     def testAddMore(self):
375         """Test various other methods for adding and setting properties"""
376         self.node.AddZeroProp('one')
377         self.dtb.Sync(auto_resize=True)
378         data = self.fdt.getprop(self.node.Offset(), 'one')
379         self.assertEqual(0, fdt32_to_cpu(data))
380
381         self.node.SetInt('one', 1)
382         self.dtb.Sync(auto_resize=False)
383         data = self.fdt.getprop(self.node.Offset(), 'one')
384         self.assertEqual(1, fdt32_to_cpu(data))
385
386         val = '123' + chr(0) + '456'
387         self.node.AddString('string', val)
388         self.dtb.Sync(auto_resize=True)
389         data = self.fdt.getprop(self.node.Offset(), 'string')
390         self.assertEqual(tools.ToBytes(val) + b'\0', data)
391
392         self.fdt.pack()
393         self.node.SetString('string', val + 'x')
394         with self.assertRaises(libfdt.FdtException) as e:
395             self.dtb.Sync(auto_resize=False)
396         self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
397         self.node.SetString('string', val[:-1])
398
399         prop = self.node.props['string']
400         prop.SetData(tools.ToBytes(val))
401         self.dtb.Sync(auto_resize=False)
402         data = self.fdt.getprop(self.node.Offset(), 'string')
403         self.assertEqual(tools.ToBytes(val), data)
404
405         self.node.AddEmptyProp('empty', 5)
406         self.dtb.Sync(auto_resize=True)
407         prop = self.node.props['empty']
408         prop.SetData(tools.ToBytes(val))
409         self.dtb.Sync(auto_resize=False)
410         data = self.fdt.getprop(self.node.Offset(), 'empty')
411         self.assertEqual(tools.ToBytes(val), data)
412
413         self.node.SetData('empty', b'123')
414         self.assertEqual(b'123', prop.bytes)
415
416     def testFromData(self):
417         dtb2 = fdt.Fdt.FromData(self.dtb.GetContents())
418         self.assertEqual(dtb2.GetContents(), self.dtb.GetContents())
419
420         self.node.AddEmptyProp('empty', 5)
421         self.dtb.Sync(auto_resize=True)
422         self.assertTrue(dtb2.GetContents() != self.dtb.GetContents())
423
424     def testMissingSetInt(self):
425         """Test handling of a missing property with SetInt"""
426         with self.assertRaises(ValueError) as e:
427             self.node.SetInt('one', 1)
428         self.assertIn("node '/spl-test': Missing property 'one'",
429                       str(e.exception))
430
431     def testMissingSetData(self):
432         """Test handling of a missing property with SetData"""
433         with self.assertRaises(ValueError) as e:
434             self.node.SetData('one', b'data')
435         self.assertIn("node '/spl-test': Missing property 'one'",
436                       str(e.exception))
437
438     def testMissingSetString(self):
439         """Test handling of a missing property with SetString"""
440         with self.assertRaises(ValueError) as e:
441             self.node.SetString('one', 1)
442         self.assertIn("node '/spl-test': Missing property 'one'",
443                       str(e.exception))
444
445
446 class TestFdtUtil(unittest.TestCase):
447     """Tests for the fdt_util module
448
449     This module will likely be mostly replaced at some point, once upstream
450     libfdt has better Python support. For now, this provides tests for current
451     functionality.
452     """
453     @classmethod
454     def setUpClass(cls):
455         tools.PrepareOutputDir(None)
456
457     @classmethod
458     def tearDownClass(cls):
459         tools.FinaliseOutputDir()
460
461     def setUp(self):
462         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
463         self.node = self.dtb.GetNode('/spl-test')
464
465     def testGetInt(self):
466         self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
467         self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
468
469         with self.assertRaises(ValueError) as e:
470             self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
471         self.assertIn("property 'intarray' has list value: expecting a single "
472                       'integer', str(e.exception))
473
474     def testGetString(self):
475         self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
476         self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
477                                                     'test'))
478
479         with self.assertRaises(ValueError) as e:
480             self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
481         self.assertIn("property 'stringarray' has list value: expecting a "
482                       'single string', str(e.exception))
483
484     def testGetBool(self):
485         self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
486         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
487         self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
488         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
489
490     def testGetByte(self):
491         self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
492         self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
493
494         with self.assertRaises(ValueError) as e:
495             fdt_util.GetByte(self.node, 'longbytearray')
496         self.assertIn("property 'longbytearray' has list value: expecting a "
497                       'single byte', str(e.exception))
498
499         with self.assertRaises(ValueError) as e:
500             fdt_util.GetByte(self.node, 'intval')
501         self.assertIn("property 'intval' has length 4, expecting 1",
502                       str(e.exception))
503
504     def testGetPhandleList(self):
505         dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
506         node = dtb.GetNode('/phandle-source2')
507         self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks'))
508         node = dtb.GetNode('/phandle-source')
509         self.assertEqual([1, 2, 11, 3, 12, 13, 1],
510                          fdt_util.GetPhandleList(node, 'clocks'))
511         self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing'))
512
513     def testGetDataType(self):
514         self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
515         self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
516                                                          str))
517         with self.assertRaises(ValueError) as e:
518             self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
519                                                      bool))
520     def testFdtCellsToCpu(self):
521         val = self.node.props['intarray'].value
522         self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
523         self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
524
525         dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
526         node1 = dtb2.GetNode('/test1')
527         val = node1.props['reg'].value
528         self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
529
530         node2 = dtb2.GetNode('/test2')
531         val = node2.props['reg'].value
532         self.assertEqual(0x1234567890123456, fdt_util.fdt_cells_to_cpu(val, 2))
533         self.assertEqual(0x9876543210987654, fdt_util.fdt_cells_to_cpu(val[2:],
534                                                                        2))
535         self.assertEqual(0x12345678, fdt_util.fdt_cells_to_cpu(val, 1))
536
537     def testEnsureCompiled(self):
538         """Test a degenerate case of this function"""
539         dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
540         self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
541
542
543 def RunTestCoverage():
544     """Run the tests and check that we get 100% coverage"""
545     test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
546             ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
547
548
549 def RunTests(args):
550     """Run all the test we have for the fdt model
551
552     Args:
553         args: List of positional args provided to fdt. This can hold a test
554             name to execute (as in 'fdt -t testFdt', for example)
555     """
556     result = unittest.TestResult()
557     sys.argv = [sys.argv[0]]
558     test_name = args and args[0] or None
559     for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
560         if test_name:
561             try:
562                 suite = unittest.TestLoader().loadTestsFromName(test_name, module)
563             except AttributeError:
564                 continue
565         else:
566             suite = unittest.TestLoader().loadTestsFromTestCase(module)
567         suite.run(result)
568
569     print(result)
570     for _, err in result.errors:
571         print(err)
572     for _, err in result.failures:
573         print(err)
574
575 if __name__ != '__main__':
576     sys.exit(1)
577
578 parser = OptionParser()
579 parser.add_option('-B', '--build-dir', type='string', default='b',
580         help='Directory containing the build output')
581 parser.add_option('-P', '--processes', type=int,
582                   help='set number of processes to use for running tests')
583 parser.add_option('-t', '--test', action='store_true', dest='test',
584                   default=False, help='run tests')
585 parser.add_option('-T', '--test-coverage', action='store_true',
586                 default=False, help='run tests and check for 100% coverage')
587 (options, args) = parser.parse_args()
588
589 # Run our meagre tests
590 if options.test:
591     RunTests(args)
592 elif options.test_coverage:
593     RunTestCoverage()