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