Skip to content

Commit

Permalink
Add instrumentation for collecting client-side code coverage.
Browse files Browse the repository at this point in the history
Review at http://gwt-code-reviews.appspot.com/1764803

Review by: cromwellian@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11164 8db76d5a-ed1c-0410-87a9-c151d255dfc7
  • Loading branch information
isbadawi@google.com committed Jul 14, 2012
1 parent 1470301 commit 8549003
Show file tree
Hide file tree
Showing 5 changed files with 651 additions and 2 deletions.
20 changes: 18 additions & 2 deletions dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
Expand Up @@ -67,8 +67,8 @@
import com.google.gwt.dev.jjs.impl.AstDumper;
import com.google.gwt.dev.jjs.impl.CastNormalizer;
import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder;
import com.google.gwt.dev.jjs.impl.CodeSplitter;
import com.google.gwt.dev.jjs.impl.CodeSplitter.MultipleDependencyGraphRecorder;
import com.google.gwt.dev.jjs.impl.CodeSplitter2;
import com.google.gwt.dev.jjs.impl.ControlFlowAnalyzer;
import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
Expand Down Expand Up @@ -103,7 +103,9 @@
import com.google.gwt.dev.jjs.impl.UnifyAst;
import com.google.gwt.dev.jjs.impl.VerifySymbolMap;
import com.google.gwt.dev.jjs.impl.gflow.DataflowOptimizer;
import com.google.gwt.dev.js.BaselineCoverageGatherer;
import com.google.gwt.dev.js.ClosureJsRunner;
import com.google.gwt.dev.js.CoverageInstrumentor;
import com.google.gwt.dev.js.EvalFunctionsAtTopScope;
import com.google.gwt.dev.js.JsBreakUpLargeVarStatements;
import com.google.gwt.dev.js.JsCoerceIntShift;
Expand Down Expand Up @@ -147,6 +149,7 @@
import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
import com.google.gwt.soyc.SoycDashboard;
import com.google.gwt.soyc.io.ArtifactsOutputDirectory;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;

import org.xml.sax.SAXException;

Expand Down Expand Up @@ -290,6 +293,12 @@ public static PermutationResult compilePermutation(TreeLogger logger, UnifiedAst

ResolveRebinds.exec(jprogram, permutation.getOrderedRebindAnswers());

// Traverse the AST to figure out which lines are instrumentable for
// coverage. This has to happen before optimizations because functions might
// be optimized out; we want those marked as "not executed", not "not
// instrumentable".
Multimap<String, Integer> instrumentableLines = BaselineCoverageGatherer.exec(jprogram);

// (4) Optimize the normalized Java AST for each permutation.
int optimizationLevel = options.getOptimizationLevel();
if (optimizationLevel == OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
Expand Down Expand Up @@ -347,6 +356,11 @@ public static PermutationResult compilePermutation(TreeLogger logger, UnifiedAst
*/
JsStackEmulator.exec(jprogram, jsProgram, propertyOracles, jjsmap);

/*
* If coverage is enabled, instrument the AST to record location info.
*/
CoverageInstrumentor.exec(jsProgram, instrumentableLines);

/*
* Work around Safari 5 bug by rewriting a >> b as ~~a >> b.
*
Expand Down Expand Up @@ -1271,7 +1285,9 @@ public void endVisit(JsNameRef x, JsContext ctx) {
// variable names, some of the are property. At least this
// this give us a safe approximation. Ideally we need
// the code removal passes to remove stuff in the scope objects.
nameUsed.add(x.getName().getIdent());
if (x.isResolved()) {
nameUsed.add(x.getName().getIdent());
}
}

@Override
Expand Down
123 changes: 123 additions & 0 deletions dev/core/src/com/google/gwt/dev/js/BaselineCoverageGatherer.java
@@ -0,0 +1,123 @@
/*
* Copyright 2012 Google Inc.
*
* 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.google.gwt.dev.js;

import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.ast.Context;
import com.google.gwt.dev.jjs.ast.JClassLiteral;
import com.google.gwt.dev.jjs.ast.JExpression;
import com.google.gwt.dev.jjs.ast.JMethodCall;
import com.google.gwt.dev.jjs.ast.JProgram;
import com.google.gwt.dev.jjs.ast.JThisRef;
import com.google.gwt.dev.jjs.ast.JVisitor;
import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
import com.google.gwt.dev.js.ast.JsContext;
import com.google.gwt.dev.js.ast.JsExpression;
import com.google.gwt.thirdparty.guava.common.base.Charsets;
import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
import com.google.gwt.thirdparty.guava.common.io.Files;

import java.io.File;
import java.io.IOException;
import java.util.Set;

/**
* Build up a collection of all instrumentable lines, useful for generating
* coverage reports.
*/
public class BaselineCoverageGatherer {
public static Multimap<String, Integer> exec(JProgram jProgram) {
if (System.getProperty("gwt.coverage") == null) {
return null;
}
return new BaselineCoverageGatherer(jProgram, getCoveredSourceFiles()).execImpl();
}

private static Set<String> getCoveredSourceFiles() {
String filename = System.getProperty("gwt.coverage");
File instrumentationFile = new File(filename);
try {
return Sets.newHashSet(Files.readLines(instrumentationFile, Charsets.UTF_8));
} catch (IOException e) {
throw new InternalCompilerException("Could not open " + filename, e);
}
}

private Multimap<String, Integer> instrumentableLines = HashMultimap.create();
private Set<String> instrumentedFiles;
private JProgram jProgram;

private BaselineCoverageGatherer(JProgram jProgram, Set<String> instrumentedFiles) {
this.jProgram = jProgram;
this.instrumentedFiles = instrumentedFiles;
}

private void cover(SourceInfo info) {
if (instrumentedFiles.contains(info.getFileName())) {
instrumentableLines.put(info.getFileName(), info.getStartLine());
}
}

private Multimap<String, Integer> execImpl() {
/**
* Figure out which lines are executable. This is mostly straightforward
* except that we have to avoid some synthetic nodes introduced earlier,
* otherwise e.g. class declarations will be visited.
*/
new JVisitor() {
@Override public void endVisit(JMethodCall x, Context ctx) {
// this is a bit of a hack. The compiler inserts no-arg super calls, but
// there isn't really a way to detect that they're synthetic, and the
// strategy below of comparing source info with that of the enclosing type
// doesn't work because the enclosing type is set to be that of the superclass.
if (x.getTarget().isSynthetic() || x.toSource().equals("super()")) {
return;
}
endVisit((JExpression) x, ctx);
}

@Override public void endVisit(JThisRef x, Context ctx) {
if (x.getSourceInfo().equals(x.getClassType().getSourceInfo())) {
return;
}
endVisit((JExpression) x, ctx);
}

@Override public void endVisit(JClassLiteral x, Context ctx) {
if (x.getSourceInfo().equals(x.getRefType().getSourceInfo())) {
return;
}
endVisit((JExpression) x, ctx);
}

@Override public void endVisit(JExpression x, Context ctx) {
cover(x.getSourceInfo());
}

@Override public void endVisit(JsniMethodBody x, Context ctx) {
new CoverageVisitor(instrumentedFiles) {
@Override public void endVisit(JsExpression x, JsContext ctx) {
cover(x.getSourceInfo());
}
}.accept(x.getFunc());
}
}.accept(jProgram);
return instrumentableLines;
}
}

0 comments on commit 8549003

Please sign in to comment.