/* * Copyright (c) 2013, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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.dart.engine.internal.index; import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; import com.google.common.collect.Sets; import com.google.dart.engine.AnalysisEngine; import com.google.dart.engine.ast.AstNode; import com.google.dart.engine.ast.CompilationUnit; import com.google.dart.engine.ast.SimpleIdentifier; import com.google.dart.engine.ast.visitor.GeneralizingAstVisitor; import com.google.dart.engine.context.AnalysisContext; import com.google.dart.engine.context.ChangeSet; import com.google.dart.engine.element.CompilationUnitElement; import com.google.dart.engine.element.Element; import com.google.dart.engine.element.LibraryElement; import com.google.dart.engine.internal.context.AnalysisOptionsImpl; import com.google.dart.engine.sdk.DartSdk; import com.google.dart.engine.sdk.DirectoryBasedDartSdk; import com.google.dart.engine.source.DartUriResolver; import com.google.dart.engine.source.FileBasedSource; import com.google.dart.engine.source.FileUriResolver; import com.google.dart.engine.source.Source; import com.google.dart.engine.source.SourceFactory; import com.google.dart.engine.utilities.io.FileUtilities2; import com.google.dart.engine.utilities.source.SourceRange; import com.google.dart.engine.utilities.source.SourceRangeFactory; import static com.google.dart.engine.utilities.io.FileUtilities2.createFile; import junit.framework.TestCase; import org.apache.commons.lang3.StringUtils; import static org.fest.assertions.Assertions.assertThat; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; public class AbstractDartTest extends TestCase { protected final static String EOL = System.getProperty("line.separator", "\n"); protected final static String EOL2 = EOL + EOL; protected static final DartSdk defaultSdk = DirectoryBasedDartSdk.getDefaultSdk(); protected static final SourceFactory sourceFactory = new SourceFactory(new DartUriResolver( defaultSdk), new FileUriResolver()); protected static AnalysisContext analysisContext; /** * @return {@link AstNode} which has required offset and type. */ public static <E extends AstNode> E findNode(AstNode root, final int offset, final Class<E> clazz) { final AtomicReference<E> resultRef = new AtomicReference<E>(); root.accept(new GeneralizingAstVisitor<Void>() { @Override @SuppressWarnings("unchecked") public Void visitNode(AstNode node) { if (node.getOffset() <= offset && offset < node.getEnd() && clazz.isInstance(node)) { resultRef.set((E) node); } return super.visitNode(node); } }); E result = resultRef.get(); assertNotNull(result); return result; } /** * Function to force formatter to put every string on separate line. */ public static String[] formatLines(String... lines) { return lines; } /** * @return the {@link String} content of the given {@link Source}. */ public static String getSourceContent(Source source) throws Exception { return analysisContext.getContents(source).getData().toString(); } /** * @return the resolved {@link CompilationUnit} for given source. */ public static CompilationUnit parseUnit(Source source) throws Exception { // parse and resolve LibraryElement library = analysisContext.computeLibraryElement(source); CompilationUnit libraryUnit = analysisContext.resolveCompilationUnit(source, library); return libraryUnit; } /** * @return the resolved {@link CompilationUnit} for given Dart code. */ public static CompilationUnit parseUnit(String path, String code) throws Exception { ensureAnalysisContext(); // configure Source Source source = new FileBasedSource(FileUtilities2.createFile(path)); ChangeSet changeSet = new ChangeSet(); changeSet.addedSource(source); analysisContext.applyChanges(changeSet); analysisContext.setContents(source, code); // parse and resolve LibraryElement library = analysisContext.computeLibraryElement(source); CompilationUnit libraryUnit = analysisContext.resolveCompilationUnit(source, library); return libraryUnit; } protected static void disableContextHints() { ensureAnalysisContext(); AnalysisOptionsImpl options = new AnalysisOptionsImpl(analysisContext.getAnalysisOptions()); options.setHint(false); analysisContext.setAnalysisOptions(options); } protected static void enableContextHints() { ensureAnalysisContext(); AnalysisOptionsImpl options = new AnalysisOptionsImpl(analysisContext.getAnalysisOptions()); options.setHint(true); analysisContext.setAnalysisOptions(options); } /** * Ensure that {@link #analysisContext} is initialized. */ protected static void ensureAnalysisContext() { if (analysisContext == null) { analysisContext = AnalysisEngine.getInstance().createAnalysisContext(); analysisContext.setSourceFactory(sourceFactory); AnalysisOptionsImpl analysisOptionsImpl = new AnalysisOptionsImpl(); analysisOptionsImpl.setEnableAsync(true); analysisOptionsImpl.setEnableDeferredLoading(true); analysisOptionsImpl.setEnableEnum(true); analysisOptionsImpl.setHint(false); analysisContext.setAnalysisOptions(analysisOptionsImpl); } } /** * @return the offset of given <code>search</code> string in the given code. Fails test if not * found. */ protected static int findOffset(String code, String search) { int offset = code.indexOf(search); assertThat(offset).describedAs(code).isNotEqualTo(-1); return offset; } /** * @return the {@link SourceRange} for given start/end search strings. Fails test if not found. */ protected static SourceRange findRangeIdentifier(String code, String search) { int start = findOffset(code, search); int end = CharMatcher.JAVA_LETTER_OR_DIGIT.negate().indexIn(code, start); return SourceRangeFactory.rangeStartEnd(start, end); } protected static String makeSource(String... lines) { return Joiner.on(EOL).join(lines); } /** * Prints given multi-line source in the way ready to paste back into Java test source. */ protected static void printSourceLines(String source) { String[] lines = StringUtils.splitByWholeSeparatorPreserveAllTokens(source, EOL); for (int i = 0; i < lines.length; i++) { String line = lines[i]; line = StringUtils.replace(line, "\"", "\\\""); System.out.print("\""); System.out.print(line); if (i != lines.length - 1) { System.out.println("\","); } else { System.out.println("\""); } } } /** * @return {@link String} with system line separator converted to Unix <code>\n</code>. */ protected static String toUnixEol(String s) { return s.replace(EOL, "\n"); } private final Set<Source> sourceWithSetContent = Sets.newHashSet(); protected boolean verifyNoTestUnitErrors = true; protected String testCode; protected Source testSource; protected CompilationUnit testUnit; protected CompilationUnitElement testUnitElement; protected LibraryElement testLibraryElement; /** * Add a source file to the content provider. * * @param contents the contents to be returned by the content provider for the specified file * @return the source object representing the added file */ protected Source addSource(String contents) { return addSource("/test.dart", contents); } /** * Add a source file to the content provider. The file path should be absolute. * * @param filePath the path of the file being added * @param contents the contents to be returned by the content provider for the specified file * @return the source object representing the added file */ protected Source addSource(String filePath, String contents) { ensureAnalysisContext(); Source source = new FileBasedSource(createFile(filePath)); // add Source to the context ChangeSet changeSet = new ChangeSet(); changeSet.addedSource(source); analysisContext.applyChanges(changeSet); analysisContext.setContents(source, contents); // remember Source to remove from the context later sourceWithSetContent.add(source); // done return source; } /** * @return the {@link Element} if there is {@link SimpleIdentifier} at position of "search", not * {@code null} or fails. */ @SuppressWarnings("unchecked") protected final <T extends Element> T findElement(String search) { Element element = findSimpleIdentifier(search).getBestElement(); assertNotNull(element); return (T) element; } /** * @return the offset directly after given <code>search</code> string in {@link testUnit}. Fails * test if not found. */ protected final int findEnd(String search) { return findOffset(search) + search.length(); } /** * @return the {@link SimpleIdentifier} at the given search pattern. */ protected final SimpleIdentifier findIdentifier(String search) { return findNode(search, SimpleIdentifier.class); } /** * @return the {@link Element} of the {@link SimpleIdentifier} at the given search pattern. */ @SuppressWarnings("unchecked") protected final <T extends Element> T findIdentifierElement(String search) { return (T) findIdentifier(search).getBestElement(); } /** * @return {@link AstNode} form {@link #testUnit} which has required offset and type. */ protected final <E extends AstNode> E findNode(int offset, Class<E> clazz) { return findNode(testUnit, offset, clazz); } /** * @return {@link AstNode} from {@link #testUnit} which starts at given text has has given type. */ protected final <E extends AstNode> E findNode(String search, Class<E> clazz) { int offset = findOffset(search); return findNode(testUnit, offset, clazz); } /** * @return the offset of given <code>search</code> string in {@link testUnit}. Fails test if not * found. */ protected final int findOffset(String search) { int offset = testCode.indexOf(search); assertThat(offset).describedAs(testCode).isNotEqualTo(-1); return offset; } /** * @return the {@link SourceRange} for given sub-string. Fails test if not found. */ protected final SourceRange findRange(String search) { int start = findOffset(search); return SourceRangeFactory.rangeStartLength(start, search.length()); } /** * @return the {@link SourceRange} for given start/end search strings. Fails test if not found. */ protected final SourceRange findRangeIdentifier(String search) { int start = findOffset(search); int end = CharMatcher.JAVA_LETTER_OR_DIGIT.negate().indexIn(testCode, start); return SourceRangeFactory.rangeStartEnd(start, end); } /** * @return the {@link SourceRange} for given start/end search strings. Fails test if not found. */ protected final SourceRange findRangeStartEnd(String searchStart, String searchEnd) { return SourceRangeFactory.rangeStartEnd(findOffset(searchStart), findOffset(searchEnd)); } /** * @return the first {@link SimpleIdentifier} which starts at position of given string. */ protected final SimpleIdentifier findSimpleIdentifier(String pattern) { return findNode(pattern, SimpleIdentifier.class); } protected AnalysisContext getAnalysisContext() { return analysisContext; } /** * Sets {@link #testUnit} with mocked {@link Source} which has given code. */ protected final void parseTestUnit(Source source) throws Exception { testUnit = parseUnit(source); initTestFields(testUnit); } protected final void parseTestUnit(Source libSource, Source unitSource) throws Exception { CompilationUnit resolvedUnit = analysisContext.resolveCompilationUnit(unitSource, libSource); initTestFields(resolvedUnit); } /** * Sets {@link #testUnit} with mocked {@link Source} which has given code. */ protected final void parseTestUnit(String... lines) throws Exception { String code = makeSource(lines); testUnit = parseUnit("/Test.dart", code); initTestFields(testUnit); } /** * Sets {@link #testUnit} with mocked {@link Source} which has given code. */ protected final void parseTestUnits(Source... sources) throws Exception { Source librarySource = sources[0]; testSource = sources[1]; testCode = analysisContext.getContents(testSource).getData().toString(); // fill AnalysisContext { ChangeSet changeSet = new ChangeSet(); for (Source source : sources) { changeSet.addedSource(source); } analysisContext.applyChanges(changeSet); } // testLibraryElement = analysisContext.computeLibraryElement(librarySource); testUnit = analysisContext.resolveCompilationUnit(testSource, testLibraryElement); testUnitElement = testUnit.getElement(); if (verifyNoTestUnitErrors) { assertThat(analysisContext.getErrors(testUnitElement.getSource()).getErrors()).describedAs( testCode).isEmpty(); } } /** * Configures {@link SourceFactory} to use given content for file at given path. * * @return the {@link Source} which corresponds given path. */ protected final Source setFileContent(String path, String content) { ensureAnalysisContext(); FileBasedSource source = new FileBasedSource(createFile("/" + path)); sourceWithSetContent.add(source); analysisContext.setContents(source, content); return source; } @Override protected void tearDown() throws Exception { // reset SourceFactory for (Source source : sourceWithSetContent) { analysisContext.setContents(source, null); } // reset AnalysisContext if (analysisContext != null) { ChangeSet changeSet = new ChangeSet(); if (testSource != null) { changeSet.removedSource(testSource); } for (Source source : sourceWithSetContent) { changeSet.removedSource(source); } analysisContext.applyChanges(changeSet); } // clear fields testCode = null; testSource = null; testUnit = null; testUnitElement = null; testLibraryElement = null; // continue super.tearDown(); } private void initTestFields(CompilationUnit resolvedUnit) throws Exception { testUnit = resolvedUnit; testUnitElement = testUnit.getElement(); testLibraryElement = testUnitElement.getEnclosingElement(); if (verifyNoTestUnitErrors) { assertThat(analysisContext.getErrors(testUnitElement.getSource()).getErrors()).describedAs( testCode).isEmpty(); } testSource = testUnitElement.getSource(); testCode = getSourceContent(testSource); } }