Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Generate and process code coverage. | 6 """Generate and process code coverage. |
| 7 | 7 |
| 8 TODO(jrg): rename this from coverage_posix.py to coverage_all.py! | 8 TODO(jrg): rename this from coverage_posix.py to coverage_all.py! |
| 9 | 9 |
| 10 Written for and tested on Mac, Linux, and Windows. To use this script | 10 Written for and tested on Mac, Linux, and Windows. To use this script |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 87 have all text before a ':' stripped to help with gyp compatibility. | 87 have all text before a ':' stripped to help with gyp compatibility. |
| 88 For example, ../base/base.gyp:base_unittests is interpreted as a test | 88 For example, ../base/base.gyp:base_unittests is interpreted as a test |
| 89 named "base_unittests". | 89 named "base_unittests". |
| 90 """ | 90 """ |
| 91 | 91 |
| 92 import glob | 92 import glob |
| 93 import logging | 93 import logging |
| 94 import optparse | 94 import optparse |
| 95 import os | 95 import os |
| 96 import Queue | 96 import Queue |
| 97 import re | |
| 97 import shutil | 98 import shutil |
| 98 import signal | 99 import signal |
| 99 import subprocess | 100 import subprocess |
| 100 import sys | 101 import sys |
| 101 import tempfile | 102 import tempfile |
| 102 import threading | 103 import threading |
| 103 import time | 104 import time |
| 104 import traceback | 105 import traceback |
| 105 | 106 |
| 106 | 107 |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 139 os.kill(pid, sig) | 140 os.kill(pid, sig) |
| 140 else: | 141 else: |
| 141 subprocess.call(['taskkill.exe', '/PID', str(pid)]) | 142 subprocess.call(['taskkill.exe', '/PID', str(pid)]) |
| 142 sys.exit(0) | 143 sys.exit(0) |
| 143 | 144 |
| 144 | 145 |
| 145 class RunTooLongException(Exception): | 146 class RunTooLongException(Exception): |
| 146 """Thrown when a command runs too long without output.""" | 147 """Thrown when a command runs too long without output.""" |
| 147 pass | 148 pass |
| 148 | 149 |
| 150 class BadUserInput(Exception): | |
| 151 """Thrown when arguments from the user are incorrectly formatted.""" | |
| 152 pass | |
| 153 | |
| 149 | 154 |
| 150 class RunProgramThread(threading.Thread): | 155 class RunProgramThread(threading.Thread): |
| 151 """A thread to run a subprocess. | 156 """A thread to run a subprocess. |
| 152 | 157 |
| 153 We want to print the output of our subprocess in real time, but also | 158 We want to print the output of our subprocess in real time, but also |
| 154 want a timeout if there has been no output for a certain amount of | 159 want a timeout if there has been no output for a certain amount of |
| 155 time. Normal techniques (e.g. loop in select()) aren't cross | 160 time. Normal techniques (e.g. loop in select()) aren't cross |
| 156 platform enough. the function seems simple: "print output of child, kill it | 161 platform enough. the function seems simple: "print output of child, kill it |
| 157 if there is no output by timeout. But it was tricky to get this right | 162 if there is no output by timeout. But it was tricky to get this right |
| 158 in a x-platform way (see warnings about deadlock on the python | 163 in a x-platform way (see warnings about deadlock on the python |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 309 # The "final" lcov-format file | 314 # The "final" lcov-format file |
| 310 self.coverage_info_file = os.path.join(self.directory, 'coverage.info') | 315 self.coverage_info_file = os.path.join(self.directory, 'coverage.info') |
| 311 # If needed, an intermediate VSTS-format file | 316 # If needed, an intermediate VSTS-format file |
| 312 self.vsts_output = os.path.join(self.directory, 'coverage.vsts') | 317 self.vsts_output = os.path.join(self.directory, 'coverage.vsts') |
| 313 # Needed for Windows. | 318 # Needed for Windows. |
| 314 self.src_root = options.src_root | 319 self.src_root = options.src_root |
| 315 self.FindPrograms() | 320 self.FindPrograms() |
| 316 self.ConfirmPlatformAndPaths() | 321 self.ConfirmPlatformAndPaths() |
| 317 self.tests = [] | 322 self.tests = [] |
| 318 self.xvfb_pid = 0 | 323 self.xvfb_pid = 0 |
| 324 self.test_files = [] # List of files with test specifications. | |
| 325 self.test_filters = {} # Mapping from testname->--gtest_filter arg. | |
| 319 logging.info('self.directory: ' + self.directory) | 326 logging.info('self.directory: ' + self.directory) |
| 320 logging.info('self.directory_parent: ' + self.directory_parent) | 327 logging.info('self.directory_parent: ' + self.directory_parent) |
| 321 | 328 |
| 322 def FindInPath(self, program): | 329 def FindInPath(self, program): |
| 323 """Find program in our path. Return abs path to it, or None.""" | 330 """Find program in our path. Return abs path to it, or None.""" |
| 324 if not 'PATH' in os.environ: | 331 if not 'PATH' in os.environ: |
| 325 logging.fatal('No PATH environment variable?') | 332 logging.fatal('No PATH environment variable?') |
| 326 sys.exit(1) | 333 sys.exit(1) |
| 327 paths = os.environ['PATH'].split(os.pathsep) | 334 paths = os.environ['PATH'].split(os.pathsep) |
| 328 for path in paths: | 335 for path in paths: |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 430 Assume all non-option items in the arg list are tests to be run. | 437 Assume all non-option items in the arg list are tests to be run. |
| 431 """ | 438 """ |
| 432 # Before we begin, find the bundles file if not an absolute path. | 439 # Before we begin, find the bundles file if not an absolute path. |
| 433 self.FindBundlesFile() | 440 self.FindBundlesFile() |
| 434 | 441 |
| 435 # Small tests: can be run in the "chromium" directory. | 442 # Small tests: can be run in the "chromium" directory. |
| 436 # If asked, run all we can find. | 443 # If asked, run all we can find. |
| 437 if self.options.all_unittests: | 444 if self.options.all_unittests: |
| 438 self.tests += glob.glob(os.path.join(self.directory, '*_unittests')) | 445 self.tests += glob.glob(os.path.join(self.directory, '*_unittests')) |
| 439 | 446 |
| 440 # Tests can come in as args or as a file of bundles. | 447 # Tests can come in as args directly, indirectly (through a file |
| 448 # of test lists) or as a file of bundles. | |
| 441 all_testnames = self.args[:] # Copy since we might modify | 449 all_testnames = self.args[:] # Copy since we might modify |
| 450 | |
| 451 for test_file in self.options.test_files: | |
| 452 f = open(test_file) | |
| 453 for line in f: | |
| 454 line = re.sub(r"#.*$", "", line) | |
| 455 line = re.sub(r"\s*", "", line) | |
| 456 if re.match("\s*$"): | |
| 457 continue | |
| 458 all_testnames.append(line) | |
| 459 f.close() | |
| 460 | |
| 442 tests_from_bundles = None | 461 tests_from_bundles = None |
| 443 if self.options.bundles: | 462 if self.options.bundles: |
| 444 try: | 463 try: |
| 445 tests_from_bundles = eval(open(self.options.bundles).read()) | 464 tests_from_bundles = eval(open(self.options.bundles).read()) |
| 446 except IOError: | 465 except IOError: |
| 447 logging.fatal('IO error in bundle file ' + | 466 logging.fatal('IO error in bundle file ' + |
| 448 self.options.bundles + ' (doesn\'t exist?)') | 467 self.options.bundles + ' (doesn\'t exist?)') |
| 449 except (NameError, SyntaxError): | 468 except (NameError, SyntaxError): |
| 450 logging.fatal('Parse or syntax error in bundle file ' + | 469 logging.fatal('Parse or syntax error in bundle file ' + |
| 451 self.options.bundles) | 470 self.options.bundles) |
| 452 if hasattr(tests_from_bundles, '__iter__'): | 471 if hasattr(tests_from_bundles, '__iter__'): |
| 453 all_testnames += tests_from_bundles | 472 all_testnames += tests_from_bundles |
| 454 else: | 473 else: |
| 455 logging.fatal('Fatal error with bundle file; could not get list from' + | 474 logging.fatal('Fatal error with bundle file; could not get list from' + |
| 456 self.options.bundles) | 475 self.options.bundles) |
| 457 sys.exit(1) | 476 sys.exit(1) |
| 458 | 477 |
| 459 # If told explicit tests, run those (after stripping the name as | 478 # If told explicit tests, run those (after stripping the name as |
| 460 # appropriate) | 479 # appropriate) |
| 461 for testname in all_testnames: | 480 for testname in all_testnames: |
| 481 mo = re.search(r"(.*)\[(.*)\]$", testname) | |
| 482 gtest_filter = None | |
| 483 if mo: | |
| 484 gtest_filter = mo.group(2) | |
| 485 testname = mo.group(1) | |
| 486 | |
| 462 if ':' in testname: | 487 if ':' in testname: |
| 463 self.tests += [os.path.join(self.directory, testname.split(':')[1])] | 488 testname = testname.split(':')[1] |
| 464 else: | 489 self.tests += [os.path.join(self.directory, testname)] |
| 465 self.tests += [os.path.join(self.directory, testname)] | 490 if gtest_filter: |
| 491 self.test_filters[testname] = gtest_filter | |
| 492 | |
| 466 # Medium tests? | 493 # Medium tests? |
| 467 # Not sure all of these work yet (e.g. page_cycler_tests) | 494 # Not sure all of these work yet (e.g. page_cycler_tests) |
| 468 # self.tests += glob.glob(os.path.join(self.directory, '*_tests')) | 495 # self.tests += glob.glob(os.path.join(self.directory, '*_tests')) |
| 469 | 496 |
| 470 # If needed, append .exe to tests since vsinstr.exe likes it that | 497 # If needed, append .exe to tests since vsinstr.exe likes it that |
| 471 # way. | 498 # way. |
| 472 if self.IsWindows(): | 499 if self.IsWindows(): |
| 473 for ind in range(len(self.tests)): | 500 for ind in range(len(self.tests)): |
| 474 test = self.tests[ind] | 501 test = self.tests[ind] |
| 475 test_exe = test + '.exe' | 502 test_exe = test + '.exe' |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 593 def GtestFilter(self, fulltest, excl=None): | 620 def GtestFilter(self, fulltest, excl=None): |
| 594 """Return a --gtest_filter=BLAH for this test. | 621 """Return a --gtest_filter=BLAH for this test. |
| 595 | 622 |
| 596 Args: | 623 Args: |
| 597 fulltest: full name of test executable | 624 fulltest: full name of test executable |
| 598 exclusions: the exclusions list. Only set in a unit test; | 625 exclusions: the exclusions list. Only set in a unit test; |
| 599 else uses gTestExclusions. | 626 else uses gTestExclusions. |
| 600 Returns: | 627 Returns: |
| 601 String of the form '--gtest_filter=BLAH', or None. | 628 String of the form '--gtest_filter=BLAH', or None. |
| 602 """ | 629 """ |
| 603 if self.options.no_exclusions: | 630 positive_gfilter_list = [] |
| 604 return | 631 negative_gfilter_list = [] |
| 605 exclusions = excl or gTestExclusions | 632 |
| 606 excldict = exclusions.get(sys.platform) | 633 # Exclude all flaky and failing tests; they don't count for code coverage. |
| 607 if excldict: | 634 negative_gfilter_list += ("*.FLAKY_*", "*.FAILS_*") |
| 608 for test in excldict.keys(): | 635 |
| 609 # example: if base_unittests in ../blah/blah/base_unittests.exe | 636 if not self.options.no_exclusions: |
| 610 if test in fulltest: | 637 exclusions = excl or gTestExclusions |
| 611 gfilter = '--gtest_filter=-' + ':-'.join(excldict[test]) | 638 excldict = exclusions.get(sys.platform) |
| 612 return gfilter | 639 if excldict: |
| 613 return None | 640 for test in excldict.keys(): |
| 641 # example: if base_unittests in ../blah/blah/base_unittests.exe | |
| 642 if test in fulltest: | |
| 643 negative_gfilter_list += excldict[test] | |
| 644 | |
| 645 fulltest_basename = os.path.basename(fulltest) | |
| 646 if fulltest_basename in self.test_filters: | |
| 647 specific_test_filters = self.test_filters[fulltest_basename].split("-") | |
| 648 if len(specific_test_filters) > 2: | |
| 649 logging.error("Multiple '-' symbols in filter list: %s" % | |
| 650 self.test_filters[fulltest_basename]) | |
| 651 raise BadUserInput() | |
| 652 if len(specific_test_filters) == 2: | |
| 653 # Remove trailing ":" | |
| 654 specific_test_filters[0] = specific_test_filters[0][:-1] | |
| 655 | |
| 656 if specific_test_filters[0]: # Test for no positive filters. | |
| 657 positive_gfilter_list += specific_test_filters[0].split(":") | |
| 658 if len(specific_test_filters) > 1: | |
| 659 negative_gfilter_list += specific_test_filters[1].split(":") | |
| 660 | |
| 661 if not positive_gfilter_list and not negative_gfilter_list: | |
| 662 return None | |
| 663 | |
| 664 result = "--gtest_filter=" | |
|
John Grabowski
2011/11/30 00:52:11
prefer single quotes on strings (multiple spots)
| |
| 665 if positive_gfilter_list: | |
| 666 result += ":".join(positive_gfilter_list) | |
| 667 if negative_gfilter_list: | |
| 668 if positive_gfilter_list: result += ":" | |
| 669 result += "-" + ":".join(negative_gfilter_list) | |
| 670 return result | |
| 614 | 671 |
| 615 def RunTests(self): | 672 def RunTests(self): |
| 616 """Run all unit tests and generate appropriate lcov files.""" | 673 """Run all unit tests and generate appropriate lcov files.""" |
| 617 self.BeforeRunAllTests() | 674 self.BeforeRunAllTests() |
| 618 for fulltest in self.tests: | 675 for fulltest in self.tests: |
| 619 if not os.path.exists(fulltest): | 676 if not os.path.exists(fulltest): |
| 620 logging.info(fulltest + ' does not exist') | 677 logging.info(fulltest + ' does not exist') |
| 621 if self.options.strict: | 678 if self.options.strict: |
| 622 sys.exit(2) | 679 sys.exit(2) |
| 623 else: | 680 else: |
| (...skipping 257 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 881 'used for finding bundlefile (e.g. Debug)')) | 938 'used for finding bundlefile (e.g. Debug)')) |
| 882 parser.add_option('--no_exclusions', | 939 parser.add_option('--no_exclusions', |
| 883 dest='no_exclusions', | 940 dest='no_exclusions', |
| 884 default=None, | 941 default=None, |
| 885 help=('Disable the exclusion list.')) | 942 help=('Disable the exclusion list.')) |
| 886 parser.add_option('--dont-clear-coverage-data', | 943 parser.add_option('--dont-clear-coverage-data', |
| 887 dest='dont_clear_coverage_data', | 944 dest='dont_clear_coverage_data', |
| 888 default=False, | 945 default=False, |
| 889 action='store_true', | 946 action='store_true', |
| 890 help=('Turn off clearing of cov data from a prev run')) | 947 help=('Turn off clearing of cov data from a prev run')) |
| 948 parser.add_option('-F', | |
| 949 '--test-file', | |
| 950 dest="test_files", | |
|
John Grabowski
2011/11/30 00:52:11
like this one
| |
| 951 default=[], | |
| 952 action="append", | |
|
John Grabowski
2011/11/30 00:52:11
and this one
| |
| 953 help=('Specify a file from which tests to be run will ' + | |
| 954 'be extracted')) | |
| 891 return parser | 955 return parser |
| 892 | 956 |
| 893 | 957 |
| 894 def main(): | 958 def main(): |
| 895 # Print out the args to help someone do it by hand if needed | 959 # Print out the args to help someone do it by hand if needed |
| 896 print >>sys.stderr, sys.argv | 960 print >>sys.stderr, sys.argv |
| 897 | 961 |
| 898 # Try and clean up nice if we're killed by buildbot, Ctrl-C, ... | 962 # Try and clean up nice if we're killed by buildbot, Ctrl-C, ... |
| 899 signal.signal(signal.SIGINT, TerminateSignalHandler) | 963 signal.signal(signal.SIGINT, TerminateSignalHandler) |
| 900 signal.signal(signal.SIGTERM, TerminateSignalHandler) | 964 signal.signal(signal.SIGTERM, TerminateSignalHandler) |
| 901 | 965 |
| 902 parser = CoverageOptionParser() | 966 parser = CoverageOptionParser() |
| 903 (options, args) = parser.parse_args() | 967 (options, args) = parser.parse_args() |
| 904 coverage = Coverage(options, args) | 968 coverage = Coverage(options, args) |
| 905 coverage.ClearData() | 969 coverage.ClearData() |
| 906 coverage.FindTests() | 970 coverage.FindTests() |
| 907 if options.trim: | 971 if options.trim: |
| 908 coverage.TrimTests() | 972 coverage.TrimTests() |
| 909 coverage.RunTests() | 973 coverage.RunTests() |
| 910 if options.genhtml: | 974 if options.genhtml: |
| 911 coverage.GenerateHtml() | 975 coverage.GenerateHtml() |
| 912 return 0 | 976 return 0 |
| 913 | 977 |
| 914 | 978 |
| 915 if __name__ == '__main__': | 979 if __name__ == '__main__': |
| 916 sys.exit(main()) | 980 sys.exit(main()) |
| OLD | NEW |