Skip to content

Commit 2c49635

Browse files
committed
Switch to python's logging module
Description: - Fix #41 - Refactor log traces to use python logging - Add `--verbose` option which sets logging level to debug - Add some debug level traces to print internal processing
1 parent c264133 commit 2c49635

File tree

4 files changed

+64
-34
lines changed

4 files changed

+64
-34
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Here are some common filtering combinations you may find useful:
7878
```bash
7979
$ fastcov.py --exclude /usr/include test/ # Exclude system header files and test files from final report
8080
$ fastcov.py --include src/ # Only include files with "src/" in its path in the final report
81-
$ fastcov.py --source-files ../src/source1.cpp ../src/source2.cpp # Only include exactly ../src/source1.cpp and ../src/source2.cpp in the final rpeort
81+
$ fastcov.py --source-files ../src/source1.cpp ../src/source2.cpp # Only include exactly ../src/source1.cpp and ../src/source2.cpp in the final report
8282
$ fastcov.py --branch-coverage # Only include most useful branches (discards exceptional branches and initializer list branches)
8383
$ fastcov.py --exceptional-branch-coverage # Include ALL branches in coverage report
8484
```

example/build.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ ninja
1919
ctest
2020

2121
# Run fastcov with smart branch filtering, as well as system header (/usr/include) and cmake project test file filtering
22-
${BASE_DIR}/fastcov.py -t ExampleTest --gcov gcov-9 --branch-coverage --exclude /usr/include cmake_project/test/ --lcov -o example.info
22+
# ${BASE_DIR}/fastcov.py -t ExampleTest --gcov gcov-9 --branch-coverage --include cmake_project/src/ --verbose --lcov -o example.info
23+
${BASE_DIR}/fastcov.py -t ExampleTest --gcov gcov-9 --branch-coverage --exclude /usr/include cmake_project/test/ --verbose --lcov -o example.info
2324

2425
# Generate report with lcov's genhtml
2526
genhtml --branch-coverage example.info -o coverage_report

fastcov.py

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import glob
2323
import json
2424
import time
25+
import logging
2526
import argparse
2627
import threading
2728
import subprocess
@@ -36,12 +37,14 @@
3637
GCOVS_TOTAL = []
3738
GCOVS_SKIPPED = []
3839

39-
def logger(line, quiet=True):
40-
if not quiet:
41-
print("[{:.3f}s] {}".format(stopwatch(), line))
40+
# Disable all logging in case developers are using this as a module
41+
logging.disable(level=logging.CRITICAL)
4242

43-
# Global logger defaults to quiet in case developers are using as module
44-
log = logger
43+
class FastcovFormatter(logging.Formatter):
44+
def format(self, record):
45+
record.levelname = record.levelname.lower()
46+
log_message = super(FastcovFormatter, self).format(record)
47+
return "[{:.3f}s] {}".format(stopwatch(), log_message)
4548

4649
def chunks(l, n):
4750
"""Yield successive n-sized chunks from l."""
@@ -79,6 +82,7 @@ def getFilteredCoverageFiles(coverage_files, exclude):
7982
def excludeGcda(gcda):
8083
for ex in exclude:
8184
if ex in gcda:
85+
logging.debug("Omitting %s due to '--exclude-gcda %s'", gcda, ex)
8286
return False
8387
return True
8488
return list(filter(excludeGcda, coverage_files))
@@ -89,7 +93,8 @@ def findCoverageFiles(cwd, coverage_files, use_gcno):
8993
coverage_type = "gcno" if use_gcno else "gcda"
9094
coverage_files = glob.glob(os.path.join(os.path.abspath(cwd), "**/*." + coverage_type), recursive=True)
9195

92-
log("Found {} coverage files ({})".format(len(coverage_files), coverage_type))
96+
logging.info("Found {} coverage files ({})".format(len(coverage_files), coverage_type))
97+
logging.debug("Coverage files found:\n %s", "\n ".join(coverage_files))
9398
return coverage_files
9499

95100
def gcovWorker(cwd, gcov, files, chunk, gcov_filter_options, branch_coverage):
@@ -117,7 +122,7 @@ def processGcdas(cwd, gcov, jobs, coverage_files, gcov_filter_options, branch_co
117122
threads.append(t)
118123
t.start()
119124

120-
log("Spawned {} gcov threads, each processing at most {} coverage files".format(len(threads), chunk_size))
125+
logging.info("Spawned {} gcov threads, each processing at most {} coverage files".format(len(threads), chunk_size))
121126
for t in threads:
122127
t.join()
123128

@@ -131,22 +136,34 @@ def processGcov(cwd, gcov, files, gcov_filter_options):
131136
if gcov_filter_options["sources"]:
132137
if gcov["file_abs"] in gcov_filter_options["sources"]:
133138
files.append(gcov)
139+
logging.debug("Accepted coverage for '%s'", gcov["file_abs"])
140+
else:
141+
logging.debug("Skipping coverage for '%s' due to option '--source-files'", gcov["file_abs"])
134142
return
135143

136144
# Check exclude filter
137145
for ex in gcov_filter_options["exclude"]:
138146
if ex in gcov["file_abs"]:
147+
logging.debug("Skipping coverage for '%s' due to option '--exclude %s'", gcov["file_abs"], ex)
139148
return
140149

141150
# Check include filter
142151
if gcov_filter_options["include"]:
152+
included = False
143153
for ex in gcov_filter_options["include"]:
144154
if ex in gcov["file_abs"]:
155+
included = True
145156
files.append(gcov)
157+
logging.debug("Accepted coverage for '%s'", gcov["file_abs"])
146158
break
159+
160+
if not included:
161+
logging.debug("Skipping coverage for '%s' due to option '--include %s'", gcov["file_abs"], " ".join(gcov_filter_options["include"]))
162+
147163
return
148164

149165
files.append(gcov)
166+
logging.debug("Accepted coverage for '%s'", gcov["file_abs"])
150167

151168
def processGcovs(cwd, gcov_files, gcov_filter_options):
152169
files = []
@@ -217,7 +234,7 @@ def getSourceLines(source, fallback_encodings=[]):
217234
except UnicodeDecodeError:
218235
pass
219236

220-
log("Warning: could not decode '{}' with {} or fallback encodings ({}); ignoring errors".format(source, default_encoding, ",".join(fallback_encodings)))
237+
logging.warning("Could not decode '{}' with {} or fallback encodings ({}); ignoring errors".format(source, default_encoding, ",".join(fallback_encodings)))
221238
with open(source, errors="ignore") as f:
222239
return f.readlines()
223240

@@ -272,7 +289,7 @@ def scanExclusionMarkers(fastcov_json, jobs, exclude_branches_sw, include_branch
272289
threads.append(t)
273290
t.start()
274291

275-
log("Spawned {} threads each scanning at most {} source files".format(len(threads), chunk_size))
292+
logging.info("Spawned {} threads each scanning at most {} source files".format(len(threads), chunk_size))
276293
for t in threads:
277294
t.join()
278295

@@ -395,7 +412,7 @@ def addLists(list1, list2):
395412
return [b1 + b2 for b1, b2 in zip(list1, list2)]
396413
else:
397414
# One report had different number branch measurements than the other, print a warning
398-
sys.stderr.write("Warning: possible loss of correctness. Different number of branches for same line when combining reports ({} vs {})\n".format(list1, list2))
415+
logging.warning("Possible loss of correctness. Different number of branches for same line when combining reports ({} vs {})\n".format(list1, list2))
399416
return list1 if len(list1) > len(list2) else list2
400417

401418
def combineReports(base, overlay):
@@ -496,25 +513,25 @@ def parseAndCombine(paths):
496513

497514
if not base_report:
498515
base_report = report
499-
log("Setting {} as base report".format(path))
516+
logging.info("Setting {} as base report".format(path))
500517
else:
501518
combineReports(base_report, report)
502-
log("Adding {} to base report".format(path))
519+
logging.info("Adding {} to base report".format(path))
503520

504521
return base_report
505522

506523
def combineCoverageFiles(args):
507-
log("Performing combine operation")
524+
logging.info("Performing combine operation")
508525
fastcov_json = parseAndCombine(args.combine)
509526
dumpFile(fastcov_json, args)
510527

511528
def dumpFile(fastcov_json, args):
512529
if args.lcov:
513530
dumpToLcovInfo(fastcov_json, args.output)
514-
log("Created lcov info file '{}'".format(args.output))
531+
logging.info("Created lcov info file '{}'".format(args.output))
515532
else:
516533
dumpToJson(fastcov_json, args.output)
517-
log("Created fastcov json file '{}'".format(args.output))
534+
logging.info("Created fastcov json file '{}'".format(args.output))
518535

519536
def tupleToDotted(tup):
520537
return ".".join(map(str, tup))
@@ -558,17 +575,11 @@ def parseArgs():
558575
parser.add_argument('-t', '--test-name', dest='test_name', default="", help='Specify a test name for the coverage. Equivalent to lcov\'s `-t`.')
559576
parser.add_argument('-C', '--add-tracefile', dest='combine', nargs="+", help='Combine multiple coverage files into one. If this flag is specified, fastcov will do a combine operation instead invoking gcov. Equivalent to lcov\'s `-a`.')
560577

578+
parser.add_argument('-V', '--verbose', dest="verbose", action="store_true", help="Print more detailed information about what fastcov is doing")
561579
parser.add_argument('-v', '--version', action="version", version='%(prog)s {version}'.format(version=__version__), help="Show program's version number and exit")
562580

563581
args = parser.parse_args()
564582

565-
def arg_logger(line):
566-
logger(line, quiet=args.quiet)
567-
568-
# Change global logger settings to reflect arguments
569-
global log
570-
log = arg_logger
571-
572583
return args
573584

574585
def checkPythonVersion(version):
@@ -583,9 +594,26 @@ def checkGcovVersion(version):
583594
sys.stderr.write("Minimum gcov version {} required, found {}\n".format(tupleToDotted(MINIMUM_GCOV), tupleToDotted(version)))
584595
sys.exit(2)
585596

597+
def setupLogging(quiet, verbose):
598+
handler = logging.StreamHandler()
599+
handler.setFormatter(FastcovFormatter("[%(levelname)s]: %(message)s"))
600+
601+
root = logging.getLogger()
602+
root.setLevel(logging.INFO)
603+
root.addHandler(handler)
604+
605+
if not quiet:
606+
logging.disable(level=logging.NOTSET) # Re-enable logging
607+
608+
if verbose:
609+
root.setLevel(logging.DEBUG)
610+
586611
def main():
587612
args = parseArgs()
588613

614+
# Setup logging
615+
setupLogging(args.quiet, args.verbose)
616+
589617
# Need at least python 3.5 because of use of recursive glob
590618
checkPythonVersion(sys.version_info[0:2])
591619

@@ -603,12 +631,12 @@ def main():
603631
# If gcda/gcno filtering is enabled, filter them out now
604632
if args.excludepre:
605633
coverage_files = getFilteredCoverageFiles(coverage_files, args.excludepre)
606-
log("Found {} coverage files after filtering".format(len(coverage_files)))
634+
logging.info("Found {} coverage files after filtering".format(len(coverage_files)))
607635

608636
# We "zero" the "counters" by simply deleting all gcda files
609637
if args.zerocounters:
610638
removeFiles(coverage_files)
611-
log("Removed {} .gcda files".format(len(coverage_files)))
639+
logging.info("Removed {} .gcda files".format(len(coverage_files)))
612640
return
613641

614642
# Fire up one gcov per cpu and start processing gcdas
@@ -618,15 +646,16 @@ def main():
618646
# Summarize processing results
619647
gcov_total = sum(GCOVS_TOTAL)
620648
gcov_skipped = sum(GCOVS_SKIPPED)
621-
log("Processed {} .gcov files ({} total, {} skipped)".format(gcov_total - gcov_skipped, gcov_total, gcov_skipped))
649+
logging.info("Processed {} .gcov files ({} total, {} skipped)".format(gcov_total - gcov_skipped, gcov_total, gcov_skipped))
622650

623651
# Distill all the extraneous info gcov gives us down to the core report
624652
fastcov_json = distillReport(intermediate_json_files, args)
625-
log("Aggregated raw gcov JSON into fastcov JSON report")
653+
logging.info("Aggregated raw gcov JSON into fastcov JSON report")
654+
logging.debug("Final report will contain coverage for the following %d source files:\n %s", len(fastcov_json["sources"]), "\n ".join(fastcov_json["sources"]))
626655

627656
# Scan for exclusion markers
628657
scanExclusionMarkers(fastcov_json, args.jobs, args.exclude_branches_sw, args.include_branches_sw, args.minimum_chunk, args.fallback_encodings)
629-
log("Scanned {} source files for exclusion markers".format(len(fastcov_json["sources"])))
658+
logging.info("Scanned {} source files for exclusion markers".format(len(fastcov_json["sources"])))
630659

631660
# Dump to desired file format
632661
dumpFile(fastcov_json, args)

test/functional/run_all.sh

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ ${TEST_DIR}/fastcov.py --gcov gcov-9 --zerocounters # Clear previous test cover
3434
ctest -R ctest_1
3535

3636
# Test (basic report generation - no branches)
37-
coverage run --append ${TEST_DIR}/fastcov.py --gcov gcov-9 --exclude cmake_project/test/ --lcov -o test1.actual.info
37+
coverage run --append ${TEST_DIR}/fastcov.py --gcov gcov-9 --verbose --exclude cmake_project/test/ --lcov -o test1.actual.info
3838
cmp test1.actual.info ${TEST_DIR}/expected_results/test1.expected.info
3939

4040
coverage run --append ${TEST_DIR}/fastcov.py --gcov gcov-9 --exclude cmake_project/test/ -o test1.actual.fastcov.json
@@ -52,15 +52,15 @@ coverage run --append ${TEST_DIR}/fastcov.py --exceptional-branch-coverage --gco
5252
${TEST_DIR}/json_cmp.py test2.actual.fastcov.json ${TEST_DIR}/expected_results/test2.expected.fastcov.json
5353

5454
# Test (basic lcov info - with branches; equivalent --include)
55-
coverage run --append ${TEST_DIR}/fastcov.py --exceptional-branch-coverage --gcov gcov-9 --include src/ --lcov -o test3.actual.info
55+
coverage run --append ${TEST_DIR}/fastcov.py --exceptional-branch-coverage --gcov gcov-9 --verbose --include src/ --lcov -o test3.actual.info
5656
cmp test3.actual.info ${TEST_DIR}/expected_results/test2.expected.info
5757

5858
# Test (basic lcov info - with branches; equivalent --exclude-gcda)
59-
coverage run --append ${TEST_DIR}/fastcov.py --exceptional-branch-coverage --gcov gcov-9 --exclude-gcda test1.cpp.gcda --lcov -o test4.actual.info
59+
coverage run --append ${TEST_DIR}/fastcov.py --exceptional-branch-coverage --gcov gcov-9 --verbose --exclude-gcda test1.cpp.gcda --lcov -o test4.actual.info
6060
cmp test4.actual.info ${TEST_DIR}/expected_results/test2.expected.info
6161

6262
# Test (basic lcov info - with branches; equivalent --source-files)
63-
coverage run --append ${TEST_DIR}/fastcov.py --exceptional-branch-coverage --gcov gcov-9 --source-files ../src/source1.cpp ../src/source2.cpp --lcov -o test5.actual.info
63+
coverage run --append ${TEST_DIR}/fastcov.py --exceptional-branch-coverage --gcov gcov-9 --verbose --source-files ../src/source1.cpp ../src/source2.cpp --lcov -o test5.actual.info
6464
cmp test5.actual.info ${TEST_DIR}/expected_results/test2.expected.info
6565

6666
# Test (basic lcov info - with branches; gcno untested file coverage)
@@ -146,4 +146,4 @@ cmp multitest.actual.info ${TEST_DIR}/expected_results/multitest.expected.info
146146

147147
# Write out coverage as xml
148148
coverage xml -o coverage.xml
149-
# coverage html # Generate HTML report
149+
coverage html # Generate HTML report

0 commit comments

Comments
 (0)