2 # SPDX-License-Identifier: GPL-2.0+
3 # Copyright (c) 2018 Google, Inc
4 # Written by Simon Glass <sjg@chromium.org>
7 from optparse import OptionParser
15 # Bring in the patman libraries
16 our_path = os.path.dirname(os.path.realpath(__file__))
17 sys.path.insert(1, os.path.join(our_path, '..'))
20 from dtoc import fdt_util
21 from dtoc.fdt_util import fdt32_to_cpu
22 from fdt import Type, BytesToValue
24 from patman import command
25 from patman import test_util
26 from patman import tools
28 def _GetPropertyValue(dtb, node, prop_name):
29 """Low-level function to get the property value based on its offset
31 This looks directly in the device tree at the property's offset to find
32 its value. It is useful as a check that the property is in the correct
37 prop_name: Property name to find
42 Value of property as a string (found using property offset)
44 prop = node.props[prop_name]
46 # Add 12, which is sizeof(struct fdt_property), to get to start of data
47 offset = prop.GetOffset() + 12
48 data = dtb.GetContents()[offset:offset + len(prop.value)]
49 return prop, [chr(x) for x in data]
51 def find_dtb_file(dts_fname):
52 """Locate a test file in the test/ directory
55 dts_fname (str): Filename to find, e.g. 'dtoc_test_simple.dts]
58 str: Path to the test filename
60 return os.path.join('tools/dtoc/test', dts_fname)
63 class TestFdt(unittest.TestCase):
64 """Tests for the Fdt module
66 This includes unit tests for some functions and functional tests for the fdt
71 tools.PrepareOutputDir(None)
74 def tearDownClass(cls):
75 tools.FinaliseOutputDir()
78 self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts'))
81 """Test that we can open an Fdt"""
83 root = self.dtb.GetRoot()
84 self.assertTrue(isinstance(root, fdt.Node))
86 def testGetNode(self):
87 """Test the GetNode() method"""
88 node = self.dtb.GetNode('/spl-test')
89 self.assertTrue(isinstance(node, fdt.Node))
91 node = self.dtb.GetNode('/i2c@0/pmic@9')
92 self.assertTrue(isinstance(node, fdt.Node))
93 self.assertEqual('pmic@9', node.name)
94 self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing'))
96 node = self.dtb.GetNode('/')
97 self.assertTrue(isinstance(node, fdt.Node))
98 self.assertEqual(0, node.Offset())
101 """Check that we can flush the device tree out to its file"""
102 fname = self.dtb._fname
103 with open(fname, 'rb') as fd:
106 with self.assertRaises(IOError):
109 with open(fname, 'rb') as fd:
113 """Test that packing a device tree works"""
116 def testGetFdt(self):
117 """Tetst that we can access the raw device-tree data"""
118 self.assertTrue(isinstance(self.dtb.GetContents(), bytearray))
120 def testGetProps(self):
121 """Tests obtaining a list of properties"""
122 node = self.dtb.GetNode('/spl-test')
123 props = self.dtb.GetProps(node)
124 self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible',
125 'intarray', 'intval', 'longbytearray', 'notstring',
126 'stringarray', 'stringval', 'u-boot,dm-pre-reloc'],
127 sorted(props.keys()))
129 def testCheckError(self):
130 """Tests the ChecKError() function"""
131 with self.assertRaises(ValueError) as e:
132 fdt.CheckErr(-libfdt.NOTFOUND, 'hello')
133 self.assertIn('FDT_ERR_NOTFOUND: hello', str(e.exception))
135 def testGetFdt(self):
136 node = self.dtb.GetNode('/spl-test')
137 self.assertEqual(self.dtb, node.GetFdt())
139 def testBytesToValue(self):
140 self.assertEqual(BytesToValue(b'this\0is\0'),
141 (Type.STRING, ['this', 'is']))
143 class TestNode(unittest.TestCase):
144 """Test operation of the Node class"""
148 tools.PrepareOutputDir(None)
151 def tearDownClass(cls):
152 tools.FinaliseOutputDir()
155 self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts'))
156 self.node = self.dtb.GetNode('/spl-test')
157 self.fdt = self.dtb.GetFdtObj()
159 def testOffset(self):
160 """Tests that we can obtain the offset of a node"""
161 self.assertTrue(self.node.Offset() > 0)
163 def testDelete(self):
164 """Tests that we can delete a property"""
165 node2 = self.dtb.GetNode('/spl-test2')
166 offset1 = node2.Offset()
167 self.node.DeleteProp('intval')
168 offset2 = node2.Offset()
169 self.assertTrue(offset2 < offset1)
170 self.node.DeleteProp('intarray')
171 offset3 = node2.Offset()
172 self.assertTrue(offset3 < offset2)
173 with self.assertRaises(libfdt.FdtException):
174 self.node.DeleteProp('missing')
176 def testDeleteGetOffset(self):
177 """Test that property offset update when properties are deleted"""
178 self.node.DeleteProp('intval')
179 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
180 self.assertEqual(prop.value, value)
182 def testFindNode(self):
183 """Tests that we can find a node using the FindNode() functoin"""
184 node = self.dtb.GetRoot().FindNode('i2c@0')
185 self.assertEqual('i2c@0', node.name)
186 subnode = node.FindNode('pmic@9')
187 self.assertEqual('pmic@9', subnode.name)
188 self.assertEqual(None, node.FindNode('missing'))
190 def testRefreshMissingNode(self):
191 """Test refreshing offsets when an extra node is present in dtb"""
192 # Delete it from our tables, not the device tree
193 del self.dtb._root.subnodes[-1]
194 with self.assertRaises(ValueError) as e:
196 self.assertIn('Internal error, offset', str(e.exception))
198 def testRefreshExtraNode(self):
199 """Test refreshing offsets when an expected node is missing"""
200 # Delete it from the device tre, not our tables
201 self.fdt.del_node(self.node.Offset())
202 with self.assertRaises(ValueError) as e:
204 self.assertIn('Internal error, node name mismatch '
205 'spl-test != spl-test2', str(e.exception))
207 def testRefreshMissingProp(self):
208 """Test refreshing offsets when an extra property is present in dtb"""
209 # Delete it from our tables, not the device tree
210 del self.node.props['notstring']
211 with self.assertRaises(ValueError) as e:
213 self.assertIn("Internal error, node '/spl-test' property 'notstring' missing, offset ",
216 def testLookupPhandle(self):
217 """Test looking up a single phandle"""
218 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_phandle.dts'))
219 node = dtb.GetNode('/phandle-source2')
220 prop = node.props['clocks']
221 target = dtb.GetNode('/phandle-target')
222 self.assertEqual(target, dtb.LookupPhandle(fdt32_to_cpu(prop.value)))
224 def testAddNodeSpace(self):
225 """Test adding a single node when out of space"""
227 self.node.AddSubnode('subnode')
228 with self.assertRaises(libfdt.FdtException) as e:
229 self.dtb.Sync(auto_resize=False)
230 self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
232 self.dtb.Sync(auto_resize=True)
233 offset = self.fdt.path_offset('/spl-test/subnode')
234 self.assertTrue(offset > 0)
236 def testAddNodes(self):
237 """Test adding various subnode and properies"""
238 node = self.dtb.GetNode('/i2c@0')
240 # Add one more node next to the pmic one
241 sn1 = node.AddSubnode('node-one')
242 sn1.AddInt('integer-a', 12)
243 sn1.AddInt('integer-b', 23)
245 # Sync so that everything is clean
246 self.dtb.Sync(auto_resize=True)
248 # Add two subnodes next to pmic and node-one
249 sn2 = node.AddSubnode('node-two')
250 sn2.AddInt('integer-2a', 34)
251 sn2.AddInt('integer-2b', 45)
253 sn3 = node.AddSubnode('node-three')
254 sn3.AddInt('integer-3', 123)
256 # Add a property to the node after i2c@0 to check that this is not
257 # disturbed by adding a subnode to i2c@0
258 orig_node = self.dtb.GetNode('/orig-node')
259 orig_node.AddInt('integer-4', 456)
261 # Add a property to the pmic node to check that pmic properties are not
263 pmic = self.dtb.GetNode('/i2c@0/pmic@9')
264 pmic.AddInt('integer-5', 567)
266 self.dtb.Sync(auto_resize=True)
268 def testRefreshNameMismatch(self):
269 """Test name mismatch when syncing nodes and properties"""
270 prop = self.node.AddInt('integer-a', 12)
272 wrong_offset = self.dtb.GetNode('/i2c@0')._offset
273 self.node._offset = wrong_offset
274 with self.assertRaises(ValueError) as e:
276 self.assertIn("Internal error, node '/spl-test' name mismatch 'i2c@0'",
279 with self.assertRaises(ValueError) as e:
280 self.node.Refresh(wrong_offset)
281 self.assertIn("Internal error, node '/spl-test' name mismatch 'i2c@0'",
285 class TestProp(unittest.TestCase):
286 """Test operation of the Prop class"""
290 tools.PrepareOutputDir(None)
293 def tearDownClass(cls):
294 tools.FinaliseOutputDir()
297 self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts'))
298 self.node = self.dtb.GetNode('/spl-test')
299 self.fdt = self.dtb.GetFdtObj()
301 def testMissingNode(self):
302 self.assertEqual(None, self.dtb.GetNode('missing'))
304 def testPhandle(self):
305 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_phandle.dts'))
306 node = dtb.GetNode('/phandle-source2')
307 prop = node.props['clocks']
308 self.assertTrue(fdt32_to_cpu(prop.value) > 0)
310 def _ConvertProp(self, prop_name):
311 """Helper function to look up a property in self.node and return it
314 Property name to find
316 Return fdt.Prop object for this property
318 p = self.fdt.getprop(self.node.Offset(), prop_name)
319 return fdt.Prop(self.node, -1, prop_name, p)
321 def testMakeProp(self):
322 """Test we can convert all the the types that are supported"""
323 prop = self._ConvertProp('boolval')
324 self.assertEqual(Type.BOOL, prop.type)
325 self.assertEqual(True, prop.value)
327 prop = self._ConvertProp('intval')
328 self.assertEqual(Type.INT, prop.type)
329 self.assertEqual(1, fdt32_to_cpu(prop.value))
331 prop = self._ConvertProp('intarray')
332 self.assertEqual(Type.INT, prop.type)
333 val = [fdt32_to_cpu(val) for val in prop.value]
334 self.assertEqual([2, 3, 4], val)
336 prop = self._ConvertProp('byteval')
337 self.assertEqual(Type.BYTE, prop.type)
338 self.assertEqual(5, ord(prop.value))
340 prop = self._ConvertProp('longbytearray')
341 self.assertEqual(Type.BYTE, prop.type)
342 val = [ord(val) for val in prop.value]
343 self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val)
345 prop = self._ConvertProp('stringval')
346 self.assertEqual(Type.STRING, prop.type)
347 self.assertEqual('message', prop.value)
349 prop = self._ConvertProp('stringarray')
350 self.assertEqual(Type.STRING, prop.type)
351 self.assertEqual(['multi-word', 'message'], prop.value)
353 prop = self._ConvertProp('notstring')
354 self.assertEqual(Type.BYTE, prop.type)
355 val = [ord(val) for val in prop.value]
356 self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val)
358 def testGetEmpty(self):
359 """Tests the GetEmpty() function for the various supported types"""
360 self.assertEqual(True, fdt.Prop.GetEmpty(Type.BOOL))
361 self.assertEqual(chr(0), fdt.Prop.GetEmpty(Type.BYTE))
362 self.assertEqual(tools.GetBytes(0, 4), fdt.Prop.GetEmpty(Type.INT))
363 self.assertEqual('', fdt.Prop.GetEmpty(Type.STRING))
365 def testGetOffset(self):
366 """Test we can get the offset of a property"""
367 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray')
368 self.assertEqual(prop.value, value)
371 """Test widening of values"""
372 node2 = self.dtb.GetNode('/spl-test2')
373 node3 = self.dtb.GetNode('/spl-test3')
374 prop = self.node.props['intval']
377 prop2 = node2.props['intval']
379 self.assertEqual(Type.INT, prop.type)
380 self.assertEqual(1, fdt32_to_cpu(prop.value))
382 # Convert singla value to array
383 prop2 = self.node.props['intarray']
385 self.assertEqual(Type.INT, prop.type)
386 self.assertTrue(isinstance(prop.value, list))
388 # A 4-byte array looks like a single integer. When widened by a longer
389 # byte array, it should turn into an array.
390 prop = self.node.props['longbytearray']
391 prop2 = node2.props['longbytearray']
392 prop3 = node3.props['longbytearray']
393 self.assertFalse(isinstance(prop2.value, list))
394 self.assertEqual(4, len(prop2.value))
395 self.assertEqual(b'\x09\x0a\x0b\x0c', prop2.value)
397 self.assertTrue(isinstance(prop2.value, list))
398 self.assertEqual(9, len(prop2.value))
399 self.assertEqual(['\x09', '\x0a', '\x0b', '\x0c', '\0',
400 '\0', '\0', '\0', '\0'], prop2.value)
402 self.assertTrue(isinstance(prop3.value, list))
403 self.assertEqual(9, len(prop3.value))
404 self.assertEqual(['\x09', '\x0a', '\x0b', '\x0c', '\x0d',
405 '\x0e', '\x0f', '\x10', '\0'], prop3.value)
407 # Similarly for a string array
408 prop = self.node.props['stringval']
409 prop2 = node2.props['stringarray']
410 self.assertFalse(isinstance(prop.value, list))
411 self.assertEqual(7, len(prop.value))
413 self.assertTrue(isinstance(prop.value, list))
414 self.assertEqual(3, len(prop.value))
416 # Enlarging an existing array
417 prop = self.node.props['stringarray']
418 prop2 = node2.props['stringarray']
419 self.assertTrue(isinstance(prop.value, list))
420 self.assertEqual(2, len(prop.value))
422 self.assertTrue(isinstance(prop.value, list))
423 self.assertEqual(3, len(prop.value))
426 """Test adding properties"""
428 # This function should automatically expand the device tree
429 self.node.AddZeroProp('one')
430 self.node.AddZeroProp('two')
431 self.node.AddZeroProp('three')
432 self.dtb.Sync(auto_resize=True)
434 # Updating existing properties should be OK, since the device-tree size
437 self.node.SetInt('one', 1)
438 self.node.SetInt('two', 2)
439 self.node.SetInt('three', 3)
440 self.dtb.Sync(auto_resize=False)
442 # This should fail since it would need to increase the device-tree size
443 self.node.AddZeroProp('four')
444 with self.assertRaises(libfdt.FdtException) as e:
445 self.dtb.Sync(auto_resize=False)
446 self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
447 self.dtb.Sync(auto_resize=True)
449 def testAddMore(self):
450 """Test various other methods for adding and setting properties"""
451 self.node.AddZeroProp('one')
452 self.dtb.Sync(auto_resize=True)
453 data = self.fdt.getprop(self.node.Offset(), 'one')
454 self.assertEqual(0, fdt32_to_cpu(data))
456 self.node.SetInt('one', 1)
457 self.dtb.Sync(auto_resize=False)
458 data = self.fdt.getprop(self.node.Offset(), 'one')
459 self.assertEqual(1, fdt32_to_cpu(data))
462 self.node.AddInt('integer', val)
463 self.dtb.Sync(auto_resize=True)
464 data = self.fdt.getprop(self.node.Offset(), 'integer')
465 self.assertEqual(val, fdt32_to_cpu(data))
467 val = '123' + chr(0) + '456'
468 self.node.AddString('string', val)
469 self.dtb.Sync(auto_resize=True)
470 data = self.fdt.getprop(self.node.Offset(), 'string')
471 self.assertEqual(tools.ToBytes(val) + b'\0', data)
474 self.node.SetString('string', val + 'x')
475 with self.assertRaises(libfdt.FdtException) as e:
476 self.dtb.Sync(auto_resize=False)
477 self.assertIn('FDT_ERR_NOSPACE', str(e.exception))
478 self.node.SetString('string', val[:-1])
480 prop = self.node.props['string']
481 prop.SetData(tools.ToBytes(val))
482 self.dtb.Sync(auto_resize=False)
483 data = self.fdt.getprop(self.node.Offset(), 'string')
484 self.assertEqual(tools.ToBytes(val), data)
486 self.node.AddEmptyProp('empty', 5)
487 self.dtb.Sync(auto_resize=True)
488 prop = self.node.props['empty']
489 prop.SetData(tools.ToBytes(val))
490 self.dtb.Sync(auto_resize=False)
491 data = self.fdt.getprop(self.node.Offset(), 'empty')
492 self.assertEqual(tools.ToBytes(val), data)
494 self.node.SetData('empty', b'123')
495 self.assertEqual(b'123', prop.bytes)
497 # Trying adding a lot of data at once
498 self.node.AddData('data', tools.GetBytes(65, 20000))
499 self.dtb.Sync(auto_resize=True)
501 def testFromData(self):
502 dtb2 = fdt.Fdt.FromData(self.dtb.GetContents())
503 self.assertEqual(dtb2.GetContents(), self.dtb.GetContents())
505 self.node.AddEmptyProp('empty', 5)
506 self.dtb.Sync(auto_resize=True)
507 self.assertTrue(dtb2.GetContents() != self.dtb.GetContents())
509 def testMissingSetInt(self):
510 """Test handling of a missing property with SetInt"""
511 with self.assertRaises(ValueError) as e:
512 self.node.SetInt('one', 1)
513 self.assertIn("node '/spl-test': Missing property 'one'",
516 def testMissingSetData(self):
517 """Test handling of a missing property with SetData"""
518 with self.assertRaises(ValueError) as e:
519 self.node.SetData('one', b'data')
520 self.assertIn("node '/spl-test': Missing property 'one'",
523 def testMissingSetString(self):
524 """Test handling of a missing property with SetString"""
525 with self.assertRaises(ValueError) as e:
526 self.node.SetString('one', 1)
527 self.assertIn("node '/spl-test': Missing property 'one'",
530 def testGetFilename(self):
531 """Test the dtb filename can be provided"""
532 self.assertEqual(tools.GetOutputFilename('source.dtb'),
533 self.dtb.GetFilename())
536 class TestFdtUtil(unittest.TestCase):
537 """Tests for the fdt_util module
539 This module will likely be mostly replaced at some point, once upstream
540 libfdt has better Python support. For now, this provides tests for current
545 tools.PrepareOutputDir(None)
548 def tearDownClass(cls):
549 tools.FinaliseOutputDir()
552 self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts'))
553 self.node = self.dtb.GetNode('/spl-test')
555 def testGetInt(self):
556 self.assertEqual(1, fdt_util.GetInt(self.node, 'intval'))
557 self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3))
559 with self.assertRaises(ValueError) as e:
560 self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray'))
561 self.assertIn("property 'intarray' has list value: expecting a single "
562 'integer', str(e.exception))
564 def testGetString(self):
565 self.assertEqual('message', fdt_util.GetString(self.node, 'stringval'))
566 self.assertEqual('test', fdt_util.GetString(self.node, 'missing',
569 with self.assertRaises(ValueError) as e:
570 self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray'))
571 self.assertIn("property 'stringarray' has list value: expecting a "
572 'single string', str(e.exception))
574 def testGetBool(self):
575 self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval'))
576 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing'))
577 self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True))
578 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False))
580 def testGetByte(self):
581 self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval'))
582 self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3))
584 with self.assertRaises(ValueError) as e:
585 fdt_util.GetByte(self.node, 'longbytearray')
586 self.assertIn("property 'longbytearray' has list value: expecting a "
587 'single byte', str(e.exception))
589 with self.assertRaises(ValueError) as e:
590 fdt_util.GetByte(self.node, 'intval')
591 self.assertIn("property 'intval' has length 4, expecting 1",
594 def testGetPhandleList(self):
595 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_phandle.dts'))
596 node = dtb.GetNode('/phandle-source2')
597 self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks'))
598 node = dtb.GetNode('/phandle-source')
599 self.assertEqual([1, 2, 11, 3, 12, 13, 1],
600 fdt_util.GetPhandleList(node, 'clocks'))
601 self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing'))
603 def testGetDataType(self):
604 self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int))
605 self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval',
607 with self.assertRaises(ValueError) as e:
608 self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval',
610 def testFdtCellsToCpu(self):
611 val = self.node.props['intarray'].value
612 self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0))
613 self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1))
615 dtb2 = fdt.FdtScan(find_dtb_file('dtoc_test_addr64.dts'))
616 node1 = dtb2.GetNode('/test1')
617 val = node1.props['reg'].value
618 self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2))
620 node2 = dtb2.GetNode('/test2')
621 val = node2.props['reg'].value
622 self.assertEqual(0x1234567890123456, fdt_util.fdt_cells_to_cpu(val, 2))
623 self.assertEqual(0x9876543210987654, fdt_util.fdt_cells_to_cpu(val[2:],
625 self.assertEqual(0x12345678, fdt_util.fdt_cells_to_cpu(val, 1))
627 def testEnsureCompiled(self):
628 """Test a degenerate case of this function (file already compiled)"""
629 dtb = fdt_util.EnsureCompiled(find_dtb_file('dtoc_test_simple.dts'))
630 self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb))
632 def testEnsureCompiledTmpdir(self):
633 """Test providing a temporary directory"""
635 old_outdir = tools.outdir
637 tmpdir = tempfile.mkdtemp(prefix='test_fdt.')
638 dtb = fdt_util.EnsureCompiled(find_dtb_file('dtoc_test_simple.dts'),
640 self.assertEqual(tmpdir, os.path.dirname(dtb))
641 shutil.rmtree(tmpdir)
643 tools.outdir= old_outdir
646 def RunTestCoverage():
647 """Run the tests and check that we get 100% coverage"""
648 test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None,
649 ['tools/patman/*.py', '*test_fdt.py'], options.build_dir)
653 """Run all the test we have for the fdt model
656 args: List of positional args provided to fdt. This can hold a test
657 name to execute (as in 'fdt -t testFdt', for example)
659 result = unittest.TestResult()
660 sys.argv = [sys.argv[0]]
661 test_name = args and args[0] or None
662 for module in (TestFdt, TestNode, TestProp, TestFdtUtil):
665 suite = unittest.TestLoader().loadTestsFromName(test_name, module)
666 except AttributeError:
669 suite = unittest.TestLoader().loadTestsFromTestCase(module)
673 for _, err in result.errors:
675 for _, err in result.failures:
678 if __name__ != '__main__':
681 parser = OptionParser()
682 parser.add_option('-B', '--build-dir', type='string', default='b',
683 help='Directory containing the build output')
684 parser.add_option('-P', '--processes', type=int,
685 help='set number of processes to use for running tests')
686 parser.add_option('-t', '--test', action='store_true', dest='test',
687 default=False, help='run tests')
688 parser.add_option('-T', '--test-coverage', action='store_true',
689 default=False, help='run tests and check for 100% coverage')
690 (options, args) = parser.parse_args()
692 # Run our meagre tests
695 elif options.test_coverage: