/* * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 org.eclipse.jdt.groovy.core.tests.basic; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.jdt.groovy.internal.compiler.ast.GroovyCompilationUnitDeclaration; import org.codehaus.jdt.groovy.internal.compiler.ast.GroovyParser; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.ToolFactory; import org.eclipse.jdt.core.tests.compiler.regression.AbstractRegressionTest; import org.eclipse.jdt.core.tests.util.AbstractCompilerTest; import org.eclipse.jdt.core.tests.util.CompilerTestSetup; import org.eclipse.jdt.core.tests.util.Util; import org.eclipse.jdt.core.util.ClassFileBytesDisassembler; import org.eclipse.jdt.groovy.core.util.ReflectionUtils; import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.ast.Wildcard; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.rules.TestName; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @RunWith(Parameterized.class) public abstract class GroovyCompilerTestSuite { //CompilerOptions.versionFromJdkLevel(_) protected static final long JDK5 = ClassFileConstants.JDK1_5; protected static final long JDK6 = ClassFileConstants.JDK1_6; protected static final long JDK7 = ClassFileConstants.JDK1_7; protected static final long JDK8 = (52 << 16) + ClassFileConstants.MINOR_VERSION_0; protected static final long JDK9 = (53 << 16) + ClassFileConstants.MINOR_VERSION_0; protected static final List<Long> JDKs = Collections.unmodifiableList(Arrays.asList(JDK5, JDK6, JDK7, JDK8, JDK9)); @Parameterized.Parameters public static Iterable<Object[]> params() { long javaSpec = CompilerOptions.versionToJdkLevel(System.getProperty("java.specification.version")); List<Object[]> params = new ArrayList<Object[]>(); for (long jdk : JDKs) { if (jdk <= javaSpec) { params.add(new Object[] {jdk}); } } return params; } private final long compliance; public GroovyCompilerTestSuite(long compliance) { this.compliance = compliance; } @Rule public TestName test = new TestName(); private AbstractRegressionTest testDriver; @Before public final void setUpTestCase() throws Exception { System.out.println("----------------------------------------"); System.out.println("Starting: " + test.getMethodName()); GroovyCompilationUnitDeclaration.defaultCheckGenerics = true; GroovyParser.debugRequestor = new DebugRequestor(); testDriver = new AbstractRegressionTest(test.getMethodName()) { /** * Include the groovy runtime jars on the classpath that is used. * Other classpath issues can be seen in TestVerifier/VerifyTests and only when * the right prefixes are registered in there will it use the classloader with this * classpath rather than the one it conjures up just to load the built code. */ @Override protected String[] getDefaultClassPaths() { String[] cps = Util.concatWithClassLibs(AbstractRegressionTest.OUTPUT_DIR, false); String[] newcps = new String[cps.length + 3]; System.arraycopy(cps, 0, newcps, 0, cps.length); String[] ivyVersions = {"2.4.0", "2.3.0", "2.2.0"}; String[] groovyVersions = {"2.5.0", "2.4.11", "2.3.11", "2.2.2", "2.1.9", "2.0.8", "1.8.9"}; try { URL groovyJar = null; for (String groovyVer : groovyVersions) { groovyJar = Platform.getBundle("org.codehaus.groovy").getEntry("lib/groovy-all-" + groovyVer + ".jar"); if (groovyJar != null) break; } newcps[newcps.length-3] = FileLocator.resolve(groovyJar).getFile(); URL ivyJar = null; for (String ivyVer : ivyVersions) { ivyJar = Platform.getBundle("org.codehaus.groovy").getEntry("lib/ivy-" + ivyVer + ".jar"); if (ivyJar != null) break; } newcps[newcps.length-2] = FileLocator.resolve(ivyJar).getFile(); // FIXASC think more about why this is here... the tests that need it specify the option but that is just for // the groovy class loader to access it. The annotation within this jar needs to be resolvable by the compiler when // building the annotated source - and so I suspect that the groovyclassloaderpath does need merging onto the project // classpath for just this reason, hmm. newcps[newcps.length-1] = FileLocator.resolve(Platform.getBundle("org.eclipse.jdt.groovy.core.tests.compiler").getEntry("astTransformations/transforms.jar")).getFile(); } catch (IOException e) { fail("IOException thrown " + e.getMessage()); } return newcps; } }; testDriver.initialize(new CompilerTestSetup(compliance)); ReflectionUtils.executeNoArgPrivateMethod(AbstractRegressionTest.class, "setUp", testDriver); } @After public void tearDownTestCase() throws Exception { ReflectionUtils.executeNoArgPrivateMethod(AbstractRegressionTest.class, "tearDown", testDriver); GroovyCompilationUnitDeclaration.defaultCheckGenerics = false; GroovyParser.debugRequestor = null; } protected final boolean isAtLeastJava(long level) { final long complianceLevel = (Long) ReflectionUtils.getPrivateField(AbstractCompilerTest.class, "complianceLevel", testDriver); return complianceLevel >= level; } @SuppressWarnings("unchecked") protected final Map<String, String> getCompilerOptions() { return (Map<String, String>) ReflectionUtils.executeNoArgPrivateMethod(AbstractRegressionTest.class, "getCompilerOptions", testDriver); } protected final void runConformTest(String[] sources) { testDriver.runConformTest(sources); } protected final void runConformTest(String[] sources, String expectedStdout) { testDriver.runConformTest(sources, expectedStdout); } protected final void runConformTest(String[] sources, String expectedStdout, String expectedStderr) { testDriver.runConformTest(/*flush*/true, sources, /*ecjlog*/"", expectedStdout, expectedStderr, new AbstractRegressionTest.JavacTestOptions()); } protected final void runConformTest(String[] sources, String expectedOutput, Map<String, String> compilerOptions) { testDriver.runConformTest(sources, expectedOutput, /*classlibs:*/null, /*flush*/true, /*vmargs*/null, compilerOptions, /*requestor*/null); } protected final void runNegativeTest(String[] sources, String expectedOutput) { testDriver.runNegativeTest(sources, expectedOutput); } protected final void runNegativeTest(String[] sources, String expectedOutput, Map<String, String> compilerOptions) { testDriver.runNegativeTest(sources, expectedOutput, null, true, compilerOptions); } //-------------------------------------------------------------------------- protected static GroovyCompilationUnitDeclaration getCUDeclFor(String filename) { return ((DebugRequestor) GroovyParser.debugRequestor).declarations.get(filename); } protected static ModuleNode getModuleNode(String filename) { GroovyCompilationUnitDeclaration decl = getCUDeclFor(filename); if (decl != null) { return decl.getModuleNode(); } else { return null; } } protected static void checkDisassemblyFor(String filename, String expectedOutput) { checkDisassemblyFor(filename, expectedOutput, ClassFileBytesDisassembler.DETAILED); } /** * Check the disassembly of a .class file for a particular piece of text */ protected static void checkDisassemblyFor(String filename, String expectedOutput, int detail) { try { File f = new File(AbstractRegressionTest.OUTPUT_DIR + File.separator + filename); byte[] classFileBytes = org.eclipse.jdt.internal.compiler.util.Util.getFileByteContent(f); ClassFileBytesDisassembler disassembler = ToolFactory.createDefaultClassFileBytesDisassembler(); String result = disassembler.disassemble(classFileBytes, "\n", detail); int index = result.indexOf(expectedOutput); if (index == -1 || expectedOutput.length() == 0) { System.out.println(Util.displayString(result, 3)); } if (index == -1) { assertEquals("Wrong contents", expectedOutput, result); } } catch (Exception e) { fail(e.toString()); } } protected static void checkGCUDeclaration(String filename, String expectedOutput) { GroovyCompilationUnitDeclaration decl = ((DebugRequestor) GroovyParser.debugRequestor).declarations.get(filename); String declarationContents = decl.print(); if (expectedOutput == null || expectedOutput.length() == 0) { System.out.println(Util.displayString(declarationContents, 2)); } else { int foundIndex = declarationContents.indexOf(expectedOutput); if (foundIndex == -1) { fail( "Did not find expected output:\n" + expectedOutput + "\nin actual output:\n" + declarationContents); } } } protected static FieldDeclaration grabField(GroovyCompilationUnitDeclaration decl, String fieldname) { FieldDeclaration[] fDecls = decl.types[0].fields; for (int i = 0, n = fDecls.length; i < n; i += 1) { if (new String(fDecls[i].name).equals(fieldname)) { return fDecls[i]; } } return null; } protected static String stringify(TypeReference type) { StringBuilder sb = new StringBuilder(); stringify(type, sb); return sb.toString(); } protected static void stringify(TypeReference type, StringBuilder sb) { if (type.getClass() == ParameterizedSingleTypeReference.class) { ParameterizedSingleTypeReference pstr = (ParameterizedSingleTypeReference) type; sb.append("(" + pstr.sourceStart + ">" + pstr.sourceEnd + ")").append(pstr.token); TypeReference[] typeArgs = pstr.typeArguments; sb.append("<"); for (int t = 0; t < typeArgs.length; t++) { stringify(typeArgs[t], sb); } sb.append(">"); } else if (type.getClass() == ParameterizedQualifiedTypeReference.class) { ParameterizedQualifiedTypeReference pqtr = (ParameterizedQualifiedTypeReference) type; sb.append("(" + type.sourceStart + ">" + type.sourceEnd + ")"); long[] positions = pqtr.sourcePositions; TypeReference[][] allTypeArgs = pqtr.typeArguments; for (int i = 0; i < pqtr.tokens.length; i++) { if (i > 0) { sb.append('.'); } sb.append("(" + (int) (positions[i] >>> 32) + ">" + (int) (positions[i] & 0x00000000FFFFFFFFL) + ")").append(pqtr.tokens[i]); if (allTypeArgs[i] != null) { sb.append("<"); for (int t = 0; t < allTypeArgs[i].length; t++) { stringify(allTypeArgs[i][t], sb); } sb.append(">"); } } } else if (type.getClass() == ArrayTypeReference.class) { ArrayTypeReference atr = (ArrayTypeReference) type; // for a reference 'String[]' sourceStart='S' sourceEnd=']' originalSourceEnd='g' sb.append("(" + atr.sourceStart + ">" + atr.sourceEnd + " ose:" + atr.originalSourceEnd + ")") .append(atr.token); for (int d = 0; d < atr.dimensions; d++) { sb.append("[]"); } } else if (type.getClass() == Wildcard.class) { Wildcard w = (Wildcard) type; if (w.kind == Wildcard.UNBOUND) { sb.append("(" + type.sourceStart + ">" + type.sourceEnd + ")").append('?'); } else if (w.kind == Wildcard.SUPER) { sb.append("(" + type.sourceStart + ">" + type.sourceEnd + ")").append("? super "); stringify(w.bound, sb); } else if (w.kind == Wildcard.EXTENDS) { sb.append("(" + type.sourceStart + ">" + type.sourceEnd + ")").append("? extends "); stringify(w.bound, sb); } } else if (type.getClass() == SingleTypeReference.class) { sb.append("(" + type.sourceStart + ">" + type.sourceEnd + ")").append(((SingleTypeReference) type).token); } else if (type instanceof ArrayQualifiedTypeReference) { ArrayQualifiedTypeReference aqtr = (ArrayQualifiedTypeReference) type; sb.append("(" + type.sourceStart + ">" + type.sourceEnd + ")"); long[] positions = aqtr.sourcePositions; for (int i = 0; i < aqtr.tokens.length; i++) { if (i > 0) { sb.append('.'); } sb.append("(" + (int) (positions[i] >>> 32) + ">" + (int) (positions[i] & 0x00000000FFFFFFFFL) + ")").append(aqtr.tokens[i]); } for (int i = 0; i < aqtr.dimensions(); i++) { sb.append("[]"); } } else if (type.getClass() == QualifiedTypeReference.class) { QualifiedTypeReference qtr = (QualifiedTypeReference) type; sb.append("(" + type.sourceStart + ">" + type.sourceEnd + ")"); long[] positions = qtr.sourcePositions; for (int i = 0; i < qtr.tokens.length; i++) { if (i > 0) { sb.append('.'); } sb.append("(" + (int) (positions[i] >>> 32) + ">" + (int) (positions[i] & 0x00000000FFFFFFFFL) + ")").append(qtr.tokens[i]); } } else { throw new RuntimeException("Dont know how to print " + type.getClass()); } } }