blob: 03cf4b4f7cfbb30d7687e5f03af38ec7733f0d56 [file] [log] [blame]
Simon Glass0ed50752018-07-06 10:27:24 -06001#!/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
Simon Glass9c526332018-07-06 10:27:28 -060021import fdt_util
Simon Glass0ed50752018-07-06 10:27:24 -060022from fdt_util import fdt32_to_cpu
23import libfdt
24import test_util
25import tools
26
Simon Glass4df8a0c2018-07-06 10:27:29 -060027def _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
Simon Glass0ed50752018-07-06 10:27:24 -060051class 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)
Simon Glass9c526332018-07-06 10:27:28 -060081 self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing'))
Simon Glass0ed50752018-07-06 10:27:24 -060082
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"""
Simon Glass792d2392018-07-06 10:27:27 -0600101 self.assertTrue(isinstance(self.dtb.GetContents(), bytearray))
Simon Glass0ed50752018-07-06 10:27:24 -0600102
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',
Simon Glass9c526332018-07-06 10:27:28 -0600108 'intarray', 'intval', 'longbytearray', 'notstring',
Simon Glass0ed50752018-07-06 10:27:24 -0600109 '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:
Simon Glass9c526332018-07-06 10:27:28 -0600115 fdt.CheckErr(-libfdt.NOTFOUND, 'hello')
Simon Glass0ed50752018-07-06 10:27:24 -0600116 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)
Simon Glass9c526332018-07-06 10:27:28 -0600148 with self.assertRaises(libfdt.FdtException):
149 self.node.DeleteProp('missing')
Simon Glass0ed50752018-07-06 10:27:24 -0600150
Simon Glass4df8a0c2018-07-06 10:27:29 -0600151 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
Simon Glass0ed50752018-07-06 10:27:24 -0600157 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)
Simon Glass9c526332018-07-06 10:27:28 -0600163 self.assertEqual(None, node._FindNode('missing'))
Simon Glass0ed50752018-07-06 10:27:24 -0600164
Simon Glass4df8a0c2018-07-06 10:27:29 -0600165 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
Simon Glass0ed50752018-07-06 10:27:24 -0600191
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
Simon Glassc5eddc82018-07-06 10:27:30 -0600208 def testMissingNode(self):
209 self.assertEqual(None, self.dtb.GetNode('missing'))
210
Simon Glass9c526332018-07-06 10:27:28 -0600211 def testPhandle(self):
212 dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts')
Simon Glass609e2b12018-07-06 10:27:31 -0600213 node = dtb.GetNode('/phandle-source2')
214 prop = node.props['clocks']
215 self.assertTrue(fdt32_to_cpu(prop.value) > 0)
Simon Glass9c526332018-07-06 10:27:28 -0600216
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
Simon Glass0ed50752018-07-06 10:27:24 -0600265 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"""
Simon Glass4df8a0c2018-07-06 10:27:29 -0600274 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
275 self.assertEqual(prop.value, value)
Simon Glass0ed50752018-07-06 10:27:24 -0600276
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
Simon Glasse80c5562018-07-06 10:27:38 -0600322 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
Simon Glass0ed50752018-07-06 10:27:24 -0600342
Simon Glass9c526332018-07-06 10:27:28 -0600343class 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
Simon Glass91710b32018-07-17 13:25:32 -0600383 def testGetDataType(self):
384 self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
385 self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
386 str))
387 with self.assertRaises(ValueError) as e:
388 self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
389 bool))
390
Simon Glass9c526332018-07-06 10:27:28 -0600391 def testFdtCellsToCpu(self):
392 val = self.node.props['intarray'].value
393 self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
394 self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
395
396 dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts')
397 node2 = dtb2.GetNode('/test1')
398 val = node2.props['reg'].value
399 self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
400
401 def testEnsureCompiled(self):
402 """Test a degenerate case of this function"""
403 dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts')
404 self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
405
406 def testGetPlainBytes(self):
407 self.assertEqual('fred', fdt_util.get_plain_bytes('fred'))
408
409
410def RunTestCoverage():
411 """Run the tests and check that we get 100% coverage"""
412 test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
413 ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
414
415
Simon Glass0ed50752018-07-06 10:27:24 -0600416def RunTests(args):
417 """Run all the test we have for the fdt model
418
419 Args:
420 args: List of positional args provided to fdt. This can hold a test
421 name to execute (as in 'fdt -t testFdt', for example)
422 """
423 result = unittest.TestResult()
424 sys.argv = [sys.argv[0]]
425 test_name = args and args[0] or None
Simon Glass9c526332018-07-06 10:27:28 -0600426 for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
Simon Glass0ed50752018-07-06 10:27:24 -0600427 if test_name:
428 try:
429 suite = unittest.TestLoader().loadTestsFromName(test_name, module)
430 except AttributeError:
431 continue
432 else:
433 suite = unittest.TestLoader().loadTestsFromTestCase(module)
434 suite.run(result)
435
436 print result
437 for _, err in result.errors:
438 print err
439 for _, err in result.failures:
440 print err
441
442if __name__ != '__main__':
443 sys.exit(1)
444
445parser = OptionParser()
Simon Glass9c526332018-07-06 10:27:28 -0600446parser.add_option('-B', '--build-dir', type='string', default='b',
447 help='Directory containing the build output')
Simon Glass0ed50752018-07-06 10:27:24 -0600448parser.add_option('-t', '--test', action='store_true', dest='test',
449 default=False, help='run tests')
Simon Glass9c526332018-07-06 10:27:28 -0600450parser.add_option('-T', '--test-coverage', action='store_true',
451 default=False, help='run tests and check for 100% coverage')
Simon Glass0ed50752018-07-06 10:27:24 -0600452(options, args) = parser.parse_args()
453
454# Run our meagre tests
455if options.test:
456 RunTests(args)
Simon Glass9c526332018-07-06 10:27:28 -0600457elif options.test_coverage:
458 RunTestCoverage()