xref: /openbmc/u-boot/tools/dtoc/test_fdt.py (revision ad7061ed)
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 testFdtCellsToCpu(self):
384        val = self.node.props['intarray'].value
385        self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
386        self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
387
388        dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
389        node2 = dtb2.GetNode('/test1')
390        val = node2.props['reg'].value
391        self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
392
393    def testEnsureCompiled(self):
394        """Test a degenerate case of this function"""
395        dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
396        self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
397
398    def testGetPlainBytes(self):
399        self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
400
401
402def RunTestCoverage():
403    """Run the tests and check that we get 100% coverage"""
404    test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
405            ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
406
407
408def RunTests(args):
409    """Run all the test we have for the fdt model
410
411    Args:
412        args: List of positional args provided to fdt. This can hold a test
413            name to execute (as in 'fdt -t testFdt', for example)
414    """
415    result = unittest.TestResult()
416    sys.argv = [sys.argv[0]]
417    test_name = args and args[0] or None
418    for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
419        if test_name:
420            try:
421                suite = unittest.TestLoader().loadTestsFromName(test_name, module)
422            except AttributeError:
423                continue
424        else:
425            suite = unittest.TestLoader().loadTestsFromTestCase(module)
426        suite.run(result)
427
428    print result
429    for _, err in result.errors:
430        print err
431    for _, err in result.failures:
432        print err
433
434if __name__ != '__main__':
435    sys.exit(1)
436
437parser = OptionParser()
438parser.add_option('-B', '--build-dir', type='string', default='b',
439        help='Directory containing the build output')
440parser.add_option('-t', '--test', action='store_true', dest='test',
441                  default=False, help='run tests')
442parser.add_option('-T', '--test-coverage', action='store_true',
443                default=False, help='run tests and check for 100% coverage')
444(options, args) = parser.parse_args()
445
446# Run our meagre tests
447if options.test:
448    RunTests(args)
449elif options.test_coverage:
450    RunTestCoverage()
451