| /* |
| * 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.JConstructor; |
| import com.google.gwt.dev.jjs.ast.JDeclarationStatement; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JField; |
| 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.JValueLiteral; |
| 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.ImmutableSet; |
| 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) { |
| return new BaselineCoverageGatherer(jProgram, getCoveredSourceFiles()).execImpl(); |
| } |
| |
| private static Set<String> getCoveredSourceFiles() { |
| String filename = System.getProperty(CoverageInstrumentor.GWT_COVERAGE_SYSTEM_PROPERTY); |
| if (filename.indexOf(',') != -1) { |
| return ImmutableSet.copyOf(filename.split(",")); |
| } |
| 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.getTarget().isConstructor() |
| && ((JConstructor) x.getTarget()).isDefaultConstructor())) { |
| 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()); |
| } |
| |
| // don't instrument fields whose initializers are literals, because (1) CoverageVisitor |
| // doesn't visit literals because it can introduce syntax errors in some cases, and (2) it's |
| // consistent with other coverage tools, e.g. Emma. |
| @Override public boolean visit(JDeclarationStatement x, Context ctx) { |
| return !(x.getInitializer() instanceof JValueLiteral && |
| x.getVariableRef().getTarget() instanceof JField); |
| } |
| |
| // don't instrument method call arguments; we can get weird coverage results when a call is |
| // spread over several lines |
| @Override public boolean visit(JMethodCall x, Context ctx) { |
| return false; |
| } |
| }.accept(jProgram); |
| return instrumentableLines; |
| } |
| } |