blob: 92247b15651447633a69246c4e9538f7831e6ebd [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.google.dart.compiler;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.dart.compiler.CommandLineOptions.CompilerOptions;
import com.google.dart.compiler.ast.ASTVisitor;
import com.google.dart.compiler.ast.DartExpression;
import com.google.dart.compiler.ast.DartFunctionTypeAlias;
import com.google.dart.compiler.ast.DartIdentifier;
import com.google.dart.compiler.ast.DartNode;
import com.google.dart.compiler.ast.DartUnit;
import com.google.dart.compiler.ast.LibraryUnit;
import com.google.dart.compiler.common.ErrorExpectation;
import com.google.dart.compiler.common.SourceInfo;
import com.google.dart.compiler.end2end.inc.MemoryLibrarySource;
import com.google.dart.compiler.parser.DartParser;
import com.google.dart.compiler.parser.DartParserRunner;
import com.google.dart.compiler.resolver.Element;
import com.google.dart.compiler.type.Type;
import com.google.dart.compiler.type.TypeQuality;
import static com.google.dart.compiler.common.ErrorExpectation.assertErrors;
import junit.framework.TestCase;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* Base class for compiler tests, with helpful utility methods.
*/
public abstract class CompilerTestCase extends TestCase {
private static final String UTF8 = "UTF-8";
protected CompilerConfiguration compilerConfiguration;
protected String testSource;
protected DartUnit testUnit;
/**
* Instance of {@link CompilerConfiguration} for incremental check-only compilation.
*/
protected static final CompilerConfiguration CHECK_ONLY_CONFIGURATION =
new DefaultCompilerConfiguration(new CompilerOptions()) {
@Override
public boolean incremental() {
return true;
}
@Override
public boolean resolveDespiteParseErrors() {
return true;
}
};
/**
* Read a resource from the given URL.
*/
protected static String readUrl(URL url) {
try {
StringBuffer out = new StringBuffer();
Reader in = new InputStreamReader(url.openStream(), UTF8);
char[] buf = new char[10240];
int n;
while ((n = in.read(buf)) > 0) {
out.append(buf, 0, n);
}
in.close();
return out.toString();
} catch (IOException e) {
// Just punt a RuntimeException out the top if something goes wrong.
// It will simply cause the test to fail, which is exactly what we want.
throw new RuntimeException(e);
}
}
/**
* Return a URL that can be used to read an input file for the given test name
* and path.
*/
protected static URL inputUrlFor(Class<?> testClass, String testName) {
String fullPath = testClass.getPackage().getName().replace('.', '/') + "/"
+ testName;
URL url = chooseClassLoader().getResource(fullPath);
if (url == null) {
fail("Could not find input file: " + fullPath);
}
return url;
}
private static ClassLoader chooseClassLoader() {
if (Thread.currentThread().getContextClassLoader() != null) {
return Thread.currentThread().getContextClassLoader();
}
return CompilerTestCase.class.getClassLoader();
}
/**
* Collects the results of running analyzeLibrary.
*/
protected static class AnalyzeLibraryResult extends DartCompilerListener.Empty {
public String source;
private final List<DartCompilationError> errors = Lists.newArrayList();
private final List<DartCompilationError> compilationErrors = Lists.newArrayList();
private final List<DartCompilationError> compilationWarnings = Lists.newArrayList();
private final List<DartCompilationError> typeErrors = Lists.newArrayList();
private LibraryUnit result;
@Override
public void onError(DartCompilationError event) {
errors.add(event);
if (event.getErrorCode().getSubSystem() == SubSystem.STATIC_TYPE) {
typeErrors.add(event);
} else if (event.getErrorCode().getErrorSeverity() == ErrorSeverity.ERROR) {
compilationErrors.add(event);
} else if (event.getErrorCode().getErrorSeverity() == ErrorSeverity.WARNING) {
compilationWarnings.add(event);
}
}
public List<DartCompilationError> getErrors() {
return errors;
}
public List<DartCompilationError> getTypeErrors() {
return typeErrors;
}
public List<DartCompilationError> getCompilationErrors() {
return compilationErrors;
}
public List<DartCompilationError> getCompilationWarnings() {
return compilationWarnings;
}
/**
* @param lib
*/
public void setLibraryUnitResult(LibraryUnit lib) {
result = lib;
}
/**
* @return the analyzed library
*/
public LibraryUnit getLibraryUnitResult() {
return result;
}
}
/**
* Build a multi-line string from a list of strings.
*
* @param lines
* @return a single string containing {@code lines}, each terminated by \n
*/
protected static String makeCode(String... lines) {
StringBuilder buf = new StringBuilder();
for (String line : lines) {
buf.append(line).append('\n');
}
return buf.toString();
}
@Override
protected void setUp() throws Exception {
super.setUp();
compilerConfiguration = CHECK_ONLY_CONFIGURATION;
}
@Override
protected void tearDown() throws Exception {
compilerConfiguration = null;
testSource = null;
testUnit = null;
super.tearDown();
}
protected AnalyzeLibraryResult analyzeLibrary(String... lines) throws Exception {
testSource = makeCode(lines);
AnalyzeLibraryResult libraryResult = analyzeLibrary(testSource);
testUnit = libraryResult.getLibraryUnitResult().getUnits().iterator().next();
return libraryResult;
}
/**
* Simulate running {@code analyzeLibrary} the way the IDE will.
* <p>
* <b>Note:</b> if the IDE changes how it calls analyzeLibrary, this should
* be changed to match.
* @param code the Dart code to parse/analyze
*
* @return an {@link AnalyzeLibraryResult} containing the {@link LibraryUnit}
* and all the errors/warnings generated from the supplied code
* @throws Exception
*/
protected AnalyzeLibraryResult analyzeLibrary(String code)
throws Exception {
AnalyzeLibraryResult result = new AnalyzeLibraryResult();
result.source = code;
// Prepare library.
MemoryLibrarySource lib = new MemoryLibrarySource("Test.dart");
lib.setContent("Test.dart", code);
// Prepare unit.
Map<URI, DartUnit> testUnits = Maps.newHashMap();
{
DartSource src = lib.getSourceFor("Test.dart");
DartUnit unit = makeParser(src, code, result).parseUnit();
testUnits.put(src.getUri(), unit);
}
DartArtifactProvider provider = new MockArtifactProvider();
result.setLibraryUnitResult(DartCompiler.analyzeLibrary(
lib,
testUnits,
compilerConfiguration,
provider,
result));
// TODO(zundel): One day, we want all AST nodes that are identifiers to point to
// elements if they are resolved. Uncommenting this line helps track missing elements
// down.
// ResolverAuditVisitor.exec(unit);
return result;
}
/**
* Compiles a single unit with a synthesized application.
*/
protected DartSource compileSingleUnit(String name, String code,
DartArtifactProvider provider) throws IOException {
MockLibrarySource lib = new MockLibrarySource();
DartSourceTest src = new DartSourceTest(name, code, lib);
lib.addSource(src);
CompilerConfiguration config = getCompilerConfiguration();
DartCompilerListener listener = new DartCompilerListenerTest(src.getName());
DartCompiler.compileLib(lib, config, provider, listener);
return src;
}
/**
* Allow tests to override the configuration used.
*/
protected CompilerConfiguration getCompilerConfiguration() {
return new DefaultCompilerConfiguration();
}
/**
* Parse a single compilation unit for the given input file.
*/
protected final DartUnit parseUnit(String path) {
// final because we delegate to the method below, and only that one should
// be overriden to do extra checks.
URL url = inputUrlFor(getClass(), path);
String source = readUrl(url);
return parseUnit(path, source);
}
/**
* Parse a single compilation unit for the name and source.
*/
protected DartUnit parseUnit(String srcName, String sourceCode, Object... errors) {
testSource = sourceCode;
// TODO(jgw): We'll need to fill in the library parameter when testing multiple units.
DartSourceTest src = new DartSourceTest(srcName, sourceCode, null);
DartCompilerListenerTest listener = new DartCompilerListenerTest(srcName, errors);
testUnit = makeParser(src, sourceCode, listener).parseUnit();
listener.checkAllErrorsReported();
return testUnit;
}
/**
* Parse a single compilation unit for the name and source. The parse expects some kind of error,
* but isn't picky about the actual contents. This is useful for testing parser recovery where we
* don't want to make the test too brittle.
*/
protected DartUnit parseUnitUnspecifiedErrors(String srcName, String sourceCode) {
DartSourceTest src = new DartSourceTest(srcName, sourceCode, null);
final List<DartCompilationError> errorsEncountered = Lists.newArrayList();
DartCompilerListener listener = new DartCompilerListener.Empty() {
@Override
public void onError(DartCompilationError event) {
errorsEncountered.add(event);
}
};
DartUnit unit = makeParser(src, sourceCode, listener).parseUnit();
assertTrue("Expected some compilation errors, got none.", errorsEncountered.size() > 0);
return unit;
}
protected DartUnit parseUnitAsSystemLibrary(final String srcName, String sourceCode,
Object... errors) {
DartSourceTest src = new DartSourceTest(srcName, sourceCode, null) {
@Override
public URI getUri() {
return URI.create("dart:core/" + srcName);
}
};
DartCompilerListenerTest listener = new DartCompilerListenerTest(srcName, errors);
DartUnit unit = makeParser(src, sourceCode, listener).parseUnit();
listener.checkAllErrorsReported();
return unit;
}
/**
* Parse a single compilation unit with given name and source, and check for a set of expected errors.
*
* @param errors a sequence of errors represented as triples of the form
* (String msg, int line, int column) or
* (ErrorCode code, int line, int column)
*/
protected DartUnit parseSourceUnitErrors(String sourceCode, Object... errors) {
String srcName = "Test.dart";
DartSourceTest src = new DartSourceTest(srcName, sourceCode, null);
DartCompilerListenerTest listener = new DartCompilerListenerTest(srcName, errors);
DartUnit unit = makeParser(src, sourceCode, listener).parseUnit();
listener.checkAllErrorsReported();
return unit;
}
/**
* Parse a single compilation unit for the given input file, and check for a
* set of expected errors.
*
* @param errors a sequence of errors represented as triples of the form
* (String msg, int line, int column) or
* (ErrorCode code, int line, int column)
*/
protected DartUnit parseUnitErrors(final String path, final Object... errors) {
URL url = inputUrlFor(getClass(), path);
String sourceCode = readUrl(url);
// TODO(jgw): We'll need to fill in the library parameter when testing multiple units.
DartSourceTest src = new DartSourceTest(path, sourceCode, null);
DartCompilerListenerTest listener = new DartCompilerListenerTest(path, errors);
DartUnit unit = makeParser(src, sourceCode, listener).parseUnit();
listener.checkAllErrorsReported();
return unit;
}
/**
* Override this method to provide an alternate {@link DartParser}.
*/
protected DartParser makeParser(Source src, String sourceCode,
DartCompilerListener listener) {
return new DartParser(src, sourceCode, false, Sets.<String>newHashSet(), listener, null);
}
/**
* @return the {@link DartParserRunner} with parsed source. It can be used to request
* {@link DartUnit} or compilation problems.
*/
protected final DartParserRunner parseSource(String code) {
return DartParserRunner.parse(getName(), code, Integer.MAX_VALUE, false);
}
/**
* Parses given source and checks parsing problems.
*/
protected final DartParserRunner parseExpectErrors(String code,
ErrorExpectation... expectedErrors) {
DartParserRunner parserRunner = parseSource(code);
List<DartCompilationError> errors = parserRunner.getErrors();
assertErrors(errors, expectedErrors);
return parserRunner;
}
/**
* Parses given source and checks parsing problems.
*/
protected final void parseExpectWarnings(String code, ErrorExpectation... expectedWarnings) {
DartParserRunner runner = DartParserRunner.parse(getName(), code, Integer.MAX_VALUE, true);
List<DartCompilationError> errors = runner.getErrors();
assertErrors(errors, expectedWarnings);
}
/**
* Find node in {@link #testUnit} parsed form {@link #testSource}.
*/
public <T extends DartNode> T findNode(final Class<T> clazz, String pattern) {
final int index = testSource.indexOf(pattern);
assertTrue(index != -1);
final AtomicReference<T> result = new AtomicReference<T>();
testUnit.accept(new ASTVisitor<Void>() {
@Override
@SuppressWarnings("unchecked")
public Void visitNode(DartNode node) {
SourceInfo sourceInfo = node.getSourceInfo();
if (sourceInfo.getOffset() <= index
&& index < sourceInfo.getEnd()
&& clazz.isInstance(node)) {
result.set((T) node);
}
return super.visitNode(node);
}
});
return result.get();
}
/**
* @return the {@link DartExpression} with given {@link DartNode#toSource()}. This is inaccurate approach, but good
* enough for specific tests.
*/
@SuppressWarnings("unchecked")
protected static <T extends DartNode> T findNodeBySource(DartNode rootNode, final String sampleSource) {
final DartNode result[] = new DartNode[1];
rootNode.accept(new ASTVisitor<Void>() {
@Override
public Void visitNode(DartNode node) {
if (node.toSource().equals(sampleSource)) {
result[0] = node;
}
return super.visitNode(node);
}
});
return (T) result[0];
}
protected static DartFunctionTypeAlias findTypedef(DartNode rootNode, final String name) {
final DartFunctionTypeAlias result[] = new DartFunctionTypeAlias[1];
rootNode.accept(new ASTVisitor<Void>() {
@Override
public Void visitFunctionTypeAlias(DartFunctionTypeAlias node) {
if (node.getName().getName().equals(name)) {
result[0] = node;
}
return null;
}
});
return result[0];
}
public static String getNodeSource(String code, DartNode node) {
SourceInfo sourceInfo = node.getSourceInfo();
return code.substring(sourceInfo.getOffset(), sourceInfo.getEnd());
}
/**
* Asserts that parsed {@link DartUnit} has {@link DartExpression} with given source and its
* {@link SourceInfo} points to the given source.
*/
protected void assertNodeSourceInfo(String nodeSource) {
DartNode node = findNode(DartNode.class, nodeSource);
assertNotNull(node);
SourceInfo sourceInfo = node.getSourceInfo();
String actualSource = testSource.substring(sourceInfo.getOffset(), sourceInfo.getEnd());
assertEquals(nodeSource, actualSource);
}
/**
* Asserts that {@link Element} with given name has expected type.
*/
protected static void assertInferredElementTypeString(
DartUnit unit,
String variableName,
String expectedType,
TypeQuality quality) {
// find element
Element element = getNamedElement(unit, variableName);
assertNotNull(element);
// check type
Type actualType = element.getType();
assertInferredElementTypeString(actualType, element.getName(), expectedType, quality);
}
protected static void assertInferredElementTypeString(
Type actualType,
String testName,
String expectedType,
TypeQuality quality) {
assertEquals(testName, expectedType, getTypeSource(actualType));
if (quality != null) {
assertSame(quality, actualType.getQuality());
}
}
/**
* @return the source-like {@link String} for the given {@link Type}.
*/
protected static String getTypeSource(Type actualType) {
return actualType.toString();
}
/**
* @return the {@link Element} with given name, may be <code>null</code>.
*/
private static Element getNamedElement(DartUnit unit, final String name) {
final Element[] result = {null};
unit.accept(new ASTVisitor<Void>() {
@Override
public Void visitIdentifier(DartIdentifier node) {
Element element = node.getElement();
if (element != null && element.getName().equals(name)) {
result[0] = element;
}
return super.visitIdentifier(node);
}
});
return result[0];
}
}