9fef8ed5496ec0bcae2ab8732eac531d6265623f
[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
119 class TestNode(unittest.TestCase):
120     """Test operation of the Node class"""
121
122     @classmethod
123     def setUpClass(cls):
124         tools.PrepareOutputDir(None)
125
126     @classmethod
127     def tearDownClass(cls):
128         tools._FinaliseForTest()
129
130     def setUp(self):
131         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
132         self.node = self.dtb.GetNode('/spl-test')
133
134     def testOffset(self):
135         """Tests that we can obtain the offset of a node"""
136         self.assertTrue(self.node.Offset() > 0)
137
138     def testDelete(self):
139         """Tests that we can delete a property"""
140         node2 = self.dtb.GetNode('/spl-test2')
141         offset1 = node2.Offset()
142         self.node.DeleteProp('intval')
143         offset2 = node2.Offset()
144         self.assertTrue(offset2 < offset1)
145         self.node.DeleteProp('intarray')
146         offset3 = node2.Offset()
147         self.assertTrue(offset3 < offset2)
148         with self.assertRaises(libfdt.FdtException):
149             self.node.DeleteProp('missing')
150
151     def testDeleteGetOffset(self):
152         """Test that property offset update when properties are deleted"""
153         self.node.DeleteProp('intval')
154         prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
155         self.assertEqual(prop.value, value)
156
157     def testFindNode(self):
158         """Tests that we can find a node using the _FindNode() functoin"""
159         node = self.dtb.GetRoot()._FindNode('i2c@0')
160         self.assertEqual('i2c@0', node.name)
161         subnode = node._FindNode('pmic@9')
162         self.assertEqual('pmic@9', subnode.name)
163         self.assertEqual(None, node._FindNode('missing'))
164
165     def testRefreshMissingNode(self):
166         """Test refreshing offsets when an extra node is present in dtb"""
167         # Delete it from our tables, not the device tree
168         del self.dtb._root.subnodes[-1]
169         with self.assertRaises(ValueError) as e:
170             self.dtb.Refresh()
171         self.assertIn('Internal error, offset', str(e.exception))
172
173     def testRefreshExtraNode(self):
174         """Test refreshing offsets when an expected node is missing"""
175         # Delete it from the device tre, not our tables
176         self.dtb.GetFdtObj().del_node(self.node.Offset())
177         with self.assertRaises(ValueError) as e:
178             self.dtb.Refresh()
179         self.assertIn('Internal error, node name mismatch '
180                       'spl-test != spl-test2', str(e.exception))
181
182     def testRefreshMissingProp(self):
183         """Test refreshing offsets when an extra property is present in dtb"""
184         # Delete it from our tables, not the device tree
185         del self.node.props['notstring']
186         with self.assertRaises(ValueError) as e:
187             self.dtb.Refresh()
188         self.assertIn("Internal error, property 'notstring' missing, offset ",
189                       str(e.exception))
190
191
192 class TestProp(unittest.TestCase):
193     """Test operation of the Prop class"""
194
195     @classmethod
196     def setUpClass(cls):
197         tools.PrepareOutputDir(None)
198
199     @classmethod
200     def tearDownClass(cls):
201         tools._FinaliseForTest()
202
203     def setUp(self):
204         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
205         self.node = self.dtb.GetNode('/spl-test')
206         self.fdt = self.dtb.GetFdtObj()
207
208     def testMissingNode(self):
209         self.assertEqual(None, self.dtb.GetNode('missing'))
210
211     def testPhandle(self):
212         dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
213         node = dtb.GetNode('/phandle-source')
214
215     def _ConvertProp(self, prop_name):
216         """Helper function to look up a property in self.node and return it
217
218         Args:
219             Property name to find
220
221         Return fdt.Prop object for this property
222         """
223         p = self.fdt.get_property(self.node.Offset(), prop_name)
224         return fdt.Prop(self.node, -1, prop_name, p)
225
226     def testMakeProp(self):
227         """Test we can convert all the the types that are supported"""
228         prop = self._ConvertProp('boolval')
229         self.assertEqual(fdt.TYPE_BOOL, prop.type)
230         self.assertEqual(True, prop.value)
231
232         prop = self._ConvertProp('intval')
233         self.assertEqual(fdt.TYPE_INT, prop.type)
234         self.assertEqual(1, fdt32_to_cpu(prop.value))
235
236         prop = self._ConvertProp('intarray')
237         self.assertEqual(fdt.TYPE_INT, prop.type)
238         val = [fdt32_to_cpu(val) for val in prop.value]
239         self.assertEqual([2, 3, 4], val)
240
241         prop = self._ConvertProp('byteval')
242         self.assertEqual(fdt.TYPE_BYTE, prop.type)
243         self.assertEqual(5, ord(prop.value))
244
245         prop = self._ConvertProp('longbytearray')
246         self.assertEqual(fdt.TYPE_BYTE, prop.type)
247         val = [ord(val) for val in prop.value]
248         self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val)
249
250         prop = self._ConvertProp('stringval')
251         self.assertEqual(fdt.TYPE_STRING, prop.type)
252         self.assertEqual('message', prop.value)
253
254         prop = self._ConvertProp('stringarray')
255         self.assertEqual(fdt.TYPE_STRING, prop.type)
256         self.assertEqual(['multi-word', 'message'], prop.value)
257
258         prop = self._ConvertProp('notstring')
259         self.assertEqual(fdt.TYPE_BYTE, prop.type)
260         val = [ord(val) for val in prop.value]
261         self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val)
262
263     def testGetEmpty(self):
264         """Tests the GetEmpty() function for the various supported types"""
265         self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL))
266         self.assertEqual(chr(0), fdt.Prop.GetEmpty(fdt.TYPE_BYTE))
267         self.assertEqual(chr(0) * 4, fdt.Prop.GetEmpty(fdt.TYPE_INT))
268         self.assertEqual('', fdt.Prop.GetEmpty(fdt.TYPE_STRING))
269
270     def testGetOffset(self):
271         """Test we can get the offset of a property"""
272         prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
273         self.assertEqual(prop.value, value)
274
275     def testWiden(self):
276         """Test widening of values"""
277         node2 = self.dtb.GetNode('/spl-test2')
278         prop = self.node.props['intval']
279
280         # No action
281         prop2 = node2.props['intval']
282         prop.Widen(prop2)
283         self.assertEqual(fdt.TYPE_INT, prop.type)
284         self.assertEqual(1, fdt32_to_cpu(prop.value))
285
286         # Convert singla value to array
287         prop2 = self.node.props['intarray']
288         prop.Widen(prop2)
289         self.assertEqual(fdt.TYPE_INT, prop.type)
290         self.assertTrue(isinstance(prop.value, list))
291
292         # A 4-byte array looks like a single integer. When widened by a longer
293         # byte array, it should turn into an array.
294         prop = self.node.props['longbytearray']
295         prop2 = node2.props['longbytearray']
296         self.assertFalse(isinstance(prop2.value, list))
297         self.assertEqual(4, len(prop2.value))
298         prop2.Widen(prop)
299         self.assertTrue(isinstance(prop2.value, list))
300         self.assertEqual(9, len(prop2.value))
301
302         # Similarly for a string array
303         prop = self.node.props['stringval']
304         prop2 = node2.props['stringarray']
305         self.assertFalse(isinstance(prop.value, list))
306         self.assertEqual(7, len(prop.value))
307         prop.Widen(prop2)
308         self.assertTrue(isinstance(prop.value, list))
309         self.assertEqual(3, len(prop.value))
310
311         # Enlarging an existing array
312         prop = self.node.props['stringarray']
313         prop2 = node2.props['stringarray']
314         self.assertTrue(isinstance(prop.value, list))
315         self.assertEqual(2, len(prop.value))
316         prop.Widen(prop2)
317         self.assertTrue(isinstance(prop.value, list))
318         self.assertEqual(3, len(prop.value))
319
320
321 class TestFdtUtil(unittest.TestCase):
322     """Tests for the fdt_util module
323
324     This module will likely be mostly replaced at some point, once upstream
325     libfdt has better Python support. For now, this provides tests for current
326     functionality.
327     """
328     @classmethod
329     def setUpClass(cls):
330         tools.PrepareOutputDir(None)
331
332     def setUp(self):
333         self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
334         self.node = self.dtb.GetNode('/spl-test')
335
336     def testGetInt(self):
337         self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
338         self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
339
340         with self.assertRaises(ValueError) as e:
341             self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
342         self.assertIn("property 'intarray' has list value: expecting a single "
343                       'integer', str(e.exception))
344
345     def testGetString(self):
346         self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
347         self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
348                                                     'test'))
349
350         with self.assertRaises(ValueError) as e:
351             self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
352         self.assertIn("property 'stringarray' has list value: expecting a "
353                       'single string', str(e.exception))
354
355     def testGetBool(self):
356         self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
357         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
358         self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
359         self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
360
361     def testFdtCellsToCpu(self):
362         val = self.node.props['intarray'].value
363         self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
364         self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
365
366         dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
367         node2 = dtb2.GetNode('/test1')
368         val = node2.props['reg'].value
369         self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
370
371     def testEnsureCompiled(self):
372         """Test a degenerate case of this function"""
373         dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
374         self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
375
376     def testGetPlainBytes(self):
377         self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
378
379
380 def RunTestCoverage():
381     """Run the tests and check that we get 100% coverage"""
382     test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
383             ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
384
385
386 def RunTests(args):
387     """Run all the test we have for the fdt model
388
389     Args:
390         args: List of positional args provided to fdt. This can hold a test
391             name to execute (as in 'fdt -t testFdt', for example)
392     """
393     result = unittest.TestResult()
394     sys.argv = [sys.argv[0]]
395     test_name = args and args[0] or None
396     for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
397         if test_name:
398             try:
399                 suite = unittest.TestLoader().loadTestsFromName(test_name, module)
400             except AttributeError:
401                 continue
402         else:
403             suite = unittest.TestLoader().loadTestsFromTestCase(module)
404         suite.run(result)
405
406     print result
407     for _, err in result.errors:
408         print err
409     for _, err in result.failures:
410         print err
411
412 if __name__ != '__main__':
413     sys.exit(1)
414
415 parser = OptionParser()
416 parser.add_option('-B', '--build-dir', type='string', default='b',
417         help='Directory containing the build output')
418 parser.add_option('-t', '--test', action='store_true', dest='test',
419                   default=False, help='run tests')
420 parser.add_option('-T', '--test-coverage', action='store_true',
421                 default=False, help='run tests and check for 100% coverage')
422 (options, args) = parser.parse_args()
423
424 # Run our meagre tests
425 if options.test:
426     RunTests(args)
427 elif options.test_coverage:
428     RunTestCoverage()