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