blob: 8b9beb0010892f4c5d087f629204b646bb3bda03 [file] [log] [blame]
/*
* Copyright 2014 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.core.ext.linker;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.CompilerOptionsImpl;
import com.google.gwt.dev.util.Util;
import com.google.gwt.dev.util.arg.OptionOptimize;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
import com.google.gwt.thirdparty.guava.common.collect.Iterables;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Multimap;
import com.google.gwt.util.tools.Utility;
import junit.framework.TestCase;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
/**
* Basic tests for Source maps and (new) soyc reports.
*/
public class SymbolMapTest extends TestCase {
private static File[] filterByName(File dir, final String namePattern) {
return dir.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().matches(namePattern);
}
});
}
/**
* This class represents each row in a generated SymbolMap file. Because not all fields are
* serialized, such as CastableTypeMap, some methods are not implemented.
*
*/
static final class SimpleSymbolData implements SymbolData {
static Map<String, SimpleSymbolData> readSymbolMap(File filePath) throws IOException {
Map<String, SimpleSymbolData> sdata = Maps.newLinkedHashMap();
BufferedReader reader = new BufferedReader(new FileReader(filePath));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("#")) {
// reading a comment
continue;
}
SimpleSymbolData symbolData = new SimpleSymbolData(line);
String key = symbolData.getJsniIdent();
assertFalse("Duplicate signature <" + key + "> in symbol maps", sdata.containsKey(key));
sdata.put(key,symbolData);
}
return sdata;
}
private static final String NOT_IMPLEMENTED_MESSAGE =
"Data not available in current serialized SymbolMap";
private String jsName;
private String jsniIdent;
private String className;
private String memberName;
private String sourceUri;
private int sourceLine;
private int fragmentNumber;
public SimpleSymbolData(String line) {
this.parseFromLine(line);
}
@Override
public String getClassName() {
return this.className;
}
@Override
public int getFragmentNumber() {
return this.fragmentNumber;
}
@Override
public String getJsniIdent() {
return this.jsniIdent;
}
@Override
public String getMemberName() {
return this.memberName;
}
@Override
public String getRuntimeTypeId() {
throw new UnsupportedOperationException(NOT_IMPLEMENTED_MESSAGE);
}
@Override
public int getSourceLine() {
return this.sourceLine;
}
@Override
public String getSourceUri() {
return this.sourceUri;
}
@Override
public String getSymbolName() {
return this.jsName;
}
@Override
public boolean isClass() {
return this.memberName == null || this.memberName.isEmpty();
}
@Override
public boolean isField() {
return !this.isClass() && jsniIdent.indexOf("(") < 0;
}
@Override
public boolean isMethod() {
return !this.isClass() && jsniIdent.indexOf("(") >= 0;
}
@Override
public String toString() {
return jsniIdent + " -> " + jsName;
}
private void parseFromLine(String line) {
String[] fields = line.split(",");
this.jsName = fields[0];
this.jsniIdent = fields[1].isEmpty() ? fields[2] : fields[1];
this.className = fields[2];
this.memberName = fields[3]; // may be empty
this.sourceUri = fields[4];
this.sourceLine = Integer.parseInt(fields[5]);
this.fragmentNumber = Integer.parseInt(fields[6]);
}
}
/**
* Loads the symbol map from a file.
*/
private Iterable<Map<String, SimpleSymbolData>> loadSymbolMaps(File root) throws Exception {
// Testing SourceMaps as SymbolMap replacement
// make sure the files have been produced
assertTrue(root.exists());
File[] symbolMapFiles = filterByName(root, "(.*)\\.symbolMap");
assertTrue(symbolMapFiles.length >= 1);
return Iterables.transform(Arrays.asList(symbolMapFiles),
new Function<File, Map<String, SimpleSymbolData>>() {
@Override
public Map<String, SimpleSymbolData> apply(File file) {
try {
return SimpleSymbolData.readSymbolMap(file);
} catch (IOException e) {
fail("Error reading symbol map " + file.getAbsolutePath());
}
return null;
}
});
}
private static final String JSE_METHOD =
"com.google.gwt.core.client.JavaScriptException::getThrown()Ljava/lang/Object;";
private static final String JSE_FIELD = "com.google.gwt.core.client.JavaScriptException::message";
private static final String JSE_CLASS = "com.google.gwt.core.client.JavaScriptException";
private static final String UNINSTANTIABLE_CLASS = "com.google.gwt.lang.Array";
/**
* Tests for the presence of some elements.
*/
private void assertSymbolMapSanity(int optimizeLevel) throws IOException,
UnableToCompleteException, Exception {
String benchmark = "hello";
String module = "com.google.gwt.sample.hello.Hello";
File work = Utility.makeTemporaryDirectory(null, benchmark + "work");
try {
CompilerOptionsImpl options = new CompilerOptionsImpl();
options.addModuleName(module);
options.setWarDir(new File(work, "war"));
options.setExtraDir(new File(work, "extra"));
options.setOptimizationLevel(optimizeLevel);
PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
logger.setMaxDetail(TreeLogger.ERROR);
new com.google.gwt.dev.Compiler(options).run(logger);
// Change parentDir for cached/pre-built reports
String parentDir = options.getExtraDir() + "/" + benchmark;
for (Map<String, SimpleSymbolData> symbolDataByJsniIdentifier :
loadSymbolMaps(new File(parentDir + "/symbolMaps/"))) {
assertTrue(!symbolDataByJsniIdentifier.isEmpty());
assertNotNull(symbolDataByJsniIdentifier.get(JSE_METHOD));
assertTrue(symbolDataByJsniIdentifier.get(JSE_METHOD).isMethod());
assertFalse(symbolDataByJsniIdentifier.get(JSE_METHOD).isField());
assertFalse(symbolDataByJsniIdentifier.get(JSE_METHOD).isClass());
assertNotNull(symbolDataByJsniIdentifier.get(JSE_FIELD));
assertTrue(symbolDataByJsniIdentifier.get(JSE_FIELD).isField());
assertFalse(symbolDataByJsniIdentifier.get(JSE_FIELD).isMethod());
assertFalse(symbolDataByJsniIdentifier.get(JSE_FIELD).isClass());
assertNotNull(symbolDataByJsniIdentifier.get(JSE_CLASS));
assertTrue(symbolDataByJsniIdentifier.get(JSE_CLASS).isClass());
assertFalse(symbolDataByJsniIdentifier.get(JSE_CLASS).isField());
assertFalse(symbolDataByJsniIdentifier.get(JSE_CLASS).isMethod());
if (optimizeLevel == OptionOptimize.OPTIMIZE_LEVEL_DRAFT) {
assertNotNull(symbolDataByJsniIdentifier.get(UNINSTANTIABLE_CLASS));
} else {
assertNull(symbolDataByJsniIdentifier.get(UNINSTANTIABLE_CLASS));
}
assertSymbolUniquenessForMethods(symbolDataByJsniIdentifier);
}
} finally {
Util.recursiveDelete(work, false);
}
}
private void assertSymbolUniquenessForMethods(
Map<String, SimpleSymbolData> symbolDataByJsniIdentifier) {
Multimap<String, SymbolData> methodSymbolDataBySymbol = HashMultimap.create();
for (SymbolData symbolData : symbolDataByJsniIdentifier.values()) {
if (symbolData.isMethod()) {
methodSymbolDataBySymbol.put(symbolData.getSymbolName(), symbolData);
}
}
Iterator<String> iterator = methodSymbolDataBySymbol.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
if (methodSymbolDataBySymbol.get(key).size() <= 1) {
iterator.remove();
}
}
assertTrue("The following method symbols where not unique " + methodSymbolDataBySymbol,
methodSymbolDataBySymbol.isEmpty());
}
public void testSymbolMapSanityDraft() throws Exception {
assertSymbolMapSanity(OptionOptimize.OPTIMIZE_LEVEL_DRAFT);
}
public void testSymbolMapSanityOptimized() throws Exception {
assertSymbolMapSanity(OptionOptimize.OPTIMIZE_LEVEL_MAX);
}
}