dtoc: Fixed endianness in Prop.GetEmpty()
[oweals/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
356     def testAddNode(self):
357         self.fdt.pack()
358
359
360 class TestFdtUtil(unittest.TestCase):
361     """Tests for the fdt_util module
362
363     This module will likely be mostly replaced at some point, once upstream
364     libfdt has better Python support. For now, this provides tests for current
365     functionality.
366     """
367     @classmethod
368     def setUpClass(cls):
369         tools.PrepareOutputDir(None)
370
371     def setUp(self):
372         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
373         self.node = self.dtb.GetNode('/spl-test')
374
375     def testGetInt(self):
376         self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
377         self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
378
379         with self.assertRaises(ValueError) as e:
380             self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
381         self.assertIn("property 'intarray' has list value: expecting a single "
382                       'integer', str(e.exception))
383
384     def testGetString(self):
385         self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
386         self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
387                                                     'test'))
388
389         with self.assertRaises(ValueError) as e:
390             self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
391         self.assertIn("property 'stringarray' has list value: expecting a "
392                       'single string', str(e.exception))
393
394     def testGetBool(self):
395         self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
396         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
397         self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
398         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
399
400     def testGetByte(self):
401         self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
402         self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
403
404         with self.assertRaises(ValueError) as e:
405             fdt_util.GetByte(self.node, 'longbytearray')
406         self.assertIn("property 'longbytearray' has list value: expecting a "
407                       'single byte', str(e.exception))
408
409         with self.assertRaises(ValueError) as e:
410             fdt_util.GetByte(self.node, 'intval')
411         self.assertIn("property 'intval' has length 4, expecting 1",
412                       str(e.exception))
413
414     def testGetPhandleList(self):
415         dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
416         node = dtb.GetNode('/phandle-source2')
417         self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks'))
418         node = dtb.GetNode('/phandle-source')
419         self.assertEqual([1, 2, 11, 3, 12, 13, 1],
420                          fdt_util.GetPhandleList(node, 'clocks'))
421         self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing'))
422
423     def testGetDataType(self):
424         self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
425         self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
426                                                          str))
427         with self.assertRaises(ValueError) as e:
428             self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
429                                                      bool))
430     def testFdtCellsToCpu(self):
431         val = self.node.props['intarray'].value
432         self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
433         self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
434
435         dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
436         node2 = dtb2.GetNode('/test1')
437         val = node2.props['reg'].value
438         self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
439
440     def testEnsureCompiled(self):
441         """Test a degenerate case of this function"""
442         dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
443         self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
444
445     def testGetPlainBytes(self):
446         self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
447
448
449 def RunTestCoverage():
450     """Run the tests and check that we get 100% coverage"""
451     test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
452             ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
453
454
455 def RunTests(args):
456     """Run all the test we have for the fdt model
457
458     Args:
459         args: List of positional args provided to fdt. This can hold a test
460             name to execute (as in 'fdt -t testFdt', for example)
461     """
462     result = unittest.TestResult()
463     sys.argv = [sys.argv[0]]
464     test_name = args and args[0] or None
465     for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
466         if test_name:
467             try:
468                 suite = unittest.TestLoader().loadTestsFromName(test_name, module)
469             except AttributeError:
470                 continue
471         else:
472             suite = unittest.TestLoader().loadTestsFromTestCase(module)
473         suite.run(result)
474
475     print result
476     for _, err in result.errors:
477         print err
478     for _, err in result.failures:
479         print err
480
481 if __name__ != '__main__':
482     sys.exit(1)
483
484 parser = OptionParser()
485 parser.add_option('-B', '--build-dir', type='string', default='b',
486         help='Directory containing the build output')
487 parser.add_option('-t', '--test', action='store_true', dest='test',
488                   default=False, help='run tests')
489 parser.add_option('-T', '--test-coverage', action='store_true',
490                 default=False, help='run tests and check for 100% coverage')
491 (options, args) = parser.parse_args()
492
493 # Run our meagre tests
494 if options.test:
495     RunTests(args)
496 elif options.test_coverage:
497     RunTestCoverage()