xref: /openbmc/u-boot/tools/dtoc/test_fdt.py (revision 1d85888c)
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
119class 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
192class 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-source2')
214        prop = node.props['clocks']
215        self.assertTrue(fdt32_to_cpu(prop.value) > 0)
216
217    def _ConvertProp(self, prop_name):
218        """Helper function to look up a property in self.node and return it
219
220        Args:
221            Property name to find
222
223        Return fdt.Prop object for this property
224        """
225        p = self.fdt.get_property(self.node.Offset(), prop_name)
226        return fdt.Prop(self.node, -1, prop_name, p)
227
228    def testMakeProp(self):
229        """Test we can convert all the the types that are supported"""
230        prop = self._ConvertProp('boolval')
231        self.assertEqual(fdt.TYPE_BOOL, prop.type)
232        self.assertEqual(True, prop.value)
233
234        prop = self._ConvertProp('intval')
235        self.assertEqual(fdt.TYPE_INT, prop.type)
236        self.assertEqual(1, fdt32_to_cpu(prop.value))
237
238        prop = self._ConvertProp('intarray')
239        self.assertEqual(fdt.TYPE_INT, prop.type)
240        val = [fdt32_to_cpu(val) for val in prop.value]
241        self.assertEqual([2, 3, 4], val)
242
243        prop = self._ConvertProp('byteval')
244        self.assertEqual(fdt.TYPE_BYTE, prop.type)
245        self.assertEqual(5, ord(prop.value))
246
247        prop = self._ConvertProp('longbytearray')
248        self.assertEqual(fdt.TYPE_BYTE, prop.type)
249        val = [ord(val) for val in prop.value]
250        self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val)
251
252        prop = self._ConvertProp('stringval')
253        self.assertEqual(fdt.TYPE_STRING, prop.type)
254        self.assertEqual('message', prop.value)
255
256        prop = self._ConvertProp('stringarray')
257        self.assertEqual(fdt.TYPE_STRING, prop.type)
258        self.assertEqual(['multi-word', 'message'], prop.value)
259
260        prop = self._ConvertProp('notstring')
261        self.assertEqual(fdt.TYPE_BYTE, prop.type)
262        val = [ord(val) for val in prop.value]
263        self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val)
264
265    def testGetEmpty(self):
266        """Tests the GetEmpty() function for the various supported types"""
267        self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL))
268        self.assertEqual(chr(0), fdt.Prop.GetEmpty(fdt.TYPE_BYTE))
269        self.assertEqual(chr(0) * 4, fdt.Prop.GetEmpty(fdt.TYPE_INT))
270        self.assertEqual('', fdt.Prop.GetEmpty(fdt.TYPE_STRING))
271
272    def testGetOffset(self):
273        """Test we can get the offset of a property"""
274        prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
275        self.assertEqual(prop.value, value)
276
277    def testWiden(self):
278        """Test widening of values"""
279        node2 = self.dtb.GetNode('/spl-test2')
280        prop = self.node.props['intval']
281
282        # No action
283        prop2 = node2.props['intval']
284        prop.Widen(prop2)
285        self.assertEqual(fdt.TYPE_INT, prop.type)
286        self.assertEqual(1, fdt32_to_cpu(prop.value))
287
288        # Convert singla value to array
289        prop2 = self.node.props['intarray']
290        prop.Widen(prop2)
291        self.assertEqual(fdt.TYPE_INT, prop.type)
292        self.assertTrue(isinstance(prop.value, list))
293
294        # A 4-byte array looks like a single integer. When widened by a longer
295        # byte array, it should turn into an array.
296        prop = self.node.props['longbytearray']
297        prop2 = node2.props['longbytearray']
298        self.assertFalse(isinstance(prop2.value, list))
299        self.assertEqual(4, len(prop2.value))
300        prop2.Widen(prop)
301        self.assertTrue(isinstance(prop2.value, list))
302        self.assertEqual(9, len(prop2.value))
303
304        # Similarly for a string array
305        prop = self.node.props['stringval']
306        prop2 = node2.props['stringarray']
307        self.assertFalse(isinstance(prop.value, list))
308        self.assertEqual(7, len(prop.value))
309        prop.Widen(prop2)
310        self.assertTrue(isinstance(prop.value, list))
311        self.assertEqual(3, len(prop.value))
312
313        # Enlarging an existing array
314        prop = self.node.props['stringarray']
315        prop2 = node2.props['stringarray']
316        self.assertTrue(isinstance(prop.value, list))
317        self.assertEqual(2, len(prop.value))
318        prop.Widen(prop2)
319        self.assertTrue(isinstance(prop.value, list))
320        self.assertEqual(3, len(prop.value))
321
322    def testAdd(self):
323        """Test adding properties"""
324        self.fdt.pack()
325        # This function should automatically expand the device tree
326        self.node.AddZeroProp('one')
327        self.node.AddZeroProp('two')
328        self.node.AddZeroProp('three')
329
330        # Updating existing properties should be OK, since the device-tree size
331        # does not change
332        self.fdt.pack()
333        self.node.SetInt('one', 1)
334        self.node.SetInt('two', 2)
335        self.node.SetInt('three', 3)
336
337        # This should fail since it would need to increase the device-tree size
338        with self.assertRaises(libfdt.FdtException) as e:
339            self.node.SetInt('four', 4)
340        self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
341
342
343class TestFdtUtil(unittest.TestCase):
344    """Tests for the fdt_util module
345
346    This module will likely be mostly replaced at some point, once upstream
347    libfdt has better Python support. For now, this provides tests for current
348    functionality.
349    """
350    @classmethod
351    def setUpClass(cls):
352        tools.PrepareOutputDir(None)
353
354    def setUp(self):
355        self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts')
356        self.node = self.dtb.GetNode('/spl-test')
357
358    def testGetInt(self):
359        self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
360        self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
361
362        with self.assertRaises(ValueError) as e:
363            self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
364        self.assertIn("property 'intarray' has list value: expecting a single "
365                      'integer', str(e.exception))
366
367    def testGetString(self):
368        self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
369        self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
370                                                    'test'))
371
372        with self.assertRaises(ValueError) as e:
373            self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
374        self.assertIn("property 'stringarray' has list value: expecting a "
375                      'single string', str(e.exception))
376
377    def testGetBool(self):
378        self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
379        self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
380        self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
381        self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
382
383    def testGetByte(self):
384        self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
385        self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
386
387        with self.assertRaises(ValueError) as e:
388            fdt_util.GetByte(self.node, 'longbytearray')
389        self.assertIn("property 'longbytearray' has list value: expecting a "
390                      'single byte', str(e.exception))
391
392        with self.assertRaises(ValueError) as e:
393            fdt_util.GetByte(self.node, 'intval')
394        self.assertIn("property 'intval' has length 4, expecting 1",
395                      str(e.exception))
396
397    def testGetDataType(self):
398        self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
399        self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
400                                                         str))
401        with self.assertRaises(ValueError) as e:
402            self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
403                                                     bool))
404    def testFdtCellsToCpu(self):
405        val = self.node.props['intarray'].value
406        self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
407        self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
408
409        dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
410        node2 = dtb2.GetNode('/test1')
411        val = node2.props['reg'].value
412        self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
413
414    def testEnsureCompiled(self):
415        """Test a degenerate case of this function"""
416        dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
417        self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
418
419    def testGetPlainBytes(self):
420        self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
421
422
423def RunTestCoverage():
424    """Run the tests and check that we get 100% coverage"""
425    test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
426            ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
427
428
429def RunTests(args):
430    """Run all the test we have for the fdt model
431
432    Args:
433        args: List of positional args provided to fdt. This can hold a test
434            name to execute (as in 'fdt -t testFdt', for example)
435    """
436    result = unittest.TestResult()
437    sys.argv = [sys.argv[0]]
438    test_name = args and args[0] or None
439    for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
440        if test_name:
441            try:
442                suite = unittest.TestLoader().loadTestsFromName(test_name, module)
443            except AttributeError:
444                continue
445        else:
446            suite = unittest.TestLoader().loadTestsFromTestCase(module)
447        suite.run(result)
448
449    print result
450    for _, err in result.errors:
451        print err
452    for _, err in result.failures:
453        print err
454
455if __name__ != '__main__':
456    sys.exit(1)
457
458parser = OptionParser()
459parser.add_option('-B', '--build-dir', type='string', default='b',
460        help='Directory containing the build output')
461parser.add_option('-t', '--test', action='store_true', dest='test',
462                  default=False, help='run tests')
463parser.add_option('-T', '--test-coverage', action='store_true',
464                default=False, help='run tests and check for 100% coverage')
465(options, args) = parser.parse_args()
466
467# Run our meagre tests
468if options.test:
469    RunTests(args)
470elif options.test_coverage:
471    RunTestCoverage()
472