blob: c5836770b0d011f121f4ee2629d516d3f3704066 [file] [log] [blame]
/*
* 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);
}
}