Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(119)

Side by Side Diff: tools/code_coverage/coverage_posix.py

Issue 8598003: Changes to code coverage infrastructure to allow subset coverage analysis. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remove downloads.croc from CL. Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698