Skip to content

Commit c50dc97

Browse files
committed
Improve coverage report format in GitHub pull requests.
Calculate and upload coverage reports to our own Metrics Service instead of Codecov.
1 parent 3f5146f commit c50dc97

File tree

6 files changed

+103
-92
lines changed

6 files changed

+103
-92
lines changed

buildSrc/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ dependencies {
4242
implementation 'digital.wup:android-maven-publish:3.6.2'
4343
implementation 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.20'
4444
implementation 'org.json:json:20180813'
45+
implementation 'org.dom4j:dom4j:2.1.1'
46+
runtime 'jaxen:jaxen:1.2.0'
4547

4648
implementation 'io.opencensus:opencensus-api:0.18.0'
4749
implementation 'io.opencensus:opencensus-exporter-stats-stackdriver:0.18.0'

buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/SdkUtil.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,9 @@ public static File getAndroidJar(Project project) {
5454
getSdkDir(project),
5555
String.format("/platforms/%s/android.jar", android.getCompileSdkVersion()));
5656
}
57+
58+
public static String getFullName(Project project) {
59+
String path = project.getPath();
60+
return path.startsWith(":") ? path.substring(1) : path;
61+
}
5762
}

buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/ci/CheckCoveragePlugin.groovy

Lines changed: 15 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515
package com.google.firebase.gradle.plugins.ci
1616

17+
import static com.google.firebase.gradle.plugins.measurement.MetricsServiceApi.Metric
18+
import static com.google.firebase.gradle.plugins.measurement.MetricsServiceApi.Report
19+
20+
import com.google.firebase.gradle.plugins.measurement.coverage.XmlReportParser
21+
import com.google.firebase.gradle.plugins.measurement.MetricsReportUploader
22+
import com.google.firebase.gradle.plugins.measurement.TestLogFinder
23+
import com.google.firebase.gradle.plugins.SdkUtil
1724
import org.gradle.api.Plugin
1825
import org.gradle.api.Project
1926
import org.gradle.api.tasks.testing.Test
@@ -78,94 +85,16 @@ class CheckCoveragePlugin implements Plugin<Project> {
7885
}
7986

8087
private def upload(task) {
81-
if (System.getenv().containsKey("FIREBASE_CI")) {
82-
def flag = convert(task.project.path)
83-
def report = task.reports.xml.destination
84-
85-
if (System.getenv().containsKey("PROW_JOB_ID")) {
86-
task.logger.quiet("Prow CI detected.")
87-
uploadFromProwJobs(task.project, report, flag)
88-
} else {
89-
uploadFromCodecovSupportedEnvironment(task.project, report, flag)
90-
}
91-
} else {
92-
task.logger.quiet("Reports upload is enabled only on CI.")
93-
}
94-
}
88+
def sdk = SdkUtil.getFullName(task.project)
89+
def xmlReport = task.reports.xml.destination
9590

96-
private def uploadFromProwJobs(project, report, flag) {
97-
// https://github.com/kubernetes/test-infra/blob/master/prow/jobs.md
98-
def name = System.getenv("JOB_NAME")
99-
def type = System.getenv("JOB_TYPE")
100-
def job = System.getenv("PROW_JOB_ID")
101-
def build = System.getenv("BUILD_ID")
102-
def org = System.getenv("REPO_OWNER")
103-
def repo = System.getenv("REPO_NAME")
104-
def branch = System.getenv("PULL_BASE_REF")
105-
def base = System.getenv("PULL_BASE_SHA")
106-
def head = System.getenv("PULL_PULL_SHA")
107-
def pr = System.getenv("PULL_NUMBER")
108-
109-
def commit = head ?: base
110-
111-
// TODO(yifany): use com.google.firebase.gradle.plugins.measurement.TestLogFinder
112-
def domain = "android-ci.firebaseopensource.com"
113-
def bucket = "android-ci"
114-
def dir = type == "presubmit" ? "pr-logs/pull/${org}_${repo}/${pr}" : "logs"
115-
def path = "${name}/${build}"
116-
def url = URLEncoder.encode("https://${domain}/view/gcs/${bucket}/${dir}/${path}", "UTF-8")
117-
118-
project.exec {
119-
environment "VCS_COMMIT_ID", "${commit}"
120-
environment "VCS_BRANCH_NAME", "${branch}"
121-
environment "VCS_PULL_REQUEST", "${pr}"
122-
environment "VCS_SLUG", "${org}/${repo}"
123-
environment "CI_BUILD_URL", "${url}"
124-
environment "CI_BUILD_ID", "${build}"
125-
environment "CI_JOB_ID", "${job}"
126-
127-
commandLine(
128-
"bash",
129-
"-c",
130-
"bash /opt/codecov/uploader.sh -f ${report} -F ${flag}"
131-
)
132-
}
133-
}
91+
def results = new XmlReportParser(sdk, xmlReport).parse()
92+
def log = TestLogFinder.generateCurrentLogLink()
93+
def report = new Report(Metric.Coverage, results, log)
13494

135-
private def uploadFromCodecovSupportedEnvironment(project, report, flag) {
136-
project.exec {
137-
commandLine(
138-
"bash",
139-
"-c",
140-
"bash <(curl -s https://codecov.io/bash) -f ${report} -F ${flag}"
141-
)
95+
new File(task.project.buildDir, 'coverage.json').withWriter {
96+
it.write(report.toJson())
14297
}
98+
MetricsReportUploader.upload(task.project, "${task.project.buildDir}/coverage.json")
14399
}
144-
145-
/*
146-
* Converts a gradle project path to a format complied with Codecov flags.
147-
*
148-
* It transforms a gradle project name into PascalCase, removes the leading `:` and
149-
* replaces all the remaining `:` with `_`.
150-
*
151-
* For example, a gradle project path
152-
*
153-
* `:encoders:firebase-encoders-processor:test-support`
154-
*
155-
* is converted to
156-
*
157-
* `Encoders_FirebaseEncodersProcessor_TestSupport`
158-
*
159-
* after processing.
160-
*
161-
* See https://docs.codecov.io/docs/flags#section-flag-creation for details.
162-
*/
163-
private def convert(path) {
164-
return path
165-
.replaceAll(/([:-])([a-z])/, { "${it[1]}${it[2].toUpperCase()}" })
166-
.replaceAll(/^:/, "")
167-
.replaceAll(/-/, "")
168-
.replaceAll(/:/, "_")
169-
}
170-
171100
}

buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/measurement/MetricsReportUploader.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ public static void upload(Project project, String report) {
3434

3535
String owner = System.getenv("REPO_OWNER");
3636
String repo = System.getenv("REPO_NAME");
37+
String branch = System.getenv("PULL_BASE_REF");
3738
String baseCommit = System.getenv("PULL_BASE_SHA");
3839
String headCommit = System.getenv("PULL_PULL_SHA");
3940
String pullRequest = System.getenv("PULL_NUMBER");
4041

41-
String commit = headCommit != null && !headCommit.isEmpty() ? headCommit : headCommit;
42+
String commit = headCommit != null && !headCommit.isEmpty() ? headCommit : baseCommit;
4243

43-
post(project, report, owner, repo, commit, baseCommit, pullRequest);
44+
post(project, report, owner, repo, commit, branch, baseCommit, pullRequest);
4445
}
4546

4647
private static void post(
@@ -49,17 +50,18 @@ private static void post(
4950
String owner,
5051
String repo,
5152
String commit,
53+
String branch,
5254
String baseCommit,
5355
String pullRequest) {
5456
String post = "-X POST";
5557
String headerAuth = "-H \"Authorization: Bearer $(gcloud auth print-identity-token)\"";
5658
String headerContentType = "-H \"Content-Type: application/json\"";
5759
String body = String.format("-d @%s", report);
5860

59-
String template = "%s/repos/%s/%s/commits/%s/reports";
60-
String endpoint = String.format(template, METRICS_SERVICE_URL, owner, repo, commit);
61+
String template = "%s/repos/%s/%s/commits/%s/reports/?branch=%s";
62+
String endpoint = String.format(template, METRICS_SERVICE_URL, owner, repo, commit, branch);
6163
if (pullRequest != null && !pullRequest.isEmpty()) {
62-
endpoint += String.format("?base_commit=%s&pull_request=%s", baseCommit, pullRequest);
64+
endpoint += String.format("&base_commit=%s&pull_request=%s", baseCommit, pullRequest);
6365
}
6466

6567
String request =

buildSrc/src/main/groovy/com/google/firebase/gradle/plugins/measurement/MetricsServiceApi.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public class MetricsServiceApi {
2222

2323
/** An enum for all supported health metrics. */
2424
enum Metric {
25-
BinarySize
25+
BinarySize,
26+
Coverage
2627
}
2728

2829
/** An api object for a test result. */
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.gradle.plugins.measurement.coverage;
16+
17+
import static com.google.firebase.gradle.plugins.measurement.MetricsServiceApi.Result;
18+
19+
import org.dom4j.Document;
20+
import org.dom4j.DocumentException;
21+
import org.dom4j.Node;
22+
import org.dom4j.io.SAXReader;
23+
import java.io.File;
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
27+
/** Helper class that extracts coverage numbers from JaCoCo Xml report. */
28+
public class XmlReportParser {
29+
30+
private final String sdk;
31+
private final Document document;
32+
33+
public XmlReportParser(String sdk, File report) {
34+
this.sdk = sdk;
35+
try {
36+
this.document = new SAXReader().read(report);
37+
} catch (DocumentException e) {
38+
throw new RuntimeException(e);
39+
}
40+
}
41+
42+
/**
43+
* Returns a list of {@link Result} containing the line coverage number for a SDK overall and
44+
* individual files in that SDK.
45+
*/
46+
public List<Result> parse() {
47+
List<Result> results = new ArrayList<>();
48+
49+
Node report = this.document.selectSingleNode("/report");
50+
double sdkCoverage = calculateCoverage(report);
51+
results.add(new Result(this.sdk, "", sdkCoverage));
52+
53+
List<Node> sources = this.document.selectNodes("//sourcefile");
54+
for (Node source : sources) {
55+
String filename = source.valueOf("@name");
56+
double fileCoverage = calculateCoverage(source);
57+
results.add(new Result(this.sdk, filename, fileCoverage));
58+
}
59+
60+
return results;
61+
}
62+
63+
private double calculateCoverage(Node node) {
64+
Node counter = node.selectSingleNode("counter[@type='LINE']");
65+
if (counter != null) {
66+
int covered = Integer.parseInt(counter.valueOf("@covered"));
67+
int missed = Integer.parseInt(counter.valueOf("@missed"));
68+
return (double) covered / (covered + missed);
69+
}
70+
return 0;
71+
}
72+
}

0 commit comments

Comments
 (0)