| // 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 static com.google.dart.compiler.common.ErrorExpectation.assertErrors; |
| |
| 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.TypeKind; |
| |
| 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) { |
| // find element |
| Element element = getNamedElement(unit, variableName); |
| assertNotNull(element); |
| // check type |
| Type actualType = element.getType(); |
| assertEquals(element.getName(), expectedType, getTypeSource(actualType)); |
| // should be inferred |
| if (TypeKind.of(actualType) != TypeKind.DYNAMIC) { |
| assertTrue("Should be marked as inferred", actualType.isInferred()); |
| } |
| } |
| |
| /** |
| * @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]; |
| } |
| } |