blob: 35edee08ab13f2c96985dfbe38927e49f69f333c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2016 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.ruby.core.tests.typeinference;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.ast.ASTNode;
import org.eclipse.dltk.ast.ASTVisitor;
import org.eclipse.dltk.ast.declarations.ModuleDeclaration;
import org.eclipse.dltk.ast.references.VariableReference;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.evaluation.types.AmbiguousType;
import org.eclipse.dltk.evaluation.types.SimpleType;
import org.eclipse.dltk.evaluation.types.UnknownType;
import org.eclipse.dltk.ruby.core.tests.Activator;
import org.eclipse.dltk.ruby.typeinference.OffsetTargetedASTVisitor;
import org.eclipse.dltk.ruby.typeinference.RubyClassType;
import org.eclipse.dltk.ti.BasicContext;
import org.eclipse.dltk.ti.DLTKTypeInferenceEngine;
import org.eclipse.dltk.ti.ITypeInferencer;
import org.eclipse.dltk.ti.goals.ExpressionTypeGoal;
import org.eclipse.dltk.ti.types.IEvaluatedType;
import org.eclipse.dltk.ti.types.RecursionTypeCall;
import org.osgi.framework.Bundle;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import junit.framework.TestResult;
import junit.framework.TestSuite;
public class TypeInferenceSuite extends TestSuite {
@Override
public void run(TestResult result) {
TypeInferenceTest tests = new TypeInferenceTest("ruby selection tests");
try {
tests.setUpSuite();
} catch (Throwable t) {
result.addError(this, t);
return;
}
try {
super.run(result);
} finally {
try {
tests.tearDownSuite();
} catch (Throwable t) {
t.printStackTrace();
}
}
}
public TypeInferenceSuite(Class<?> clazz, String testsDirectory) {
super(clazz.getName());
final Bundle bundle = Activator.getDefault().getBundle();
Enumeration<String> entryPaths = bundle.getEntryPaths(testsDirectory);
while (entryPaths.hasMoreElements()) {
final String path = entryPaths.nextElement();
URL entry = bundle.getEntry(path);
try {
entry.openStream().close();
} catch (Exception e) {
continue;
}
int pos = path.lastIndexOf('/');
final String name = (pos >= 0 ? path.substring(pos + 1) : path);
String x = path.substring(0, pos);
pos = x.lastIndexOf('/');
final String folder = (pos >= 0 ? x.substring(pos + 1) : x);
addTest(new TestCase(name) {
private Collection<IAssertion> assertions = new ArrayList<>();
@Override
public void setUp() {
}
@Override
protected void runTest() throws Throwable {
String content = loadContent(path);
String[] lines = content.split("\n");
int lineOffset = 0;
for (int i = 0; i < lines.length; i++) {
String line = lines[i].trim();
int pos = line.indexOf("##");
if (pos >= 0) {
StringTokenizer tok = new StringTokenizer(line
.substring(pos + 2));
String test = tok.nextToken();
if ("exit".equals(test)) {
return;
} else if ("localvar".equals(test)) {
String varName = tok.nextToken();
int namePos = lines[i].indexOf(varName);
Assert.isLegal(namePos >= 0);
namePos += lineOffset;
String arrow = tok.nextToken();
Assert.isLegal(arrow.equals("=>"));
String correctClassRef = tok.nextToken();
assertions.add(new VariableReturnTypeAssertion(
varName, namePos, correctClassRef));
} else if ("expr".equals(test)) {
String expr = tok.nextToken();
int namePos = lines[i].indexOf(expr);
Assert.isLegal(namePos >= 0);
namePos += lineOffset;
String arrow = tok.nextToken();
Assert.isLegal(arrow.equals("=>"));
String correctClassRef = tok.nextToken();
assertions.add(new ExpressionTypeAssertion(
expr, namePos, correctClassRef));
} else {
// Assert.isLegal(false);
}
}
lineOffset += lines[i].length() + 1;
}
if (assertions.size() == 0)
return;
ITypeInferencer inferencer = new DLTKTypeInferenceEngine();
TypeInferenceTest tests = new TypeInferenceTest(
"ruby selection tests");
tests.executeTest(folder, name, inferencer, assertions);
}
class VariableReturnTypeAssertion implements IAssertion {
private final String correctClassRef;
// private final String varName;
private final int namePos;
public VariableReturnTypeAssertion(String varName,
int namePos, String correctClassRef) {
// this.varName = varName;
this.namePos = namePos;
this.correctClassRef = correctClassRef;
}
@Override
public void check(ModuleDeclaration rootNode,
ISourceModule cu, ITypeInferencer inferencer)
throws Exception {
final ASTNode[] result = new ASTNode[1];
ASTVisitor visitor = new OffsetTargetedASTVisitor(
namePos) {
@Override
protected boolean visitGeneralInteresting(ASTNode s) {
if (s instanceof VariableReference)
if (s.sourceStart() == namePos
&& result[0] == null) {
result[0] = s;
}
return true;
}
};
rootNode.traverse(visitor);
Assert.isLegal(result[0] != null);
ExpressionTypeGoal goal = new ExpressionTypeGoal(
new BasicContext(cu, rootNode), result[0]);
IEvaluatedType type = inferencer.evaluateType(goal, -1);
assertNotNull(type);
assertEquals(correctClassRef, ((RubyClassType) type)
.getModelKey());
}
}
class ExpressionTypeAssertion implements IAssertion {
private final String correctClassRef;
// private final String expression;
private final int namePos;
public ExpressionTypeAssertion(String expression,
int namePos, String correctClassRef) {
// this.expression = expression;
this.namePos = namePos;
this.correctClassRef = correctClassRef;
}
@Override
public void check(ModuleDeclaration rootNode,
ISourceModule cu, ITypeInferencer inferencer)
throws Exception {
final ASTNode[] result = new ASTNode[1];
ASTVisitor visitor = new OffsetTargetedASTVisitor(
namePos) {
@Override
protected boolean visitGeneralInteresting(ASTNode s) {
if (s != null && result[0] == null)
if (s.sourceStart() == namePos) {
result[0] = s;
}
return true;
}
};
rootNode.traverse(visitor);
if (result[0] == null)
System.out
.println("ExpressionTypeAssertion.check()");
Assert.isLegal(result[0] != null);
ExpressionTypeGoal goal = new ExpressionTypeGoal(
new BasicContext(cu, rootNode), result[0]);
IEvaluatedType type = inferencer.evaluateType(goal, -1);
if (!correctClassRef.equals("recursion")) {
if (type == null)
throw new AssertionFailedError(
"null type fetched, but "
+ correctClassRef + " expected");
assertNotNull(type);
if (type instanceof SimpleType) {
IEvaluatedType intrinsicType = getIntrinsicType(correctClassRef);
assertEquals(intrinsicType, type);
} else if (type instanceof RubyClassType) {
RubyClassType rubyType = (RubyClassType) type;
assertEquals(correctClassRef, rubyType
.getModelKey());
} else if (type instanceof AmbiguousType) {
AmbiguousType ambiType = (AmbiguousType) type;
Set<String> modelKeySet = new HashSet<>();
IEvaluatedType[] possibleTypes = ambiType
.getPossibleTypes();
for (int cnt = 0, max = possibleTypes.length; cnt < max; cnt++) {
if (possibleTypes[cnt] instanceof RubyClassType) {
modelKeySet
.add(((RubyClassType) possibleTypes[cnt])
.getModelKey());
}
}
assertTrue(modelKeySet
.contains(correctClassRef));
} else {
fail("Unrecognized IEvaluatedType was inferred: "
+ type.getClass().getName());
}
}
}
}
});
}
}
private String loadContent(String path) throws IOException {
StringBuffer buffer = new StringBuffer();
try (InputStream input = Activator.openResource(path);) {
InputStreamReader reader = new InputStreamReader(input);
BufferedReader br = new BufferedReader(reader);
char[] data = new char[100 * 1024]; // tests shouldnt be more that
// 100 kb
int size = br.read(data);
buffer.append(data, 0, size);
}
String content = buffer.toString();
return content;
}
private static IEvaluatedType getIntrinsicType(String correctClassRef) {
IEvaluatedType correctType;
if ("recursion".equals(correctClassRef))
correctType = RecursionTypeCall.INSTANCE;
else if ("any".equals(correctClassRef))
correctType = UnknownType.INSTANCE;
// else if ("Fixnum".equals(correctClassRef))
// correctType = new SimpleType(SimpleType.TYPE_NUMBER);
// else if ("Str".equals(correctClassRef))
// correctType = new SimpleType(SimpleType.TYPE_STRING);
else
correctType = null;
return correctType;
}
}