blob: d9be8f7d08abc80b511dc191cb041960c4a12e0d [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.jack.tools.jacoco;
import com.google.gson.JsonParseException;
import com.google.gson.stream.JsonReader;
import org.jacoco.core.analysis.IClassCoverage;
import org.jacoco.core.analysis.ICounter;
import org.jacoco.core.analysis.ICoverageVisitor;
import org.jacoco.core.analysis.IMethodCoverage;
import org.jacoco.core.data.ExecutionData;
import org.jacoco.core.data.ExecutionDataStore;
import org.jacoco.core.internal.analysis.ClassCoverageImpl;
import org.jacoco.core.internal.analysis.CounterImpl;
import org.jacoco.core.internal.analysis.MethodCoverageImpl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Code coverage report analyzer.
*/
public class JackCoverageAnalyzer {
@Nonnull private static final String CURRENT_VERSION = "1.0";
@Nonnull private static final String JSON_VERSION_ATTRIBUTE = "version";
@Nonnull private static final String JSON_DATA_ATTRIBUTE = "data";
@Nonnull private final ExecutionDataStore executionDataStore;
@Nonnull private final ICoverageVisitor coverageVisitor;
public JackCoverageAnalyzer(
@Nonnull ExecutionDataStore executionDataStore, @Nonnull ICoverageVisitor coverageVisitor) {
this.executionDataStore = executionDataStore;
this.coverageVisitor = coverageVisitor;
}
/**
* Reads the coverage description file and report each class to the {@link ICoverageVisitor}.
*
* @param jackCoverageDescriptionFile
* @throws IOException
*/
public void analyze(@Nonnull File jackCoverageDescriptionFile) throws IOException {
if (!jackCoverageDescriptionFile.exists()) {
throw new IllegalArgumentException(
"File " + jackCoverageDescriptionFile + " does not exist.");
}
InputStream is = new FileInputStream(jackCoverageDescriptionFile);
try {
analyze(is);
} finally {
is.close();
}
}
private void analyze(@Nonnull InputStream coverageDescriptionInputStream) throws IOException {
JsonReader jsonReader = new JsonReader(new InputStreamReader(coverageDescriptionInputStream));
readMetadata(jsonReader);
}
private void checkVersion(@CheckForNull String version) {
if (version == null) {
throw new JsonParseException("Missing 'version' attribute before coverage metadadata");
}
String[] parts = version.split(Pattern.quote("."));
if (parts.length != 2) {
throw new JsonParseException("Version number format must be x.y");
}
if (!version.equals(CURRENT_VERSION)) {
throw new JsonParseException("Unknown version " + version);
}
}
private void readMetadata(@Nonnull JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
String version = null;
while (jsonReader.hasNext()) {
String attributeName = jsonReader.nextName();
if (attributeName.equals(JSON_VERSION_ATTRIBUTE)) {
// Reads the version so we can parse the JSON accordingly.
version = jsonReader.nextString();
} else if (attributeName.equals(JSON_DATA_ATTRIBUTE)) {
checkVersion(version);
readClasses(jsonReader);
} else {
jsonReader.skipValue();
}
}
jsonReader.endObject();
}
private void readClasses(@Nonnull JsonReader jsonReader) throws IOException {
jsonReader.beginArray();
while (jsonReader.hasNext()) {
IClassCoverage classCoverage = readClass(jsonReader);
coverageVisitor.visitCoverage(classCoverage);
}
jsonReader.endArray();
}
@Nonnull
private IClassCoverage readClass(@Nonnull JsonReader jsonReader) throws IOException {
long id = 0;
String classSignature = null;
String sourceFile = null;
String superClassName = null;
List<IMethodCoverage> methods = new ArrayList<IMethodCoverage>();
List<ProbeDescription> probes = new ArrayList<ProbeDescription>();
List<String> interfaces = new ArrayList<String>();
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String attributeName = jsonReader.nextName();
if ("id".equals(attributeName)) {
id = jsonReader.nextLong();
} else if ("name".equals(attributeName)) {
classSignature = jsonReader.nextString();
} else if ("sourceFile".equals(attributeName)) {
sourceFile = jsonReader.nextString();
} else if ("superClassName".equals(attributeName)) {
superClassName = jsonReader.nextString();
} else if ("interfaces".equals(attributeName)) {
readInterfaces(jsonReader, interfaces);
} else if ("methods".equals(attributeName)) {
readMethods(jsonReader, methods);
} else if ("probes".equals(attributeName)) {
readProbes(jsonReader, probes, methods);
} else {
throw new JsonParseException("Unknown attribute \"" + attributeName + "\"");
}
}
jsonReader.endObject();
final ExecutionData executionData = executionDataStore.get(id);
boolean noMatch;
if (executionData != null) {
noMatch = false;
// Check there is no id collision.
executionData.assertCompatibility(id, classSignature, probes.size());
} else {
noMatch = executionDataStore.contains(classSignature);
}
// Convert class signature to fully-qualified class name.
String className = classSignature.substring(1, classSignature.length() - 1);
// Build the class coverage.
String[] interfacesArray = interfaces.toArray(new String[0]);
ClassCoverageImpl c =
new ClassCoverageImpl(
className, id, noMatch, classSignature, superClassName, interfacesArray);
c.setSourceFileName(sourceFile);
// Update methods with probes.
final boolean[] executionProbes = executionData != null ? executionData.getProbes() : null;
for (ProbeDescription probe : probes) {
final int probeIndex = probe.id;
final boolean active = (executionProbes != null && executionProbes[probeIndex]);
final MethodCoverageImpl methodCoverage = probe.method;
for (ProbeDescription.Line line : probe.lines) {
ICounter instructionCounter;
ICounter branchCounter;
if (active) {
instructionCounter = CounterImpl.getInstance(0, line.instructionsCount);
branchCounter = CounterImpl.getInstance(0, line.branchesCount);
} else {
instructionCounter = CounterImpl.getInstance(line.instructionsCount, 0);
branchCounter = CounterImpl.getInstance(line.branchesCount, 0);
}
methodCoverage.increment(instructionCounter, branchCounter, line.line);
}
}
// Now methods have been updated with probes, add them to the class coverage.
for (IMethodCoverage method : methods) {
c.addMethod(method);
}
return c;
}
private void readInterfaces(@Nonnull JsonReader jsonReader, @Nonnull List<String> interfaces)
throws IOException {
jsonReader.beginArray();
while (jsonReader.hasNext()) {
interfaces.add(jsonReader.nextString());
}
jsonReader.endArray();
}
// Parses probes.
private static void readProbes(
@Nonnull JsonReader jsonReader,
@Nonnull List<ProbeDescription> probes,
@Nonnull List<? extends IMethodCoverage> methods)
throws IOException {
jsonReader.beginArray();
while (jsonReader.hasNext()) {
probes.add(readProbe(jsonReader, methods));
}
jsonReader.endArray();
}
// Parses one probe.
private static ProbeDescription readProbe(
@Nonnull JsonReader jsonReader, @Nonnull List<? extends IMethodCoverage> methods)
throws IOException {
ProbeDescription probe = new ProbeDescription();
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String attributeName = jsonReader.nextName();
if ("id".equals(attributeName)) {
probe.setId(jsonReader.nextInt());
} else if ("method".equals(attributeName)) {
int methodId = jsonReader.nextInt();
probe.setMethod((MethodCoverageImpl) methods.get(methodId));
} else if ("lines".equals(attributeName)) {
readLines(jsonReader, probe);
} else {
throw new JsonParseException("Unknown attribute \"" + attributeName + "\"");
}
}
jsonReader.endObject();
return probe;
}
private static void readLines(@Nonnull JsonReader jsonReader, @Nonnull ProbeDescription probe)
throws IOException {
jsonReader.beginArray();
while (jsonReader.hasNext()) {
jsonReader.beginObject();
int line = -1;
int instructionsCount = -1;
int branchesCount = -1;
while (jsonReader.hasNext()) {
String attributeName = jsonReader.nextName();
if ("line".equals(attributeName)) {
line = jsonReader.nextInt();
} else if ("insnCount".equals(attributeName)) {
instructionsCount = jsonReader.nextInt();
} else if ("branchCount".equals(attributeName)) {
branchesCount = jsonReader.nextInt();
} else {
throw new JsonParseException("Unknown attribute \"" + attributeName + "\"");
}
}
probe.addLine(line, instructionsCount, branchesCount);
jsonReader.endObject();
}
jsonReader.endArray();
}
private static void readMethods(
@Nonnull JsonReader jsonReader, @Nonnull List<IMethodCoverage> methods) throws IOException {
jsonReader.beginArray();
while (jsonReader.hasNext()) {
methods.add(readMethod(jsonReader));
}
jsonReader.endArray();
}
@Nonnull
private static IMethodCoverage readMethod(@Nonnull JsonReader jsonReader) throws IOException {
int id = -1;
String name = null;
String desc = null;
String signature = null;
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String attributeName = jsonReader.nextName();
if ("id".equals(attributeName)) {
id = jsonReader.nextInt();
} else if ("name".equals(attributeName)) {
name = jsonReader.nextString();
} else if ("desc".equals(attributeName)) {
desc = jsonReader.nextString();
} else if ("signature".equals(attributeName)) {
signature = jsonReader.nextString();
} else {
throw new JsonParseException("Unknown attribute \"" + attributeName + "\"");
}
}
jsonReader.endObject();
return new JackMethodCoverage(id, name, desc, signature);
}
}