Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge gcovr#326 (--json, --add-tracefile)
closes gcovr#10
closes gcovr#301
closes gcovr#321
  • Loading branch information
latk committed Nov 2, 2019
2 parents 1f5af01 + 500cbbb commit 67a4e6a
Show file tree
Hide file tree
Showing 56 changed files with 1,084 additions and 54 deletions.
1 change: 1 addition & 0 deletions AUTHORS.txt
Expand Up @@ -11,6 +11,7 @@ The following developers contributed to gcovr (ordered alphabetically):
Attie Grande,
Bernhard Breinbauer,
Carlos Jenkins,
Cezary Gapiński
Christian Taedcke,
Dave George,
Dom Postorivo,
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.rst
Expand Up @@ -37,6 +37,10 @@ Improvements and new features:
- :ref:`Configuration file <configuration>` support (experimental).
(:issue:`167`, :issue:`229`, :issue:`279`, :issue:`281`, :issue:`293`,
:issue:`300`, :issue:`304`)
- :ref:`JSON output <json_output>`. (:issue:`301`, :issue:`321`, :issue:`326`)
- :ref:`Combining tracefiles <combining_tracefiles>`
with :option:`gcovr --add-tracefile`.
(:issue:`10`, :issue:`326`)
- Handle cyclic symlinks correctly during coverage data search.
(:issue:`284`)
- Simplification of :option:`--object-directory` heuristics.
Expand All @@ -52,7 +56,7 @@ Improvements and new features:
- Stricter argument handling. (:issue:`267`)
- Reduce XML memory usage by moving to lxml.
(:issue:`1`, :issue:`118`, :issue:`307`)
- Can write multiple reports at the same time
- Can write :ref:`multiple reports <multiple output formats>` at the same time
by giving the output file name to the report format parameter.
Now, ``gcovr --html -o cov.html`` and ``gcovr --html cov.html``
are equivalent.
Expand Down
1 change: 1 addition & 0 deletions README.rst
Expand Up @@ -21,6 +21,7 @@ The ``gcovr`` command can produce different kinds of coverage reports:
- ``--html-details``: HTML report with annotated source files
- ``--xml``: machine readable XML reports in Cobertura_ format
- ``--sonarqube``: machine readable XML reports in Sonarqube format
- ``--json``: JSON report with source files structure and coverage

Thus, gcovr can be viewed
as a command-line alternative to the lcov_ utility, which runs gcov
Expand Down
88 changes: 88 additions & 0 deletions doc/source/guide.rst
Expand Up @@ -228,6 +228,72 @@ in a suitable XML format via the :option:`gcovr --sonarqube` option::
The Sonarqube XML format is documented at
`<https://docs.sonarqube.org/latest/analysis/generic-test/>`_.

.. _json_output:

JSON Output
~~~~~~~~~~~~~~~~~~~~

The ``gcovr`` command can also generate a JSON output using
the ``--json`` and ``--json-pretty`` options::

gcovr --json coverage.json

The ``--json-pretty`` option generates an indented JSON output
that is easier to read.

Structure of file is based on gcov JSON intermediate format
with additional key names specific to gcovr.

Structure of the JSON is following:
::

{
"gcovr/format_version": gcovr_json_version
"files": [file]
}

*gcovr_json_version*: version of gcovr JSON format

Each *file* has the following form:
::

{
"file": file
"lines": [line]
}

*file*: path to source code file, relative to gcovr
root directory.

Each *line* has the following form:
::

{
"branches": [branch]
"count": count
"line_number": line_number
"gcovr/noncode": gcovr_noncode
}

*gcovr_noncode*: if True coverage info on this line should be ignored

Each *branch* has the following form:
::

{
"count": count
"fallthrough": fallthrough
"throw": throw
}

*file*, *line* and *branch* have the structure defined in gcov
intermediate format. This format is documented at
`<https://gcc.gnu.org/onlinedocs/gcc/Invoking-Gcov.html#Invoking-Gcov>`_.

Multiple JSON files can be merged into the coverage data
with sum of lines and branches execution

.. _multiple output formats:

Multiple Output Formats
~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -244,9 +310,31 @@ The following report format flags can take an optional output file name:
- :option:`gcovr --html`
- :option:`gcovr --html-details`
- :option:`gcovr --sonarqube`
- :option:`gcovr --json`

Note that --html-details overrides any value of --html if it is present.

.. _combining_tracefiles:

Combining Tracefiles
~~~~~~~~~~~~~~~~~~~~

You can merge coverage data from multiple runs with :option:`gcovr --add-tracefile`.

For each run, generate :ref:`JSON output <json_output>`:

.. code-block:: bash
... # compile and run first test case
gcovr ... --json run-1.json
... # compile and run second test case
gcovr ... --json run-2.json
Next, merge the json files and generate the desired report::

gcovr --add-tracefile run-1.json --add-tracefile run-2.json --html-details coverage.html


The gcovr Command
-----------------
Expand Down
52 changes: 42 additions & 10 deletions gcovr/__main__.py
Expand Up @@ -44,6 +44,7 @@
parse_config_file, parse_config_into_dict, OutputOrDefault)
from .gcov import (find_existing_gcov_files, find_datafiles,
process_existing_gcov_file, process_datafile)
from .json_generator import (gcovr_json_files_to_coverage)
from .utils import (get_global_stats, AlwaysMatchFilter,
DirectoryPrefixFilter, Logger)
from .version import __version__
Expand All @@ -55,6 +56,7 @@
from .txt_generator import print_text_report
from .summary_generator import print_summary
from .sonarqube_generator import print_sonarqube_report
from .json_generator import print_json_report


#
Expand Down Expand Up @@ -239,6 +241,38 @@ def main(args=None):
for f in filters:
logger.verbose_msg('- {}', f)

covdata = dict()
if options.add_tracefile:
collect_coverage_from_tracefiles(covdata, options, logger)
else:
collect_coverage_from_gcov(covdata, options, logger)

logger.verbose_msg("Gathered coveraged data for {} files", len(covdata))

# Print reports
print_reports(covdata, options, logger)

if options.fail_under_line > 0.0 or options.fail_under_branch > 0.0:
fail_under(covdata, options.fail_under_line, options.fail_under_branch)


def collect_coverage_from_tracefiles(covdata, options, logger):
datafiles = set()

for trace_file in options.add_tracefile:
if not os.path.exists(normpath(trace_file)):
logger.error(
"Bad --add-tracefile option.\n"
"\tThe specified file does not exist.")
sys.exit(1)
datafiles.add(trace_file)
options.root_dir = os.path.abspath(options.root)
gcovr_json_files_to_coverage(datafiles, covdata, options)


def collect_coverage_from_gcov(covdata, options, logger):
datafiles = set()

find_files = find_datafiles
process_file = process_datafile
if options.gcov_files:
Expand All @@ -252,7 +286,6 @@ def main(args=None):
if options.objdir is not None:
options.search_paths.append(options.objdir)

datafiles = set()
for search_path in options.search_paths:
datafiles.update(find_files(search_path, logger, options.exclude_dirs))

Expand All @@ -267,7 +300,6 @@ def main(args=None):
pool.add(process_file, file_)
contexts = pool.wait()

covdata = dict()
toerase = set()
for context in contexts:
for fname, cov in context['covdata'].items():
Expand All @@ -281,14 +313,6 @@ def main(args=None):
if os.path.exists(filepath):
os.remove(filepath)

logger.verbose_msg("Gathered coveraged data for {} files", len(covdata))

# Print reports
print_reports(covdata, options, logger)

if options.fail_under_line > 0.0 or options.fail_under_branch > 0.0:
fail_under(covdata, options.fail_under_line, options.fail_under_branch)


def print_reports(covdata, options, logger):
reports_were_written = False
Expand Down Expand Up @@ -320,6 +344,14 @@ def print_reports(covdata, options, logger):
"Sonarqube output skipped - "
"consider providing output file with `--sonarqube=OUTPUT`.")))

generators.append((
lambda: options.json or options.prettyjson,
[options.json],
print_json_report,
lambda: logger.warn(
"JSON output skipped - "
"consider providing output file with `--json=OUTPUT`.")))

generators.append((
lambda: not reports_were_written,
[],
Expand Down
29 changes: 29 additions & 0 deletions gcovr/configuration.py
Expand Up @@ -421,6 +421,18 @@ def choose(_cls, choices, default=None):
"The --root is the default --filter.",
default='.',
),
GcovrConfigOption(
"add_tracefile", ["-a", "--add-tracefile"],
help="Combine the coverage data from JSON files. "
"Coverage files contains source files structure relative "
"to root directory. Those structures are combined "
"in the output relative to the current root directory. "
"Option can be specified multiple times. "
"When option is used gcov is not run to collect "
"the new coverage data.",
action="append",
default=[],
),
GcovrConfigOption(
'search_paths', config='search-path',
positional=True, nargs='*',
Expand Down Expand Up @@ -590,6 +602,23 @@ def choose(_cls, choices, default=None):
default=None,
const=OutputOrDefault(None),
),
GcovrConfigOption(
"json", ["--json"],
group="output_options",
metavar='OUTPUT',
help="Generate a JSON report. "
"OUTPUT is optional and defaults to --output.",
nargs='?',
type=OutputOrDefault,
default=None,
const=OutputOrDefault(None),
),
GcovrConfigOption(
"prettyjson", ["--json-pretty"],
group="output_options",
help="Pretty-print the JSON report. Implies --json. Default: {default!s}.",
action="store_true",
),
GcovrConfigOption(
"filter", ["-f", "--filter"],
group="filter_options",
Expand Down
8 changes: 4 additions & 4 deletions gcovr/coverage.py
Expand Up @@ -103,7 +103,7 @@ def update(self, other):
r"""Merge LineCoverage information."""
assert self.lineno == other.lineno
self.count += other.count
self.noncode |= other.noncode
self.noncode &= other.noncode
for branch_id, branch_cov in other.branches.items():
self.branch(branch_id).update(branch_cov)

Expand All @@ -127,21 +127,21 @@ def __init__(self, filename):
self.filename = filename
self.lines = {} # type: Dict[int, LineCoverage]

def line(self, lineno):
def line(self, lineno, **defaults):
# type: (int) -> LineCoverage
r"""Get or create the LineCoverage for that lineno."""
try:
return self.lines[lineno]
except KeyError:
self.lines[lineno] = line_cov = LineCoverage(lineno)
self.lines[lineno] = line_cov = LineCoverage(lineno, **defaults)
return line_cov

def update(self, other):
# type: (FileCoverage) -> None
r"""Merge FileCoverage information."""
assert self.filename == other.filename
for lineno, line_cov in other.lines.items():
self.line(lineno).update(line_cov)
self.line(lineno, noncode=True).update(line_cov)

def uncovered_lines_str(self):
# type: () -> str
Expand Down

0 comments on commit 67a4e6a

Please sign in to comment.