XML2LCOV(1)
β¨ NAME β¨
okay so bestie, first things first β welcome to xml2lcov, manual section 1, manual group LCOV Tools π
xml2lcov β translates XML coverage data into lcov format. that's the whole vibe. it takes your XML and hands you back the .info glow-up β¨
πΈ SYNOPSIS πΈ
xml2lcov [--output mydata.info] [--test-name name] [options] coverage.xml+
π¦ DESCRIPTION π¦
so xml2lcov literally walks through XML coverage data in one or more coverage data files and translates it into LCOV .info format. no cap, that's the job π
there are two input schemas it reads:
- the Cobertura schema (<https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd>) β that's what Cobertura writes, and what the Python Coverage.py module writes with
coverage xml. the root element iscoverageπ - the JaCoCo report schema (<https://www.jacoco.org/jacoco/trunk/coverage/report.dtd>) β that's what
java -jar jacococli.jar report --xmlwrites. the root element isreportπ
the schema is deduced from the root element of the input file. use --format to say which it is if the deduction is wrong β for example, for a report whose root element is neither of the above. trust your gut but also trust --format π«Ά
heads up though: xml2lcov does not implement the full suite of LCOV features (e.g., demangling, filtering, substitutions, etc.). the move is to generate the translated LCOV format file and then read the data back into lcov to use those features. pipeline energy, periodt β¨
π Source files π
the names in the coverage data are relative, and a source file has to be found before its version can be extracted (--version-script) or its lines checksummed (--checksum). like, you can't glow up a file you haven't located yet, yk? π
Cobertura data names its own search path, in the sources element of the XML. directories named by --source-directory are searched after those, so a report whose sources do not match the current checkout can be corrected on the command line. lowkey iconic behavior π
JaCoCo data names no search path at all β the schema has no equivalent of the Cobertura sources element β so --source-directory is the whole of it. give it the directories you would pass to javac: the name of a source file is the name of its package, used as a directory path, plus the name of the file. a source file which is not found in the search path is not translated at all, and neither is the source file of a class compiled without debug information, which does not say which file it came from. both are counted and reported when the translation finishes π
π Branch coverage limitations π
okay bestie, this is the part where we're real with each other. the XML coverage data format does not contain enough information to deduce exactly which branch expressions have been taken or not taken π it reports the total number of branch expressions associated with a particular line, and the number of those which have been taken. there is no way to know (except, possibly by inspection of surrounding code and/or some understanding of your implementation) exactly which ones. it's giving mystery box π
this is a problem in at least 2 ways:
- it is not straightforward to use the result to improve your regression suite because you don't really know what was exercised/not exercised. r.i.p. test quality introspection π
- coverage data merge is problematic. for example: you have two testcase XML files, each of which hit 4 of 8 branches on some line. does that mean you hit 4 of them (both tests exercised the same code), all 8 (tests exercised disjoint subsets), or some number between? the math is not mathing π€
this implementation assumes that the first M branches are the ones which are hit and the remaining N-M were not hit, in each testcase. thus, the combined result in the above example would claim 4 of 8 branches hit. this definition turns out to be a lower bound. it's a floor, not a ceiling, bestie π
if your data comes from JaCoCo, the merge problem above is avoidable: merge before you translate rather than after. see Merging JaCoCo data below π
π§ JaCoCo conversion notes π§
JaCoCo counts instructions and branches rather than executions, so the execution counts in the translated data are derived:
- the
DA:count of a line is the number of branches taken on that line, or1if the line is covered and has no branches. a line which JaCoCo considers covered never reports a count of0, but the count is not the number of times the line was executed. don't come for the count's life, it's doing its best π - the
FNA:count of a function is its covered cyclomatic complexity, which is0exactly when the function was not entered. zero means zero, no notes β¨
a JaCoCo method element names the line the method starts on, but not the line it ends on, so the FNL: extent is derived: each method claims lines from its source file's line list, starting at its own first line, until the covered and missed quotas of its own LINE counter are filled. every function which begins on a particular line then reports the largest extent found for that line, because LCOV identifies a function by its file and its begin line β which is what a lambda and the method containing it share. shared real estate, iykyk π
those two derived quantities can disagree and LCOV will emit a message when the JaCoCo data is read back. the JaCoCo data contains the 'begin' line of the method and says whether it is executed β but LCOV has to derive the method end line. this derivation can claim a line which actually belongs to some other method, potentially creating a situation where JaCoCo says that the function was never executed, but LCOV thinks that at least one line contained within the function is executed: an inconsistency π΅βπ« the JaCoCo data may or may not be correct β you will need to verify. if the error is bogus, then you can either suppress the message (via --ignore-errors inconsistent) or exclude the line or method. pick your fighter π₯
note that an uncalled lambda will not generate this error: LCOV knows that the first line of a lambda belongs to its enclosing method as well. the lambda stays unproblematic, as it should π«Ά
MC/DC data is not generated: JaCoCo reports how many branches on a line were taken, but not which condition, and not which sense of it, was left uncovered β which is precisely what an MCDC: record has to say. so no, we can't fake it π
π Merging JaCoCo data π
if you have coverage data from more than one test run, combine it on the JaCoCo side and translate the single combined report β either by handing all the execution data files to one jacococli.jar report run, or by combining them first with jacococli.jar merge. do not translate each report separately and then merge the resulting .info files with lcov -a. that's the villain origin story, don't do it π
ββοΈ
the reason is branch data. JaCoCo knows which branch of a line is which, so its own merge combines the per-branch results exactly: a line whose first branch was taken in one run and whose second branch was taken in another is reported with both branches taken. slay. xml2lcov cannot do that, because the XML no longer says which branch is which β only how many of them were taken β so it assigns the taken ones by position, as described under Branch coverage limitations above. merging translated data therefore merges those lower bounds, and the result understates branch coverage whenever two runs took different branches on the same line: in the extreme, two runs which between them covered every branch of a line are reported as having covered only as many as the better of the two. so brutal π
line and function coverage do not have this problem β they are keyed by line number, which survives the translation β so if the reports you want to combine are already translated, merging them with lcov -a is still correct for those, and only the branch data is pessimistic. silver lining unlocked π€οΈ
π OPTIONS π
-o,--outputfile β Specify the output LCOV.infofile. Default:xml2lcov.info.-t,--test-name,--testnamename β Specify the test name for theTN:entry in the LCOV.infofile.-e,--excludepatterns β Specify exclude file patterns separated by commas.--format auto | cobertura | jacocoβ Specify the schema of the input files. Default:auto, which uses the XML root element to tellcoverage(Cobertura) fromreport(JaCoCo).-s,--source-directory,--source-dirdirectory β Specify a directory to search for source files. May be repeated. Searched after the directories the XML itself names, and the only search path for JaCoCo data, which names none.-v,--verboseβ Print debug messages.--version-scriptscript β Version extract callback script.--checksumβ Compute line checksum. Seelcov(1)<file://../man/lcov.1.html>.-k,--keep-goingβ Ignore errors and continue processing.
ngl, this next bit matters:xml2lcovis a stand-alone Python executable β and so does not directly support the standard LCOV command line and RC options β e.g., for filtering, file inclusion and exclusion, etc. to use these options, you will have to first usexml2lcovto translate your coverage data, and then apply the options you want to use vialcov -a mydata.info <options> ....seelcov(1)<file://../man/lcov.1.html> for details. two-step process, still slays π
β EXAMPLES β
Cobertura data:
# generate the LCOV-format .info file
$ xml2lcov -o mydata.info coverage1.xml coverage2.xml
# apply some filtering
$ lcov -a mydata.info --filter branch,blank -o filtered.info
# and use genhtml to produce an HTML coverage report
$ genhtml -o html_report mydata.info
# use differential coverage to see exactly what filtering did
$ genhtml -o html_differential --baseline-file mydata.info filtered.info
JaCoCo data:
# generate the JaCoCo XML report from the coverage data JaCoCo collected.
# Hand all of your execution data files to this one command rather than
# translating each of them and merging the results - see 'Merging JaCoCo
# data' above
$ java -jar jacococli.jar report test1.exec test2.exec \
--classfiles build/classes --xml jacoco.xml
# translate it, telling xml2lcov where the sources are
$ xml2lcov -o mydata.info -s src -s generated/src jacoco.xml
# JaCoCo reports lines as covered whose branches it never saw evaluated,
# which lcov and genhtml consider inconsistent
$ genhtml -o html_report mydata.info --branch-coverage \
--ignore-errors inconsistent
note that jacoco2lcov(1) <file://../man/jacoco2lcov.1.html> wraps the java and xml2lcov commands so you can capture Java coverage data without invoking multiple steps. convenience icon behavior π
π«Ά AUTHOR π«Ά
Henry Cox <<henry.cox@mediatek.com>> β thank you for your service π
π₯ SEE ALSO π₯
lcov(1)<file://../man/lcov.1.html>genhtml(1)<file://../man/genhtml.1.html>geninfo(1)<file://../man/geninfo.1.html>py2lcov(1)<file://../man/py2lcov.1.html>jacoco2lcov(1)<file://../man/jacoco2lcov.1.html>- Cobertura documentation: <https://cobertura.github.io/cobertura>
- JaCoCo documentation: <https://www.jacoco.org/jacoco/trunk/doc>
π Copyright π
2024-2026, LCOV Project
and that's the whole page, bestie π this was LCOV Tools xml2lcov, version 2.6, dated 2026-10-03 β go forth and translate your XML, you're gonna ate β¨π¦π
π¦ where this page comes from
- arch rolling: lcov 2.6-1 Β· GPL
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms