blob: 1e5a749b76618ba42c5d8c477c0f4df973374c48 [file] [log] [blame]
/*
* Copyright (C) 2015 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.coverage;
import com.android.jack.Options;
import com.android.jack.coverage.ProbeDescription.ProbeLineData;
import com.android.jack.ir.ast.JClass;
import com.android.jack.ir.ast.JDefinedClassOrInterface;
import com.android.jack.ir.ast.JInterface;
import com.android.jack.ir.ast.JMethod;
import com.android.jack.ir.ast.JParameter;
import com.android.jack.ir.ast.JSession;
import com.android.jack.ir.ast.JVisitor;
import com.android.jack.ir.formatter.BinarySignatureFormatter;
import com.android.jack.ir.formatter.SourceFormatter;
import com.android.jack.ir.formatter.TypeAndMethodFormatter;
import com.android.jack.ir.sourceinfo.SourceInfo;
import com.android.sched.item.Description;
import com.android.sched.schedulable.Constraint;
import com.android.sched.schedulable.Produce;
import com.android.sched.schedulable.RunnableSchedulable;
import com.android.sched.schedulable.Transform;
import com.android.sched.util.codec.OutputStreamCodec;
import com.android.sched.util.config.HasKeyId;
import com.android.sched.util.config.ThreadConfig;
import com.android.sched.util.config.id.PropertyId;
import com.android.sched.util.file.FileOrDirectory.Existence;
import com.android.sched.util.file.OutputStreamFile;
import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.annotation.Nonnull;
/**
* Writes Jacoco metadata file.
*/
@HasKeyId
@Description("Writes Jacoco metadata file.")
@Constraint(need = CodeCoverageMarker.Complete.class)
@Transform(remove = CodeCoverageMarker.class)
@Produce(CodeCoverageMetadataFile.class)
public class CodeCoverageMetadataFileWriter implements RunnableSchedulable<JSession> {
@Nonnull
public static final PropertyId<OutputStreamFile> COVERAGE_METADATA_FILE = PropertyId.create(
"jack.coverage.metadata.file", "File where the coverage metadata will be emitted",
new OutputStreamCodec(Existence.MAY_EXIST).allowStandardOutputOrError())
.requiredIf(Options.CODE_COVERVAGE.getValue().isTrue())
.addDefaultValue("coverage.em");
private static final TypeAndMethodFormatter binaryFormatter =
BinarySignatureFormatter.getFormatter();
private static final TypeAndMethodFormatter sourceFormatter = SourceFormatter.getFormatter();
private static class Visitor extends JVisitor {
private static final String ONE_TAB = " ";
private static final int ONE_TAB_LENGTH = ONE_TAB.length();
@Nonnull
private final PrintStream writer;
@Nonnull
private final CodeCoverageMarker marker;
@Nonnull
private String currentIndent = "";
public Visitor(@Nonnull PrintStream writer, @Nonnull CodeCoverageMarker marker) {
this.writer = writer;
this.marker = marker;
}
private static String getMethodDesc(@Nonnull JMethod method) {
StringBuilder sb = new StringBuilder();
sb.append('(');
for (JParameter p : method.getParams()) {
sb.append(binaryFormatter.getName(p.getType()));
}
sb.append(')');
sb.append(binaryFormatter.getName(method.getType()));
return sb.toString();
}
private void indent() {
currentIndent = ONE_TAB + currentIndent;
}
private void unindent() {
if (currentIndent.length() < ONE_TAB_LENGTH) {
throw new IllegalStateException("Cannot decrement indentation");
}
currentIndent = currentIndent.substring(ONE_TAB_LENGTH);
}
private void println(@Nonnull String str) {
writer.println(currentIndent + str);
}
private static String getSourceFileNameWithoutPath(@Nonnull JDefinedClassOrInterface x) {
SourceInfo sourceInfo = x.getSourceInfo();
String sourceFilename = "";
if (sourceInfo != SourceInfo.UNKNOWN) {
sourceFilename = sourceInfo.getFileSourceInfo().getFileName();
int pos = sourceFilename.lastIndexOf(File.separator);
if (pos != -1) {
sourceFilename = sourceFilename.substring(pos + 1);
}
}
return sourceFilename;
}
@Override
public boolean visit(@Nonnull JDefinedClassOrInterface x) {
JClass superClass = x.getSuperClass();
String superClassName = (superClass != null) ? sourceFormatter.getName(superClass) : "";
String sourceFilename = getSourceFileNameWithoutPath(x);
List<ProbeDescription> probes = marker.getProbes();
// We do not want to include the added coverage init method in the file (it is not
// instrumented). So we create a copy of the methods list that does not contain this method.
JMethod coverageInitMethod = marker.getInitMethod();
assert coverageInitMethod != null;
List<JMethod> methods = new ArrayList<JMethod>(x.getMethods().size());
for (JMethod m : x.getMethods()) {
if (m != coverageInitMethod) {
methods.add(m);
}
}
indent();
println("\"id\": " + marker.getClassId() + ",");
println("\"name\": \"" + binaryFormatter.getName(x) + "\",");
println("\"superClassName\": \"" + superClassName + "\",");
println("\"sourceFile\": \"" + sourceFilename + "\",");
println("\"interfaces\": [");
indent();
List<JInterface> interfaces = x.getImplements();
for (int i = 0, e = interfaces.size(); i < e; ++i) {
JInterface inf = interfaces.get(i);
String commaSuffix = (i < e - 1 ? "," : "");
println("\"" + binaryFormatter.getName(inf) + "\"" + commaSuffix);
}
unindent();
println("],");
println("\"methods\": [");
indent();
for (int i = 0, e = methods.size(); i < e; ++i) {
JMethod m = methods.get(i);
println("{");
indent();
println("\"id\": " + i + ",");
println("\"name\": \"" + m.getName() + "\",");
println("\"desc\": \"" + getMethodDesc(m) + "\"");
unindent();
if (i == e - 1) {
println("}");
} else {
println("},");
}
}
unindent();
println("],");
println("\"probes\": [");
indent();
for (int i = 0, e = probes.size(); i < e; ++i) {
ProbeDescription p = probes.get(i);
println("{");
indent();
println("\"id\": " + probes.get(i).getProbeId() + ",");
println("\"method\": " + methods.indexOf(p.getMethod()) + ",");
println("\"lines\": [");
indent();
Map<Integer, ProbeLineData> linesMap = p.getLineToData();
Iterator<Entry<Integer, ProbeLineData>> keyIterator = linesMap.entrySet().iterator();
while (keyIterator.hasNext()) {
Entry<Integer, ProbeLineData> entry = keyIterator.next();
int line = entry.getKey().intValue();
ProbeLineData data = entry.getValue();
println("{");
indent();
println("\"line\": " + line + ",");
int insnsCount = data.getNodesCount();
int branchCount = data.getBranchesCount();
println("\"insnCount\": " + insnsCount + ",");
println("\"branchCount\": " + branchCount);
unindent();
if (keyIterator.hasNext()) {
println("},");
} else {
println("}");
}
}
unindent();
println("]");
unindent();
if (i == e - 1) {
println("}");
} else {
println("},");
}
}
unindent();
println("]");
unindent();
return true;
}
}
@Override
public void run(@Nonnull JSession session) throws Exception {
OutputStreamFile outputFile = ThreadConfig.get(COVERAGE_METADATA_FILE);
PrintStream writer = outputFile.getPrintStream();
try {
writeMetadata(session, writer);
} finally {
writer.close();
}
}
private void writeMetadata(@Nonnull JSession session, @Nonnull PrintStream writer) {
writer.println('[');
Iterator<JDefinedClassOrInterface> list = session.getTypesToEmit().iterator();
boolean first = true;
while (list.hasNext()) {
JDefinedClassOrInterface c = list.next();
CodeCoverageMarker marker = c.removeMarker(CodeCoverageMarker.class);
if (marker == null) {
// No code coverage for this class.
continue;
}
if (first) {
writer.println("{");
first = false;
} else {
writer.println(",{");
}
// Write metadata of the class.
new Visitor(writer, marker).accept(c);
writer.print('}');
if (list.hasNext()) {
} else {
writer.println();
}
}
writer.println(']');
}
}