sonar-golang icon indicating copy to clipboard operation
sonar-golang copied to clipboard

Question: Single vs Multiple coverage.xml files

Open kenjones-cisco opened this issue 7 years ago • 9 comments

Description

In the README I noticed the following section:

You must end-up with one coverage file per directory:

pkg1/coverage.xml
pkg2/coverage.xml
pkg3/coverage.xml
...

Currently I'm able to generate a single coverage.xml file for an entire project with multiple packages within the same project. And cobertura within Jenkins is able to read and process the file and show all of the packages.

Example single coverage.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-03.dtd">
<coverage line-rate="0" branch-rate="0" version="" timestamp="1506622955562">
	<sources>
		<source>/usr/local/go/src</source>
		<source>/go/src</source>
		<source>/authlib/src</source>
	</sources>
	<packages>
		<package name="gitscm.cisco.com/ccdev/authlib/authz" line-rate="0" branch-rate="0" complexity="0">
			<classes>
				<class name="-" filename="gitscm.cisco.com/ccdev/authlib/authz/context.go" line-rate="0" branch-rate="0" complexity="0">
					<methods>
						<method name="key" signature="" line-rate="0" branch-rate="0">
							<lines>
								<line number="11" hits="11"></line>
							</lines>
						</method>
						<method name="GetUsername" signature="" line-rate="0" branch-rate="0">
							<lines>
								<line number="16" hits="6"></line>
								<line number="17" hits="5"></line>
								<line number="19" hits="1"></line>
							</lines>
						</method>
						<method name="SetUsername" signature="" line-rate="0" branch-rate="0">
							<lines>
								<line number="24" hits="5"></line>
							</lines>
						</method>
					</methods>
					<lines>
						<line number="11" hits="11"></line>
						<line number="16" hits="6"></line>
						<line number="17" hits="5"></line>
						<line number="19" hits="1"></line>
						<line number="24" hits="5"></line>
					</lines>
				</class>
			</classes>
		</package>
		<package name="gitscm.cisco.com/ccdev/authlib/authz/casbin" line-rate="0" branch-rate="0" complexity="0">
			<classes>
				<class name="-" filename="gitscm.cisco.com/ccdev/authlib/authz/casbin/ldap_role_manager.go" line-rate="0" branch-rate="0" complexity="0">
					<methods>
						<method name="CheckLdapEnv" signature="" line-rate="0" branch-rate="0">
							<lines>
								<line number="32" hits="4"></line>
								<line number="33" hits="1"></line>
								<line number="35" hits="3"></line>
								<line number="36" hits="1"></line>
								<line number="38" hits="2"></line>
								<line number="39" hits="1"></line>
								<line number="41" hits="1"></line>
							</lines>
						</method>
						<method name="LDAPRoleManager" signature="" line-rate="0" branch-rate="0">
							<lines>
								<line number="46" hits="1"></line>
							</lines>
						</method>
						<method name="@46:9" signature="" line-rate="0" branch-rate="0">
							<lines>
								<line number="47" hits="1"></line>
								<line number="48" hits="1"></line>
								<line number="49" hits="1"></line>
								<line number="50" hits="1"></line>
								<line number="53" hits="1"></line>
								<line number="54" hits="1"></line>
								<line number="56" hits="1"></line>
							</lines>
						</method>
						<method name="NewLdapRoleManager" signature="" line-rate="0" branch-rate="0">
							<lines>
								<line number="62" hits="2"></line>
								<line number="70" hits="2"></line>
								<line number="71" hits="2"></line>
								<line number="72" hits="1"></line>
								<line number="74" hits="1"></line>
								<line number="78" hits="1"></line>
							</lines>
						</method>
					</methods>
					<lines>
						<line number="32" hits="4"></line>
						<line number="33" hits="1"></line>
						<line number="35" hits="3"></line>
						<line number="36" hits="1"></line>
						<line number="38" hits="2"></line>
						<line number="39" hits="1"></line>
						<line number="41" hits="1"></line>
						<line number="46" hits="1"></line>
						<line number="47" hits="1"></line>
						<line number="48" hits="1"></line>
						<line number="49" hits="1"></line>
						<line number="50" hits="1"></line>
						<line number="53" hits="1"></line>
						<line number="54" hits="1"></line>
						<line number="56" hits="1"></line>
						<line number="62" hits="2"></line>
						<line number="70" hits="2"></line>
						<line number="71" hits="2"></line>
						<line number="72" hits="1"></line>
						<line number="74" hits="1"></line>
						<line number="78" hits="1"></line>
					</lines>
				</class>
			</classes>
		</package>
	</packages>
</coverage>
  1. Is there a technical limitation within SonarQube that requires the coverage to be done as one coverage.xml per package? Or is this rule imposed by the plugin?

  2. As a follow up, does the coverage.xml have to be in the same directory as the code? Or is it possible to leverage a cover directory that has a directory for each package and the coverage.xml file exists within that directory?

Example Directory structure for coverage:

cover/
    github.com/org/repo/pkg1/coverage.xml
    github.com/org/repo/pkg2/coverage.xml
    github.com/org/repo/pkg3/coverage.xml
    ...

kenjones-cisco avatar Sep 28 '17 19:09 kenjones-cisco

Hi,

Thanks for your questions.

  1. The plugin use the report generated by gocov-xml. We used gocov to convert a coverage profile generate by go test. But we can't launch go test for all packages in one time (if you have a any idea or solution for do that,...). So we need to launch go test and generate the coverage profile and send this profile to gocov for convert and the conversion to gocov-xml.

  2. It's not a problem. I used Files.walk for explore the project and visit directories.

How did you do for generate a single coverage file ? If it's possible I will can modify the plugin to use a single coverage file.

thibaultfalque avatar Sep 28 '17 21:09 thibaultfalque

Generating single coverage.xml

workdir=cover
profile="$workdir/cover.out"
mode=count

for pkg in $(glide nv);
do
    for subpkg in $(go list "${pkg}");
    do
        f="$workdir/$(echo "$subpkg" | tr / -).cover"
        go test -v -covermode="$mode" -coverprofile="$f" "$subpkg" >> test.out
    done
done

set -- "$workdir"/*.cover
if [ ! -f "$1" ]; then
    echo "No Test Cases"; exit 0
fi
echo "mode: $mode" >"$profile"
grep -h -v "^mode:" "$workdir"/*.cover >>"$profile"

rm -f test.xml coverage.xml

go2xunit -input test.out -output test.xml

gocov convert "$profile" | gocov-xml > coverage.xml

That is how I have it scripted to generate the test.xml and coverage.xml that I have been using.

My alternate approach would be just to write the coverage.xml files into the cover/ directory as I don't like the idea of writing any files to my actual package directories.

kenjones-cisco avatar Sep 28 '17 22:09 kenjones-cisco

@thibaultfalque I guess that making sure to search first a single cover file in the root directory else look for multiple cover files in subdirectories could do the trick.

@kenjones-cisco thanks for the script. Would you agree that we display it in the README page of the script?

danielleberre avatar Sep 29 '17 05:09 danielleberre

Sure, no problem.

kenjones-cisco avatar Sep 29 '17 08:09 kenjones-cisco

FYI, to make it not specific to glide, you should can use this:

workdir=cover
profile="$workdir/cover.out"
mode=count

for pkg in $(go list ./...);
do
    f="$workdir/$(echo "$pkg" | tr / -).cover"
    go test -v -covermode="$mode" -coverprofile="$f" "$pkg" >> test.out
done

set -- "$workdir"/*.cover
if [ ! -f "$1" ]; then
    rm -f "$results" || :
    echo "No Test Cases"; exit 0
fi
echo "mode: $mode" >"$profile"
grep -h -v "^mode:" "$workdir"/*.cover >>"$profile"

rm -f test.xml coverage.xml

go2xunit -input test.out -output test.xml

gocov convert "$profile" | gocov-xml > coverage.xml

kenjones-cisco avatar Sep 29 '17 09:09 kenjones-cisco

@thibaultfalque are we done here?

danielleberre avatar Dec 26 '17 11:12 danielleberre

See the PR by @Kernle32DLL #50

thibaultfalque avatar Dec 29 '17 17:12 thibaultfalque

For a bit more context what gocov test ./... does - it internally does exactly that. Its walks trough the folders, and executes go test, and gocov convert. The nice thing is, that this method aggregates the results, and creates a single coverage.xml, without some shell magic (which is good, since this way it can be used across platforms).

Also also - Golang 1.10 will bring support for go test ./... -coverprofile..., which was not possible so far (hence all the extra hops with executing go test in each package separately).

kernle32dll avatar Dec 30 '17 11:12 kernle32dll

Thanks @Kernle32DLL for your explanation

thibaultfalque avatar Dec 30 '17 13:12 thibaultfalque