xref: /openbmc/u-boot/tools/dtoc/test_fdt.py (revision ef64e782)
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        self.dtb.Sync(auto_resize=True)
356
357    def testAddNode(self):
358        self.fdt.pack()
359        self.node.AddSubnode('subnode')
360        with self.assertRaises(libfdt.FdtException) as e:
361            self.dtb.Sync(auto_resize=False)
362        self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
363
364        self.dtb.Sync(auto_resize=True)
365        offset = self.fdt.path_offset('/spl-test/subnode')
366        self.assertTrue(offset > 0)
367
368    def testAddMore(self):
369        """Test various other methods for adding and setting properties"""
370        self.node.AddZeroProp('one')
371        self.dtb.Sync(auto_resize=True)
372        data = self.fdt.getprop(self.node.Offset(), 'one')
373        self.assertEqual(0, fdt32_to_cpu(data))
374
375        self.node.SetInt('one', 1)
376        self.dtb.Sync(auto_resize=False)
377        data = self.fdt.getprop(self.node.Offset(), 'one')
378        self.assertEqual(1, fdt32_to_cpu(data))
379
380        val = '123' + chr(0) + '456'
381        self.node.AddString('string', val)
382        self.dtb.Sync(auto_resize=True)
383        data = self.fdt.getprop(self.node.Offset(), 'string')
384        self.assertEqual(val + '\0', data)
385
386        self.fdt.pack()
387        self.node.SetString('string', val + 'x')
388        with self.assertRaises(libfdt.FdtException) as e:
389            self.dtb.Sync(auto_resize=False)
390        self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
391        self.node.SetString('string', val[:-1])
392
393        prop = self.node.props['string']
394        prop.SetData(val)
395        self.dtb.Sync(auto_resize=False)
396        data = self.fdt.getprop(self.node.Offset(), 'string')
397        self.assertEqual(val, data)
398
399        self.node.AddEmptyProp('empty', 5)
400        self.dtb.Sync(auto_resize=True)
401        prop = self.node.props['empty']
402        prop.SetData(val)
403        self.dtb.Sync(auto_resize=False)
404        data = self.fdt.getprop(self.node.Offset(), 'empty')
405        self.assertEqual(val, data)
406
407        self.node.SetData('empty', '123')
408        self.assertEqual('123', prop.bytes)
409
410    def testFromData(self):
411        dtb2 = fdt.Fdt.FromData(self.dtb.GetContents())
412        self.assertEqual(dtb2.GetContents(), self.dtb.GetContents())
413
414        self.node.AddEmptyProp('empty', 5)
415        self.dtb.Sync(auto_resize=True)
416        self.assertTrue(dtb2.GetContents() != self.dtb.GetContents())
417
418
419class TestFdtUtil(unittest.TestCase):
420    """Tests for the fdt_util module
421
422    This module will likely be mostly replaced at some point, once upstream
423    libfdt has better Python support. For now, this provides tests for current
424    functionality.
425    """
426    @classmethod
427    def setUpClass(cls):
428        tools.PrepareOutputDir(None)
429
430    def setUp(self):
431        self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
432        self.node = self.dtb.GetNode('/spl-test')
433
434    def testGetInt(self):
435        self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
436        self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
437
438        with self.assertRaises(ValueError) as e:
439            self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
440        self.assertIn("property 'intarray' has list value: expecting a single "
441                      'integer', str(e.exception))
442
443    def testGetString(self):
444        self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
445        self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
446                                                    'test'))
447
448        with self.assertRaises(ValueError) as e:
449            self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
450        self.assertIn("property 'stringarray' has list value: expecting a "
451                      'single string', str(e.exception))
452
453    def testGetBool(self):
454        self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
455        self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
456        self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
457        self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
458
459    def testGetByte(self):
460        self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
461        self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
462
463        with self.assertRaises(ValueError) as e:
464            fdt_util.GetByte(self.node, 'longbytearray')
465        self.assertIn("property 'longbytearray' has list value: expecting a "
466                      'single byte', str(e.exception))
467
468        with self.assertRaises(ValueError) as e:
469            fdt_util.GetByte(self.node, 'intval')
470        self.assertIn("property 'intval' has length 4, expecting 1",
471                      str(e.exception))
472
473    def testGetPhandleList(self):
474        dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
475        node = dtb.GetNode('/phandle-source2')
476        self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks'))
477        node = dtb.GetNode('/phandle-source')
478        self.assertEqual([1, 2, 11, 3, 12, 13, 1],
479                         fdt_util.GetPhandleList(node, 'clocks'))
480        self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing'))
481
482    def testGetDataType(self):
483        self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
484        self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
485                                                         str))
486        with self.assertRaises(ValueError) as e:
487            self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
488                                                     bool))
489    def testFdtCellsToCpu(self):
490        val = self.node.props['intarray'].value
491        self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
492        self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
493
494        dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
495        node2 = dtb2.GetNode('/test1')
496        val = node2.props['reg'].value
497        self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
498
499    def testEnsureCompiled(self):
500        """Test a degenerate case of this function"""
501        dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
502        self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
503
504    def testGetPlainBytes(self):
505        self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
506
507
508def RunTestCoverage():
509    """Run the tests and check that we get 100% coverage"""
510    test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
511            ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
512
513
514def RunTests(args):
515    """Run all the test we have for the fdt model
516
517    Args:
518        args: List of positional args provided to fdt. This can hold a test
519            name to execute (as in 'fdt -t testFdt', for example)
520    """
521    result = unittest.TestResult()
522    sys.argv = [sys.argv[0]]
523    test_name = args and args[0] or None
524    for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
525        if test_name:
526            try:
527                suite = unittest.TestLoader().loadTestsFromName(test_name, module)
528            except AttributeError:
529                continue
530        else:
531            suite = unittest.TestLoader().loadTestsFromTestCase(module)
532        suite.run(result)
533
534    print result
535    for _, err in result.errors:
536        print err
537    for _, err in result.failures:
538        print err
539
540if __name__ != '__main__':
541    sys.exit(1)
542
543parser = OptionParser()
544parser.add_option('-B', '--build-dir', type='string', default='b',
545        help='Directory containing the build output')
546parser.add_option('-t', '--test', action='store_true', dest='test',
547                  default=False, help='run tests')
548parser.add_option('-T', '--test-coverage', action='store_true',
549                default=False, help='run tests and check for 100% coverage')
550(options, args) = parser.parse_args()
551
552# Run our meagre tests
553if options.test:
554    RunTests(args)
555elif options.test_coverage:
556    RunTestCoverage()
557