xref: /openbmc/u-boot/tools/dtoc/test_fdt.py (revision 8d773c4a)
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.FinaliseOutputDir()
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.FinaliseOutputDir()
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.FinaliseOutputDir()
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    @classmethod
431    def tearDownClass(cls):
432        tools.FinaliseOutputDir()
433
434    def setUp(self):
435        self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
436        self.node = self.dtb.GetNode('/spl-test')
437
438    def testGetInt(self):
439        self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
440        self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
441
442        with self.assertRaises(ValueError) as e:
443            self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
444        self.assertIn("property 'intarray' has list value: expecting a single "
445                      'integer', str(e.exception))
446
447    def testGetString(self):
448        self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
449        self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
450                                                    'test'))
451
452        with self.assertRaises(ValueError) as e:
453            self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
454        self.assertIn("property 'stringarray' has list value: expecting a "
455                      'single string', str(e.exception))
456
457    def testGetBool(self):
458        self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
459        self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
460        self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
461        self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
462
463    def testGetByte(self):
464        self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
465        self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
466
467        with self.assertRaises(ValueError) as e:
468            fdt_util.GetByte(self.node, 'longbytearray')
469        self.assertIn("property 'longbytearray' has list value: expecting a "
470                      'single byte', str(e.exception))
471
472        with self.assertRaises(ValueError) as e:
473            fdt_util.GetByte(self.node, 'intval')
474        self.assertIn("property 'intval' has length 4, expecting 1",
475                      str(e.exception))
476
477    def testGetPhandleList(self):
478        dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
479        node = dtb.GetNode('/phandle-source2')
480        self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks'))
481        node = dtb.GetNode('/phandle-source')
482        self.assertEqual([1, 2, 11, 3, 12, 13, 1],
483                         fdt_util.GetPhandleList(node, 'clocks'))
484        self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing'))
485
486    def testGetDataType(self):
487        self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
488        self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
489                                                         str))
490        with self.assertRaises(ValueError) as e:
491            self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
492                                                     bool))
493    def testFdtCellsToCpu(self):
494        val = self.node.props['intarray'].value
495        self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
496        self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
497
498        dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
499        node2 = dtb2.GetNode('/test1')
500        val = node2.props['reg'].value
501        self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
502
503    def testEnsureCompiled(self):
504        """Test a degenerate case of this function"""
505        dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
506        self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
507
508    def testGetPlainBytes(self):
509        self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
510
511
512def RunTestCoverage():
513    """Run the tests and check that we get 100% coverage"""
514    test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
515            ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
516
517
518def RunTests(args):
519    """Run all the test we have for the fdt model
520
521    Args:
522        args: List of positional args provided to fdt. This can hold a test
523            name to execute (as in 'fdt -t testFdt', for example)
524    """
525    result = unittest.TestResult()
526    sys.argv = [sys.argv[0]]
527    test_name = args and args[0] or None
528    for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
529        if test_name:
530            try:
531                suite = unittest.TestLoader().loadTestsFromName(test_name, module)
532            except AttributeError:
533                continue
534        else:
535            suite = unittest.TestLoader().loadTestsFromTestCase(module)
536        suite.run(result)
537
538    print result
539    for _, err in result.errors:
540        print err
541    for _, err in result.failures:
542        print err
543
544if __name__ != '__main__':
545    sys.exit(1)
546
547parser = OptionParser()
548parser.add_option('-B', '--build-dir', type='string', default='b',
549        help='Directory containing the build output')
550parser.add_option('-P', '--processes', type=int,
551                  help='set number of processes to use for running tests')
552parser.add_option('-t', '--test', action='store_true', dest='test',
553                  default=False, help='run tests')
554parser.add_option('-T', '--test-coverage', action='store_true',
555                default=False, help='run tests and check for 100% coverage')
556(options, args) = parser.parse_args()
557
558# Run our meagre tests
559if options.test:
560    RunTests(args)
561elif options.test_coverage:
562    RunTestCoverage()
563