1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0+ 3# Copyright (c) 2018 Google, Inc 4# Written by Simon Glass <sjg@chromium.org> 5# 6 7from __future__ import print_function 8 9from optparse import OptionParser 10import glob 11import os 12import shutil 13import sys 14import tempfile 15import unittest 16 17# Bring in the patman libraries 18our_path = os.path.dirname(os.path.realpath(__file__)) 19for dirname in ['../patman', '..']: 20 sys.path.insert(0, os.path.join(our_path, dirname)) 21 22import command 23import fdt 24from fdt import TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, BytesToValue 25import fdt_util 26from fdt_util import fdt32_to_cpu 27import libfdt 28import test_util 29import tools 30 31def _GetPropertyValue(dtb, node, prop_name): 32 """Low-level function to get the property value based on its offset 33 34 This looks directly in the device tree at the property's offset to find 35 its value. It is useful as a check that the property is in the correct 36 place. 37 38 Args: 39 node: Node to look in 40 prop_name: Property name to find 41 42 Returns: 43 Tuple: 44 Prop object found 45 Value of property as a string (found using property offset) 46 """ 47 prop = node.props[prop_name] 48 49 # Add 12, which is sizeof(struct fdt_property), to get to start of data 50 offset = prop.GetOffset() + 12 51 data = dtb.GetContents()[offset:offset + len(prop.value)] 52 return prop, [tools.ToChar(x) for x in data] 53 54 55class TestFdt(unittest.TestCase): 56 """Tests for the Fdt module 57 58 This includes unit tests for some functions and functional tests for the fdt 59 module. 60 """ 61 @classmethod 62 def setUpClass(cls): 63 tools.PrepareOutputDir(None) 64 65 @classmethod 66 def tearDownClass(cls): 67 tools.FinaliseOutputDir() 68 69 def setUp(self): 70 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 71 72 def testFdt(self): 73 """Test that we can open an Fdt""" 74 self.dtb.Scan() 75 root = self.dtb.GetRoot() 76 self.assertTrue(isinstance(root, fdt.Node)) 77 78 def testGetNode(self): 79 """Test the GetNode() method""" 80 node = self.dtb.GetNode('/spl-test') 81 self.assertTrue(isinstance(node, fdt.Node)) 82 83 node = self.dtb.GetNode('/i2c@0/pmic@9') 84 self.assertTrue(isinstance(node, fdt.Node)) 85 self.assertEqual('pmic@9', node.name) 86 self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing')) 87 88 node = self.dtb.GetNode('/') 89 self.assertTrue(isinstance(node, fdt.Node)) 90 self.assertEqual(0, node.Offset()) 91 92 def testFlush(self): 93 """Check that we can flush the device tree out to its file""" 94 fname = self.dtb._fname 95 with open(fname, 'rb') as fd: 96 data = fd.read() 97 os.remove(fname) 98 with self.assertRaises(IOError): 99 open(fname, 'rb') 100 self.dtb.Flush() 101 with open(fname, 'rb') as fd: 102 data = fd.read() 103 104 def testPack(self): 105 """Test that packing a device tree works""" 106 self.dtb.Pack() 107 108 def testGetFdt(self): 109 """Tetst that we can access the raw device-tree data""" 110 self.assertTrue(isinstance(self.dtb.GetContents(), bytearray)) 111 112 def testGetProps(self): 113 """Tests obtaining a list of properties""" 114 node = self.dtb.GetNode('/spl-test') 115 props = self.dtb.GetProps(node) 116 self.assertEqual(['boolval', 'bytearray', 'byteval', 'compatible', 117 'intarray', 'intval', 'longbytearray', 'notstring', 118 'stringarray', 'stringval', 'u-boot,dm-pre-reloc'], 119 sorted(props.keys())) 120 121 def testCheckError(self): 122 """Tests the ChecKError() function""" 123 with self.assertRaises(ValueError) as e: 124 fdt.CheckErr(-libfdt.NOTFOUND, 'hello') 125 self.assertIn('FDT_ERR_NOTFOUND: hello', str(e.exception)) 126 127 def testGetFdt(self): 128 node = self.dtb.GetNode('/spl-test') 129 self.assertEqual(self.dtb, node.GetFdt()) 130 131 def testBytesToValue(self): 132 self.assertEqual(BytesToValue(b'this\0is\0'), 133 (TYPE_STRING, ['this', 'is'])) 134 135class TestNode(unittest.TestCase): 136 """Test operation of the Node class""" 137 138 @classmethod 139 def setUpClass(cls): 140 tools.PrepareOutputDir(None) 141 142 @classmethod 143 def tearDownClass(cls): 144 tools.FinaliseOutputDir() 145 146 def setUp(self): 147 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 148 self.node = self.dtb.GetNode('/spl-test') 149 150 def testOffset(self): 151 """Tests that we can obtain the offset of a node""" 152 self.assertTrue(self.node.Offset() > 0) 153 154 def testDelete(self): 155 """Tests that we can delete a property""" 156 node2 = self.dtb.GetNode('/spl-test2') 157 offset1 = node2.Offset() 158 self.node.DeleteProp('intval') 159 offset2 = node2.Offset() 160 self.assertTrue(offset2 < offset1) 161 self.node.DeleteProp('intarray') 162 offset3 = node2.Offset() 163 self.assertTrue(offset3 < offset2) 164 with self.assertRaises(libfdt.FdtException): 165 self.node.DeleteProp('missing') 166 167 def testDeleteGetOffset(self): 168 """Test that property offset update when properties are deleted""" 169 self.node.DeleteProp('intval') 170 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray') 171 self.assertEqual(prop.value, value) 172 173 def testFindNode(self): 174 """Tests that we can find a node using the FindNode() functoin""" 175 node = self.dtb.GetRoot().FindNode('i2c@0') 176 self.assertEqual('i2c@0', node.name) 177 subnode = node.FindNode('pmic@9') 178 self.assertEqual('pmic@9', subnode.name) 179 self.assertEqual(None, node.FindNode('missing')) 180 181 def testRefreshMissingNode(self): 182 """Test refreshing offsets when an extra node is present in dtb""" 183 # Delete it from our tables, not the device tree 184 del self.dtb._root.subnodes[-1] 185 with self.assertRaises(ValueError) as e: 186 self.dtb.Refresh() 187 self.assertIn('Internal error, offset', str(e.exception)) 188 189 def testRefreshExtraNode(self): 190 """Test refreshing offsets when an expected node is missing""" 191 # Delete it from the device tre, not our tables 192 self.dtb.GetFdtObj().del_node(self.node.Offset()) 193 with self.assertRaises(ValueError) as e: 194 self.dtb.Refresh() 195 self.assertIn('Internal error, node name mismatch ' 196 'spl-test != spl-test2', str(e.exception)) 197 198 def testRefreshMissingProp(self): 199 """Test refreshing offsets when an extra property is present in dtb""" 200 # Delete it from our tables, not the device tree 201 del self.node.props['notstring'] 202 with self.assertRaises(ValueError) as e: 203 self.dtb.Refresh() 204 self.assertIn("Internal error, property 'notstring' missing, offset ", 205 str(e.exception)) 206 207 def testLookupPhandle(self): 208 """Test looking up a single phandle""" 209 dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') 210 node = dtb.GetNode('/phandle-source2') 211 prop = node.props['clocks'] 212 target = dtb.GetNode('/phandle-target') 213 self.assertEqual(target, dtb.LookupPhandle(fdt32_to_cpu(prop.value))) 214 215 216class TestProp(unittest.TestCase): 217 """Test operation of the Prop class""" 218 219 @classmethod 220 def setUpClass(cls): 221 tools.PrepareOutputDir(None) 222 223 @classmethod 224 def tearDownClass(cls): 225 tools.FinaliseOutputDir() 226 227 def setUp(self): 228 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 229 self.node = self.dtb.GetNode('/spl-test') 230 self.fdt = self.dtb.GetFdtObj() 231 232 def testMissingNode(self): 233 self.assertEqual(None, self.dtb.GetNode('missing')) 234 235 def testPhandle(self): 236 dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') 237 node = dtb.GetNode('/phandle-source2') 238 prop = node.props['clocks'] 239 self.assertTrue(fdt32_to_cpu(prop.value) > 0) 240 241 def _ConvertProp(self, prop_name): 242 """Helper function to look up a property in self.node and return it 243 244 Args: 245 Property name to find 246 247 Return fdt.Prop object for this property 248 """ 249 p = self.fdt.getprop(self.node.Offset(), prop_name) 250 return fdt.Prop(self.node, -1, prop_name, p) 251 252 def testMakeProp(self): 253 """Test we can convert all the the types that are supported""" 254 prop = self._ConvertProp('boolval') 255 self.assertEqual(fdt.TYPE_BOOL, prop.type) 256 self.assertEqual(True, prop.value) 257 258 prop = self._ConvertProp('intval') 259 self.assertEqual(fdt.TYPE_INT, prop.type) 260 self.assertEqual(1, fdt32_to_cpu(prop.value)) 261 262 prop = self._ConvertProp('intarray') 263 self.assertEqual(fdt.TYPE_INT, prop.type) 264 val = [fdt32_to_cpu(val) for val in prop.value] 265 self.assertEqual([2, 3, 4], val) 266 267 prop = self._ConvertProp('byteval') 268 self.assertEqual(fdt.TYPE_BYTE, prop.type) 269 self.assertEqual(5, ord(prop.value)) 270 271 prop = self._ConvertProp('longbytearray') 272 self.assertEqual(fdt.TYPE_BYTE, prop.type) 273 val = [ord(val) for val in prop.value] 274 self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val) 275 276 prop = self._ConvertProp('stringval') 277 self.assertEqual(fdt.TYPE_STRING, prop.type) 278 self.assertEqual('message', prop.value) 279 280 prop = self._ConvertProp('stringarray') 281 self.assertEqual(fdt.TYPE_STRING, prop.type) 282 self.assertEqual(['multi-word', 'message'], prop.value) 283 284 prop = self._ConvertProp('notstring') 285 self.assertEqual(fdt.TYPE_BYTE, prop.type) 286 val = [ord(val) for val in prop.value] 287 self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val) 288 289 def testGetEmpty(self): 290 """Tests the GetEmpty() function for the various supported types""" 291 self.assertEqual(True, fdt.Prop.GetEmpty(fdt.TYPE_BOOL)) 292 self.assertEqual(chr(0), fdt.Prop.GetEmpty(fdt.TYPE_BYTE)) 293 self.assertEqual(tools.GetBytes(0, 4), fdt.Prop.GetEmpty(fdt.TYPE_INT)) 294 self.assertEqual('', fdt.Prop.GetEmpty(fdt.TYPE_STRING)) 295 296 def testGetOffset(self): 297 """Test we can get the offset of a property""" 298 prop, value = _GetPropertyValue(self.dtb, self.node, 'longbytearray') 299 self.assertEqual(prop.value, value) 300 301 def testWiden(self): 302 """Test widening of values""" 303 node2 = self.dtb.GetNode('/spl-test2') 304 prop = self.node.props['intval'] 305 306 # No action 307 prop2 = node2.props['intval'] 308 prop.Widen(prop2) 309 self.assertEqual(fdt.TYPE_INT, prop.type) 310 self.assertEqual(1, fdt32_to_cpu(prop.value)) 311 312 # Convert singla value to array 313 prop2 = self.node.props['intarray'] 314 prop.Widen(prop2) 315 self.assertEqual(fdt.TYPE_INT, prop.type) 316 self.assertTrue(isinstance(prop.value, list)) 317 318 # A 4-byte array looks like a single integer. When widened by a longer 319 # byte array, it should turn into an array. 320 prop = self.node.props['longbytearray'] 321 prop2 = node2.props['longbytearray'] 322 self.assertFalse(isinstance(prop2.value, list)) 323 self.assertEqual(4, len(prop2.value)) 324 prop2.Widen(prop) 325 self.assertTrue(isinstance(prop2.value, list)) 326 self.assertEqual(9, len(prop2.value)) 327 328 # Similarly for a string array 329 prop = self.node.props['stringval'] 330 prop2 = node2.props['stringarray'] 331 self.assertFalse(isinstance(prop.value, list)) 332 self.assertEqual(7, len(prop.value)) 333 prop.Widen(prop2) 334 self.assertTrue(isinstance(prop.value, list)) 335 self.assertEqual(3, len(prop.value)) 336 337 # Enlarging an existing array 338 prop = self.node.props['stringarray'] 339 prop2 = node2.props['stringarray'] 340 self.assertTrue(isinstance(prop.value, list)) 341 self.assertEqual(2, len(prop.value)) 342 prop.Widen(prop2) 343 self.assertTrue(isinstance(prop.value, list)) 344 self.assertEqual(3, len(prop.value)) 345 346 def testAdd(self): 347 """Test adding properties""" 348 self.fdt.pack() 349 # This function should automatically expand the device tree 350 self.node.AddZeroProp('one') 351 self.node.AddZeroProp('two') 352 self.node.AddZeroProp('three') 353 self.dtb.Sync(auto_resize=True) 354 355 # Updating existing properties should be OK, since the device-tree size 356 # does not change 357 self.fdt.pack() 358 self.node.SetInt('one', 1) 359 self.node.SetInt('two', 2) 360 self.node.SetInt('three', 3) 361 self.dtb.Sync(auto_resize=False) 362 363 # This should fail since it would need to increase the device-tree size 364 self.node.AddZeroProp('four') 365 with self.assertRaises(libfdt.FdtException) as e: 366 self.dtb.Sync(auto_resize=False) 367 self.assertIn('FDT_ERR_NOSPACE', str(e.exception)) 368 self.dtb.Sync(auto_resize=True) 369 370 def testAddNode(self): 371 self.fdt.pack() 372 self.node.AddSubnode('subnode') 373 with self.assertRaises(libfdt.FdtException) as e: 374 self.dtb.Sync(auto_resize=False) 375 self.assertIn('FDT_ERR_NOSPACE', str(e.exception)) 376 377 self.dtb.Sync(auto_resize=True) 378 offset = self.fdt.path_offset('/spl-test/subnode') 379 self.assertTrue(offset > 0) 380 381 def testAddMore(self): 382 """Test various other methods for adding and setting properties""" 383 self.node.AddZeroProp('one') 384 self.dtb.Sync(auto_resize=True) 385 data = self.fdt.getprop(self.node.Offset(), 'one') 386 self.assertEqual(0, fdt32_to_cpu(data)) 387 388 self.node.SetInt('one', 1) 389 self.dtb.Sync(auto_resize=False) 390 data = self.fdt.getprop(self.node.Offset(), 'one') 391 self.assertEqual(1, fdt32_to_cpu(data)) 392 393 val = '123' + chr(0) + '456' 394 self.node.AddString('string', val) 395 self.dtb.Sync(auto_resize=True) 396 data = self.fdt.getprop(self.node.Offset(), 'string') 397 self.assertEqual(tools.ToBytes(val) + b'\0', data) 398 399 self.fdt.pack() 400 self.node.SetString('string', val + 'x') 401 with self.assertRaises(libfdt.FdtException) as e: 402 self.dtb.Sync(auto_resize=False) 403 self.assertIn('FDT_ERR_NOSPACE', str(e.exception)) 404 self.node.SetString('string', val[:-1]) 405 406 prop = self.node.props['string'] 407 prop.SetData(tools.ToBytes(val)) 408 self.dtb.Sync(auto_resize=False) 409 data = self.fdt.getprop(self.node.Offset(), 'string') 410 self.assertEqual(tools.ToBytes(val), data) 411 412 self.node.AddEmptyProp('empty', 5) 413 self.dtb.Sync(auto_resize=True) 414 prop = self.node.props['empty'] 415 prop.SetData(tools.ToBytes(val)) 416 self.dtb.Sync(auto_resize=False) 417 data = self.fdt.getprop(self.node.Offset(), 'empty') 418 self.assertEqual(tools.ToBytes(val), data) 419 420 self.node.SetData('empty', b'123') 421 self.assertEqual(b'123', prop.bytes) 422 423 def testFromData(self): 424 dtb2 = fdt.Fdt.FromData(self.dtb.GetContents()) 425 self.assertEqual(dtb2.GetContents(), self.dtb.GetContents()) 426 427 self.node.AddEmptyProp('empty', 5) 428 self.dtb.Sync(auto_resize=True) 429 self.assertTrue(dtb2.GetContents() != self.dtb.GetContents()) 430 431 def testMissingSetInt(self): 432 """Test handling of a missing property with SetInt""" 433 with self.assertRaises(ValueError) as e: 434 self.node.SetInt('one', 1) 435 self.assertIn("node '/spl-test': Missing property 'one'", 436 str(e.exception)) 437 438 def testMissingSetData(self): 439 """Test handling of a missing property with SetData""" 440 with self.assertRaises(ValueError) as e: 441 self.node.SetData('one', b'data') 442 self.assertIn("node '/spl-test': Missing property 'one'", 443 str(e.exception)) 444 445 def testMissingSetString(self): 446 """Test handling of a missing property with SetString""" 447 with self.assertRaises(ValueError) as e: 448 self.node.SetString('one', 1) 449 self.assertIn("node '/spl-test': Missing property 'one'", 450 str(e.exception)) 451 452 def testGetFilename(self): 453 """Test the dtb filename can be provided""" 454 self.assertEqual(tools.GetOutputFilename('source.dtb'), 455 self.dtb.GetFilename()) 456 457 458class TestFdtUtil(unittest.TestCase): 459 """Tests for the fdt_util module 460 461 This module will likely be mostly replaced at some point, once upstream 462 libfdt has better Python support. For now, this provides tests for current 463 functionality. 464 """ 465 @classmethod 466 def setUpClass(cls): 467 tools.PrepareOutputDir(None) 468 469 @classmethod 470 def tearDownClass(cls): 471 tools.FinaliseOutputDir() 472 473 def setUp(self): 474 self.dtb = fdt.FdtScan('tools/dtoc/dtoc_test_simple.dts') 475 self.node = self.dtb.GetNode('/spl-test') 476 477 def testGetInt(self): 478 self.assertEqual(1, fdt_util.GetInt(self.node, 'intval')) 479 self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3)) 480 481 with self.assertRaises(ValueError) as e: 482 self.assertEqual(3, fdt_util.GetInt(self.node, 'intarray')) 483 self.assertIn("property 'intarray' has list value: expecting a single " 484 'integer', str(e.exception)) 485 486 def testGetString(self): 487 self.assertEqual('message', fdt_util.GetString(self.node, 'stringval')) 488 self.assertEqual('test', fdt_util.GetString(self.node, 'missing', 489 'test')) 490 491 with self.assertRaises(ValueError) as e: 492 self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray')) 493 self.assertIn("property 'stringarray' has list value: expecting a " 494 'single string', str(e.exception)) 495 496 def testGetBool(self): 497 self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval')) 498 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing')) 499 self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True)) 500 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False)) 501 502 def testGetByte(self): 503 self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval')) 504 self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3)) 505 506 with self.assertRaises(ValueError) as e: 507 fdt_util.GetByte(self.node, 'longbytearray') 508 self.assertIn("property 'longbytearray' has list value: expecting a " 509 'single byte', str(e.exception)) 510 511 with self.assertRaises(ValueError) as e: 512 fdt_util.GetByte(self.node, 'intval') 513 self.assertIn("property 'intval' has length 4, expecting 1", 514 str(e.exception)) 515 516 def testGetPhandleList(self): 517 dtb = fdt.FdtScan('tools/dtoc/dtoc_test_phandle.dts') 518 node = dtb.GetNode('/phandle-source2') 519 self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks')) 520 node = dtb.GetNode('/phandle-source') 521 self.assertEqual([1, 2, 11, 3, 12, 13, 1], 522 fdt_util.GetPhandleList(node, 'clocks')) 523 self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing')) 524 525 def testGetDataType(self): 526 self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int)) 527 self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval', 528 str)) 529 with self.assertRaises(ValueError) as e: 530 self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval', 531 bool)) 532 def testFdtCellsToCpu(self): 533 val = self.node.props['intarray'].value 534 self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0)) 535 self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1)) 536 537 dtb2 = fdt.FdtScan('tools/dtoc/dtoc_test_addr64.dts') 538 node1 = dtb2.GetNode('/test1') 539 val = node1.props['reg'].value 540 self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2)) 541 542 node2 = dtb2.GetNode('/test2') 543 val = node2.props['reg'].value 544 self.assertEqual(0x1234567890123456, fdt_util.fdt_cells_to_cpu(val, 2)) 545 self.assertEqual(0x9876543210987654, fdt_util.fdt_cells_to_cpu(val[2:], 546 2)) 547 self.assertEqual(0x12345678, fdt_util.fdt_cells_to_cpu(val, 1)) 548 549 def testEnsureCompiled(self): 550 """Test a degenerate case of this function (file already compiled)""" 551 dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts') 552 self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb)) 553 554 def testEnsureCompiledTmpdir(self): 555 """Test providing a temporary directory""" 556 try: 557 old_outdir = tools.outdir 558 tools.outdir= None 559 tmpdir = tempfile.mkdtemp(prefix='test_fdt.') 560 dtb = fdt_util.EnsureCompiled('tools/dtoc/dtoc_test_simple.dts', 561 tmpdir) 562 self.assertEqual(tmpdir, os.path.dirname(dtb)) 563 shutil.rmtree(tmpdir) 564 finally: 565 tools.outdir= old_outdir 566 567 568def RunTestCoverage(): 569 """Run the tests and check that we get 100% coverage""" 570 test_util.RunTestCoverage('tools/dtoc/test_fdt.py', None, 571 ['tools/patman/*.py', '*test_fdt.py'], options.build_dir) 572 573 574def RunTests(args): 575 """Run all the test we have for the fdt model 576 577 Args: 578 args: List of positional args provided to fdt. This can hold a test 579 name to execute (as in 'fdt -t testFdt', for example) 580 """ 581 result = unittest.TestResult() 582 sys.argv = [sys.argv[0]] 583 test_name = args and args[0] or None 584 for module in (TestFdt, TestNode, TestProp, TestFdtUtil): 585 if test_name: 586 try: 587 suite = unittest.TestLoader().loadTestsFromName(test_name, module) 588 except AttributeError: 589 continue 590 else: 591 suite = unittest.TestLoader().loadTestsFromTestCase(module) 592 suite.run(result) 593 594 print(result) 595 for _, err in result.errors: 596 print(err) 597 for _, err in result.failures: 598 print(err) 599 600if __name__ != '__main__': 601 sys.exit(1) 602 603parser = OptionParser() 604parser.add_option('-B', '--build-dir', type='string', default='b', 605 help='Directory containing the build output') 606parser.add_option('-P', '--processes', type=int, 607 help='set number of processes to use for running tests') 608parser.add_option('-t', '--test', action='store_true', dest='test', 609 default=False, help='run tests') 610parser.add_option('-T', '--test-coverage', action='store_true', 611 default=False, help='run tests and check for 100% coverage') 612(options, args) = parser.parse_args() 613 614# Run our meagre tests 615if options.test: 616 RunTests(args) 617elif options.test_coverage: 618 RunTestCoverage() 619