| /******************************************************************************* |
| * Copyright (c) 2005, 2016 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| *******************************************************************************/ |
| 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; |
| } |
| } |