/*
* 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.eclipse.jdt.core.tests.util.GroovyUtils.isAtLeastGroovy;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import java.net.URL;
import java.util.List;
import java.util.Map;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.jdt.groovy.internal.compiler.ast.GroovyCompilationUnitDeclaration;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.tests.util.GroovyUtils;
import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
import org.junit.Ignore;
import org.junit.Test;
import org.osgi.framework.Version;
public final class TransformationsTests extends GroovyCompilerTestSuite {
public TransformationsTests(long level) {
super(level);
}
private String getJarPath(String entry) throws Exception {
URL url = Platform.getBundle("org.eclipse.jdt.groovy.core.tests.compiler").getEntry(entry);
return FileLocator.resolve(url).getFile();
}
@Test
public void testAnnotationCollector() {
assumeTrue(isAtLeastGroovy(21));
String[] sources = {
"Book.groovy",
"import java.lang.reflect.*;\n" +
"class Book {\n" +
" @ISBN String isbn;\n" +
" public static void main(String []argv) {\n" +
" Field f = Book.class.getDeclaredField('isbn');\n" +
" Object[] os = f.getDeclaredAnnotations();\n" +
" for (Object o: os) {\n" +
" System.out.print(o);\n" +
" }\n" +
" }\n" +
"}",
"NotNull.java",
"import java.lang.annotation.*;\n" +
"@Retention(RetentionPolicy.RUNTIME) public @interface NotNull {\n" +
"}",
"Length.java",
"import java.lang.annotation.*;\n" +
"@Retention(RetentionPolicy.RUNTIME) public @interface Length {\n" +
" int value() default 0;\n" +
"}",
"ISBN.groovy",
"@NotNull @Length @groovy.transform.AnnotationCollector\n" +
"public @interface ISBN {\n" +
"}"
};
runConformTest(sources, "@NotNull()@Length(value=0)");
}
@Test
public void testBuiltInTransforms_Singleton() {
String[] sources = {
"Goo.groovy",
"class Goo {\n" +
" public static void main(String[] argv) {\n" +
" Run.main(argv)\n" +
" }\n" +
"}",
"Run.groovy",
"public class Run {\n" +
" public static void main(String[] argv) {\n" +
" System.out.println(Wibble.getInstance().field)\n" +
" }\n" +
"}",
"Wibble.groovy",
"@Singleton class Wibble {\n" +
" public String field = 'abcd'\n" +
"}"
};
runConformTest(sources, "abcd");
}
@Test // lazy option set in Singleton
public void testBuiltInTransforms_Singleton2() {
//This test breaks on Groovy < 2.2.1 because the 'strict' flag was introduced in that version.
assumeTrue(isAtLeastGroovy(22));
String[] sources = {
"Goo.groovy",
"class Goo {\n"+
" public static void main(String[] argv) {\n"+
" Run.main(argv);\n"+
" }\n"+
"}\n",
"Run.groovy",
"public class Run {\n"+
" public static void main(String[] argv) {\n"+
" Wibble.run();\n"+
" System.out.print(\"running \");\n"+
" System.out.print(Wibble.getInstance().field);\n"+
" }\n"+
"}\n",
"Wibble.groovy",
"@Singleton(lazy=false, strict=false) class Wibble {" +
" public String field = 'abcd';\n"+
" private Wibble() { print \"ctor \";}\n"+
" static void run() {}\n"+
"}\n"
};
runConformTest(sources, "ctor running abcd");
}
@Test
public void testBuiltInTransforms_Singleton3() {
//This test breaks on Groovy < 2.2.1 because the 'strict' flag was introduced in that version.
assumeTrue(isAtLeastGroovy(22));
String[] sources = {
"Goo.groovy",
"class Goo {\n"+
" public static void main(String[] argv) {\n"+
" Run.main(argv);\n"+
" }\n"+
"}\n",
"Run.groovy",
"public class Run {\n"+
" public static void main(String[] argv) {\n"+
" Wibble.run();\n"+
" System.out.print(\"running \");\n"+
" System.out.print(Wibble.getInstance().field);\n"+
" }\n"+
"}\n",
"Wibble.groovy",
"@Singleton(lazy=true, strict=false) class Wibble {" +
" public String field = 'abcd';\n"+
" private Wibble() { print \"ctor \";}\n"+
" static void run() {}\n"+
"}\n"
};
runConformTest(sources, "running ctor abcd");
}
@Test
public void testBuiltInTransforms_Category1() {
String[] sources = {
"Demo.groovy",
" use(NumberCategory) {\n"+
" def dist = 300.meters\n"+
"\n"+
" assert dist instanceof Distance\n"+
" assert dist.toString() == \"300m\"\n"+
" print dist.toString()\n"+
"}",
"Distance.groovy",
" final class Distance {\n"+
" def number\n"+
" String toString() { \"${number}m\" }\n"+
"}",
"NumberCategory.groovy",
" class NumberCategory {\n"+
" static Distance getMeters(Number self) {\n"+
" new Distance(number: self)\n"+
" }\n"+
"}"
};
runConformTest(sources, "300m");
}
@Test
public void testBuiltInTransforms_Category2() {
String[] sources = {
"Demo.groovy",
" use(NumberCategory) {\n"+
" def dist = 300.meters\n"+
"\n"+
" assert dist instanceof Distance\n"+
" assert dist.toString() == \"300m\"\n"+
" print dist.toString()\n"+
" }\n",
"Distance.groovy",
" final class Distance {\n"+
" def number\n"+
" String toString() { \"${number}m\" }\n"+
" }\n",
"NumberCategory.groovy",
" @Category(Number) class NumberCategory {\n"+
" Distance getMeters() {\n"+
" new Distance(number: this)\n"+
" }\n"+
" }\n"+
"\n"
};
runConformTest(sources, "300m");
}
@Test
public void testBuiltInTransforms_Category3() {
String[] sources = {
"Foo.groovy",
"assert new Plane().fly() ==\n"+
" \"I'm the Concorde and I fly!\"\n"+
"assert new Submarine().dive() ==\n"+
" \"I'm the Yellow Submarine and I dive!\"\n"+
"\n"+
"assert new JamesBondVehicle().fly() ==\n"+
" \"I'm the James Bond's vehicle and I fly!\"\n"+
"assert new JamesBondVehicle().dive() ==\n"+
" \"I'm the James Bond's vehicle and I dive!\"\n"+
"print new JamesBondVehicle().dive();\n",
"FlyingAbility.groovy",
"@Category(Vehicle) class FlyingAbility {\n"+
" def fly() { \"I'm the ${name} and I fly!\" }\n"+
"}\n",
"DivingAbility.groovy",
"@Category(Vehicle) class DivingAbility {\n"+
" def dive() { \"I'm the ${name} and I dive!\" }\n"+
"}\n",
"Vehicle.java",
"interface Vehicle {\n"+
" String getName();\n"+
"}\n",
"Submarine.groovy",
"@Mixin(DivingAbility)\n"+
"class Submarine implements Vehicle {\n"+
" String getName() { \"Yellow Submarine\" }\n"+
"}\n",
"Plane.groovy",
"@Mixin(FlyingAbility)\n"+
"class Plane implements Vehicle {\n"+
" String getName() { \"Concorde\" }\n"+
"}\n",
"JamesBondVehicle.groovy",
"@Mixin([DivingAbility, FlyingAbility])\n"+
"class JamesBondVehicle implements Vehicle {\n"+
" String getName() { \"James Bond's vehicle\" }\n"+
"}\n"
};
runConformTest(sources, "I'm the James Bond's vehicle and I dive!");
}
@Test
public void testBuiltInTransforms_PackageScope() {
// http://groovy.codehaus.org/PackageScope+transformation
// Adjust the visibility of a property so instead of private it is package default
String[] sources = {
"Goo.groovy",
"class Goo {\n"+
" public static void main(String[] argv) {\n"+
" q.Run.main(argv);\n"+
" }\n"+
"}\n",
"q/Run.groovy",
"package q;\n"+
"import q.Wibble;\n"+
"public class Run {\n"+
" public static void main(String[] argv) throws Exception {\n"+
" Wibble w = new Wibble();\n"+
" System.out.print(Wibble.class.getDeclaredField(\"field\").getModifiers());\n"+
" System.out.print(Wibble.class.getDeclaredField(\"field2\").getModifiers());\n"+
" }\n"+
"}\n",
"q/Wibble.groovy",
"package q\n"+
"class Wibble {" +
" String field = 'abcd';\n"+
" @groovy.transform.PackageScope String field2 = 'abcd';\n"+
"}\n"
};
runConformTest(sources, "20"); // 0x2 = private 0x0 = default (so field2 has had private vis removed by annotation);
}
@Test // not a great test, needs work
public void testCategory_STS3822() {
if (JavaCore.getPlugin().getBundle().getVersion().compareTo(Version.parseVersion("3.10")) < 0) return;
String[] sources = {
"bad.groovy",
"@Category(C.class) \n"+
"@ScriptMixin(C.class)\n"+
"class Bad {\n"+
" @Override\n"+
" public String toString()\n"+
" { return \"Bad [takeI()=\" + takeI() + \"]\"; }\n"+
"}\n"
};
runNegativeTest(sources,
"----------\n" +
"1. ERROR in bad.groovy (at line 1)\n" +
"\t@Category(C.class) \n" +
"\t ^"+(GroovyUtils.isAtLeastGroovy(20)?"^^^^^^^":"")+"\n" +
"Groovy:@groovy.lang.Category must define \'value\' which is the class to apply this category to @ line 1, column 2.\n" +
"----------\n" +
"2. ERROR in bad.groovy (at line 1)\n" +
"\t@Category(C.class) \n" +
"\t ^\n" +
"Groovy:unable to find class \'C.class\' for annotation attribute constant\n" +
"----------\n" +
"3. ERROR in bad.groovy (at line 1)\n" +
"\t@Category(C.class) \n" +
"\t ^"+(GroovyUtils.isAtLeastGroovy(20)?"^^^^^^":"")+"\n" +
"Groovy:Only classes and closures can be used for attribute \'value\' in @groovy.lang.Category\n" +
"----------\n" +
"4. ERROR in bad.groovy (at line 2)\n" +
"\t@ScriptMixin(C.class)\n" +
"\t ^^^^^^^^^^^\n" +
"Groovy:unable to resolve class ScriptMixin , unable to find class for annotation\n" +
"----------\n" +
"5. ERROR in bad.groovy (at line 2)\n" +
"\t@ScriptMixin(C.class)\n" +
"\t ^"+(GroovyUtils.isAtLeastGroovy(20)?"^^^^^^^^^^":"")+"\n" +
"Groovy:class ScriptMixin is not an annotation in @ScriptMixin\n" +
"----------\n" +
"6. ERROR in bad.groovy (at line 2)\n" +
"\t@ScriptMixin(C.class)\n" +
"\t ^\n" +
"Groovy:unable to find class \'C.class\' for annotation attribute constant\n" +
"----------\n" +
(!GroovyUtils.isAtLeastGroovy(20)?"":
"7. ERROR in bad.groovy (at line 4)\n" +
"\t@Override\n" +
"\t ^^^^^^^^\n" +
"Groovy:Method \'toString\' from class \'Bad\' does not override method from its superclass or interfaces but is annotated with @Override.\n" +
"----------\n"));
}
@Test
public void testDelegate() {
String[] sources = {
"Bar.groovy",
"class Foo { @Delegate URL myUrl }\n" +
"\n" +
"print Foo.class.getDeclaredMethod('getContent', Class[].class)"
};
runConformTest(sources, "public final java.lang.Object Foo.getContent(java.lang.Class[]) throws java.io.IOException");
}
@Test
public void testImmutable() {
String[] sources = {
"c/Main.java",
"package c;\n" +
"public class Main {\n" +
" public static void main(String[] args) {" +
" }\n" +
"}\n",
"a/SomeId.groovy",
"package a;\n" +
"import groovy.transform.Immutable\n" +
"@Immutable\n" +
"class SomeId {\n" +
" UUID id\n" +
"}\n",
"b/SomeValueObject.groovy",
"package b;\n" +
"import groovy.transform.Immutable\n" +
"import a.SomeId\n" +
"@Immutable\n" +
"class SomeValueObject {\n" +
" SomeId id\n" +
"}\n"
};
runConformTest(sources);
GroovyCompilationUnitDeclaration unit = getCUDeclFor("SomeValueObject.groovy");
ClassNode classNode = unit.getCompilationUnit().getClassNode("b.SomeValueObject");
FieldNode field = classNode.getField("id");
ClassNode type = field.getType();
List<AnnotationNode> annotations = type.getAnnotations(ClassHelper.make(groovy.transform.Immutable.class));
assertEquals(1, annotations.size());
}
/**
* COOL!!! The getInstance() method is added by a late AST Transformation made due to the Singleton annotation - and yet
* still it is referencable from Java. This is not possible with normal joint compilation.
* currently have to 'turn on' support in GroovyClassScope.getAnyExtraMethods() - still thinking about this stuff...
*/
@Test @Ignore
public void testJavaAccessingTransformedGroovy_Singleton() {
String[] sources = {
"Goo.groovy",
"class Goo {\n"+
" public static void main(String[] argv) {\n"+
" Run.main(argv);\n"+
" }\n"+
"}\n",
"Run.java",
"public class Run {\n"+
" public static void main(String[] argv) {\n"+
" System.out.println(Wibble.getInstance().field);\n"+
" }\n"+
"}\n",
"Wibble.groovy",
"@Singleton class Wibble {" +
" public String field = 'abc';\n"+
"}\n"
};
runConformTest(sources, "abc");
}
@Test
public void testTypeChecked1() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"Foo.groovy",
"import groovy.transform.TypeChecked\n"+
"@TypeChecked\n"+
"void method(String message) {\n"+
" if (rareCondition) {\n"+
" println \"Did you spot the error in this ${message.toUppercase()}?\"\n"+
" }\n"+
"}"
};
runNegativeTest(sources,
"----------\n" +
"1. ERROR in Foo.groovy (at line 4)\n" +
"\tif (rareCondition) {\n" +
"\t ^"+(GroovyUtils.isAtLeastGroovy(20)?"^^^^^^^^^^^^":"")+"\n" +
"Groovy:[Static type checking] - The variable [rareCondition] is undeclared.\n" +
"----------\n" +
"2. ERROR in Foo.groovy (at line 5)\n" +
"\tprintln \"Did you spot the error in this ${message.toUppercase()}?\"\n" +
"\t ^"+(GroovyUtils.isAtLeastGroovy(20)?"^^^^^^^^^^^^^^^^^^^^^^":"")+"\n" +
"Groovy:[Static type checking] - Cannot find matching method java.lang.String#toUppercase()"+(GroovyUtils.isAtLeastGroovy(20)?". Please check if the declared type is right and if the method exists.":"")+"\n" +
"----------\n");
}
@Test
public void testTypeChecked2() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"Foo.groovy",
"import groovy.transform.TypeChecked\n"+
"@TypeChecked\n"+
"void method(String message) {\n"+
" List<Integer> ls = new ArrayList<Integer>();\n"+
" ls.add(123);\n"+
" ls.add('abc');\n"+
"}"
};
runNegativeTest(sources,
"----------\n" +
"1. ERROR in Foo.groovy (at line 6)\n" +
"\tls.add(\'abc\');\n" +
"\t^" + (GroovyUtils.isAtLeastGroovy(20) ? "^^^^^^^^^^^^" : "") + "\n" +
(GroovyUtils.isAtLeastGroovy(23) ? "Groovy:[Static type checking] - Cannot call java.util.ArrayList <Integer>#add(java.lang.Integer) with arguments [java.lang.String] ":
"Groovy:[Static type checking] - Cannot find matching method java.util.ArrayList#add(java.lang.String)" + (GroovyUtils.isAtLeastGroovy(20) ? ". Please check if the declared type is right and if the method exists." : "")) + "\n" +
"----------\n");
}
@Test
public void testTypeChecked3() {
assumeTrue(isAtLeastGroovy(21));
String[] sources = {
"Foo.groovy",
"@groovy.transform.TypeChecked\n" +
"class Foo {" +
" def method() {\n" +
" Set<java.beans.BeanInfo> defs = []\n" +
" defs*.additionalBeanInfo\n" +
" }\n" +
"}"
};
runConformTest(sources);
}
@Test @Ignore("https://issues.apache.org/jira/browse/GROOVY-8033")
public void testTypeChecked4() {
assumeTrue(isAtLeastGroovy(21));
String[] sources = {
"Foo.groovy",
"@groovy.transform.TypeChecked\n" +
"class Foo {" +
" static def method() {\n" + // static method alters type checking
" Set<java.beans.BeanInfo> defs = []\n" +
" defs*.additionalBeanInfo\n" +
" }\n" +
"}"
};
runConformTest(sources);
}
@Test
public void testCompileStatic1() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"Foo.groovy",
"import groovy.transform.CompileStatic\n"+
"@CompileStatic\n"+
"void method(String message) {\n"+
" List<Integer> ls = new ArrayList<Integer>();\n"+
" ls.add(123);\n"+
" ls.add('abc');\n"+
"}"
};
runNegativeTest(sources,
"----------\n" +
"1. ERROR in Foo.groovy (at line 6)\n" +
"\tls.add(\'abc\');\n" +
"\t^"+(GroovyUtils.isAtLeastGroovy(20)?"^^^^^^^^^^^^":"")+"\n" +
(GroovyUtils.isAtLeastGroovy(23)?
"Groovy:[Static type checking] - Cannot call java.util.ArrayList <Integer>#add(java.lang.Integer) with arguments [java.lang.String] \n":
"Groovy:[Static type checking] - Cannot find matching method java.util.ArrayList#add(java.lang.String)"+(GroovyUtils.isAtLeastGroovy(20)?". Please check if the declared type is right and if the method exists.":""))+(GroovyUtils.isAtLeastGroovy(23)?"":"\n") +
"----------\n");
}
/**
* Testing the code in the StaticTypeCheckingSupport.checkCompatibleAssignmentTypes.
*
* That method does a lot of equality by == testing against classnode constants, which doesn't work so well for us...
*/
@Test
public void testCompileStatic2() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"Foo.groovy",
"import groovy.transform.CompileStatic;\n"+
"\n"+
"import java.util.Properties;\n"+
"\n"+
"class One { \n"+
" @CompileStatic\n"+
" private String getPropertyValue(String propertyName, Properties props, String defaultValue) {\n"+
" // First check whether we have a system property with the given name.\n"+
" def value = getValueFromSystemOrBuild(propertyName, props)\n"+
"\n"+
" // Return the BuildSettings value if there is one, otherwise\n"+
" // use the default.\n"+
" return value != null ? value : defaultValue \n"+
" }\n"+
"\n"+
" @CompileStatic\n"+
" private getValueFromSystemOrBuild(String propertyName, Properties props) {\n"+
" def value = System.getProperty(propertyName)\n"+
" if (value != null) return value\n"+
"\n"+
" // Now try the BuildSettings config.\n"+
" value = props[propertyName]\n"+
" return value\n"+
" } \n"+
"} \n"
};
runConformTest(sources);
}
@Test
public void testCompileStatic3() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"Foo.groovy",
"import groovy.transform.CompileStatic;\n"+
"\n"+
"@CompileStatic void test() {\n"+
" int littleInt = 3\n"+
" Integer objectInt = littleInt\n"+
"}\n"
};
runConformTest(sources);
}
@Test
public void testCompileStatic_1511() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"Foo.groovy",
"@groovy.transform.CompileStatic\n"+
"def meth() {\n"+
" List<String> second = []\n"+
" List<String> artefactResources2 = []\n"+
" second.addAll(artefactResources2)\n"+
" println 'abc'\n"+
"}\n"+
"meth();"
};
runConformTest(sources, "abc");
}
@Test
public void testCompileStatic_1505() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"DynamicQuery.groovy",
"import groovy.transform.TypeChecked\n"+
"@TypeChecked\n"+
"class DynamicQuery {\n"+
" public static void main(String[]argv) {\n"+
" new DynamicQuery().foo(null);\n"+
" }\n"+
" private foo(Map sumpin){\n"+
" Map foo\n"+
" foo.collect{ Map.Entry it ->it.key}\n"+
" print 'abc';\n"+
" }\n"+
"}\n"
};
runConformTest(sources, "abc");
}
@Test @Ignore
public void testCompileStatic_1506() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"LoggerTest.groovy",
"import groovy.transform.TypeChecked\n"+
"import groovy.util.logging.*\n"+
"@Slf4j\n"+
"@TypeChecked\n"+
" public class LoggerTest {\n"+
" public static void main(String... args) {\n"+
" println \"println\"\n"+
" LoggerTest.log.info(\"Logged\");\n"+
" log.info(\"foo\")\n"+
" }\n"+
" }\n"
};
runConformTest(sources);
}
@Test
public void testCompileStatic4() {
assumeTrue(isAtLeastGroovy(20));
// verify generics are correct for the 'Closure<?>' as CompileStatic will attempt an exact match
String[] sources = {
"A.groovy",
"class A {\n"+
" public void profile(String name, groovy.lang.Closure<?> callable) { }\n"+
"}\n",
"B.groovy",
"@groovy.transform.CompileStatic\n"+
"class B extends A {\n"+
"\n"+
" def foo() {\n"+
" profile(\"creating plugin manager with classes\") {\n"+
" System.out.println('abc');\n"+
" }\n"+
" }\n"+
"\n"+
"}\n"
};
runConformTest(sources);
}
@Test
public void testCompileDynamic() {
assumeTrue(isAtLeastGroovy(21));
String[] sources = {
"A.groovy",
"@groovy.transform.CompileStatic\n" +
"class A {\n" +
" int prop\n" +
" int computeStatic(int input) {\n" +
" prop + input\n" +
" }\n" +
" @groovy.transform.CompileDynamic\n" +
" int computeDynamic(int input) {\n" +
" missing(prop, input)\n" +
" }\n" +
"}"
};
runConformTest(sources);
}
@Test
public void testTransforms_BasicLogging() throws Exception {
Map<String, String> options = getCompilerOptions();
options.put(CompilerOptions.OPTIONG_GroovyClassLoaderPath, getJarPath("astTransformations/transforms.jar"));
options.put(CompilerOptions.OPTIONG_GroovyProjectName, "Test");
// From: http://svn.codehaus.org/groovy/trunk/groovy/groovy-core/src/examples/transforms/local
String[] sources = {
"examples/local/LoggingExample.groovy",
"package examples.local\n"+
"\n"+
"/**\n"+
"* Demonstrates how a local transformation works. \n"+
"* \n"+
"* @author Hamlet D'Arcy\n"+
"*/ \n"+
"\n"+
"def greet() {\n"+
" println \"Hello World\"\n"+
"}\n"+
" \n"+
"@WithLogging //this should trigger extra logging\n"+
"def greetWithLogging() {\n"+
" println \"Hello World\"\n"+
"}\n"+
" \n"+
"// this prints out a simple Hello World\n"+
"greet()\n"+
"\n"+
"// this prints out Hello World along with the extra compile time logging\n"+
"greetWithLogging()\n"+
"\n"+
"\n"+
"//\n"+
"// The rest of this script is asserting that this all works correctly. \n"+
"//\n"+
"\n"+
"def oldOut = System.out\n"+
"// redirect standard out so we can make assertions on it\n"+
"def standardOut = new ByteArrayOutputStream();\n"+
"System.setOut(new PrintStream(standardOut)); \n"+
" \n"+
"greet()\n"+
"assert \"Hello World\" == standardOut.toString(\"ISO-8859-1\").trim()\n"+
"\n"+
"// reset standard out and redirect it again\n"+
"standardOut.close()\n"+
"standardOut = new ByteArrayOutputStream();\n"+
"System.setOut(new PrintStream(standardOut)); \n"+
"\n"+
"greetWithLogging()\n"+
"def result = standardOut.toString(\"ISO-8859-1\").split('\\n')\n"+
"assert \"Starting greetWithLogging\" == result[0].trim()\n"+
"assert \"Hello World\" == result[1].trim()\n"+
"assert \"Ending greetWithLogging\" == result[2].trim()\n"+
"\n"+
"System.setOut(oldOut);\n"+
"print 'done'\n"+
"\n",
// "examples/local/WithLogging.groovy",
// "package examples.local\n"+
// "import java.lang.annotation.Retention\n"+
// "import java.lang.annotation.Target\n"+
// "import org.codehaus.groovy.transform.GroovyASTTransformationClass\n"+
// "import java.lang.annotation.ElementType\n"+
// "import java.lang.annotation.RetentionPolicy\n"+
// "\n"+
// "/**\n"+
// "* This is just a marker interface that will trigger a local transformation. \n"+
// "* The 3rd Annotation down is the important one: @GroovyASTTransformationClass\n"+
// "* The parameter is the String form of a fully qualified class name. \n"+
// "*\n"+
// "* @author Hamlet D'Arcy\n"+
// "*/ \n"+
// "@Retention(RetentionPolicy.SOURCE)\n"+
// "@Target([ElementType.METHOD])\n"+
// "@GroovyASTTransformationClass([\"examples.local.LoggingASTTransformation\"])\n"+
// "public @interface WithLogging {\n"+
// "}\n"
};
runConformTest(sources,
"Hello World\n" +
"Starting greetWithLogging\n" +
"Hello World\n" +
"Ending greetWithLogging\n" +
"done",
options);
}
@Test
public void testTransforms_AtLog() {
// See
// https://jira.codehaus.org/browse/GRECLIPSE-1503
// https://jira.codehaus.org/browse/GROOVY-5736
String[] sources = {
"examples/local/Log4jExample.groovy",
"package examples.local\n" +
"import groovy.util.logging.*\n" +
"@Log4j\n" +
"class Log4jExample {\n" +
" def meth() {\n" +
" logger.info('yay!')\n" +
" }\n" +
"}",
"examples/local/Slf4JExample.groovy",
"package examples.local\n" +
"import groovy.util.logging.*\n" +
"@Slf4j\n" +
"class Slf4jExample {\n" +
" def meth() {\n" +
" logger.info('yay!')\n" +
" }\n" +
"}",
"examples/local/LoggingExample.groovy",
"package examples.local\n" +
"import groovy.util.logging.*\n" +
"@Log\n" +
"class LoggingExample {\n" +
" def meth() {\n" +
" logger.info('yay!')\n" +
" }\n" +
"}",
"examples/local/CommonsExample.groovy",
"package examples.local\n" +
"import groovy.util.logging.*\n" +
"@Commons\n" +
"class CommonsExample {\n" +
" def meth() {\n" +
" logger.info('yay!')\n" +
" }\n" +
"}"
};
runConformTest(sources);
}
@Test
public void testJDTClassNode_1731() {
assumeTrue(isAtLeastGroovy(21));
// Testcode based on article: http://www.infoq.com/articles/groovy-1.5-new
// The groups of tests are loosely based on the article contents - but what is really exercised here is the accessibility of
// the described constructs across the Java/Groovy divide.
String[] sources = {
"c/Main.java",
"package c;\n" +
"import java.lang.reflect.Method;\n" +
"import a.SampleAnnotation;\n" +
"import b.Sample;\n" +
"public class Main {\n" +
" public static void main(String[] args) throws Exception {" +
" Method method = Sample.class.getMethod(\"doSomething\");\n" +
" SampleAnnotation annotation = method.getAnnotation(SampleAnnotation.class);\n" +
" System.out.print(annotation);\n" +
" }\n" +
"}\n",
"a/SampleAnnotation.java",
"package a;\n" +
"import java.lang.annotation.ElementType;\n" +
"import java.lang.annotation.Retention;\n" +
"import java.lang.annotation.RetentionPolicy;\n" +
"import java.lang.annotation.Target;\n" +
"@Retention(RetentionPolicy.RUNTIME)\n" +
"@Target({ElementType.METHOD})\n" +
"public @interface SampleAnnotation {}\n",
"a/DelegateInOtherProject.java",
"package a;\n" +
"public class DelegateInOtherProject {\n" +
" @SampleAnnotation\n" +
" public void doSomething() {}\n" +
"}\n",
"b/Sample.groovy",
"package b\n" +
"import groovy.transform.CompileStatic\n" +
"import a.DelegateInOtherProject;\n" +
"@CompileStatic\n" +
"class Sample {\n" +
" @Delegate(methodAnnotations = true)\n" +
" DelegateInOtherProject delegate\n" +
"}\n",
"b/Delegated.groovy",
"package b\n" +
"import groovy.transform.CompileStatic\n" +
"import a.SampleAnnotation;\n" +
"@CompileStatic\n" +
"class Delegated {\n" +
" @SampleAnnotation\n" +
" def something() {}\n" +
"}\n"
};
runConformTest(sources, "@a.SampleAnnotation()");
}
@Test @Ignore("Grab is failing on CI server")
public void testGrab() {
String[] sources = {
"Printer.groovy",
"@Grab('joda-time:joda-time:1.6')\n"+
"def printDate() {\n"+
" def dt = new org.joda.time.DateTime()\n"+
"}\n"+
"printDate()"
};
runConformTest(sources);
}
/**
* Improving grab, this program has a broken grab. Without changes we get a 'general error' recorded on the first line of the source file (big juicy exception)
* General error during conversion: Error grabbing Grapes -- [unresolved dependency: org.aspectj#aspectjweaver;1.6.11x: not found] java.lang.RuntimeException: Error grabbing
* Grapes -- [unresolved dependency: org.aspectj#aspectjweaver;1.6.11x: not found] at sun.reflect.GeneratedConstructorAccessor48.newInstance(Unknown Source) at
* sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at
* org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77) at ...
* With grab improvements we get two errors - the missing dependency and the missing type (which is at the right version of that dependency!)
*/
@Test @Ignore("Grab is failing on CI server")
public void testGrabWithErrors() {
String[] sources = {
"Grab1.groovy",
"@Grapes([\n"+
" @Grab(group='joda-time', module='joda-time', version='1.6'),\n"+
" @Grab(group='org.aspectj', module='aspectjweaver', version='1.6.11x')\n"+
"])\n" +
"class C {\n"+
" def printDate() {\n"+
" def dt = new org.joda.time.DateTime()\n"+
" def world = new org.aspectj.weaver.bcel.BcelWorld()\n"+
" print dt\n"+
" }\n"+
" public static void main(String[] argv) {\n"+
" new C().printDate()\n"+
" }\n"+
"}\n"
};
runNegativeTest(sources,
"----------\n" +
"1. ERROR in Grab1.groovy (at line 3)\n" +
"\t@Grab(group='org.aspectj', module='aspectjweaver', version='1.6.11x')\n" +
"\t ^^^\n" +
"Groovy:Error grabbing Grapes -- [unresolved dependency: org.aspectj#aspectjweaver;1.6.11x: not found]\n" +
"----------\n" +
"2. ERROR in Grab1.groovy (at line 8)\n" +
"\tdef world = new org.aspectj.weaver.bcel.BcelWorld()\n" +
"\t ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" +
"Groovy:unable to resolve class org.aspectj.weaver.bcel.BcelWorld \n" +
"----------\n");
}
@Test @Ignore("Grab is failing on CI server")
public void testGrabScriptAndImports_GRE680() {
String[] sources = {
"Script.groovy",
"import org.mortbay.jetty.Server\n"+
"import org.mortbay.jetty.servlet.*\n"+
"import groovy.servlet.*\n"+
"\n"+
"@Grab(group='org.mortbay.jetty', module='jetty-embedded', version='6.1.0')\n"+
"def runServer(duration) { }\n"+
"runServer(10000)"
};
runConformTest(sources);
}
@Test
public void testGreclipse1506() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"Foo.groovy",
"import groovy.transform.TypeChecked;\n"+
"import groovy.util.logging.Log;\n"+
"\n"+
"@TypeChecked @Log\n"+
"public class LoggerTest {\n"+
" public static void main(String... args) {\n"+
" println 'println'\n"+
" log.info('foo')\n"+
" }\n"+
"}"
};
runConformTest(sources);
}
@Test
public void testGreclipse1514() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"C.groovy",
"@SuppressWarnings(\"rawtypes\")\n"+
"@groovy.transform.CompileStatic\n"+
"class C {\n"+
" def xxx(List list) {\n"+
" list.unique().each { }\n"+
" }\n"+
"}\n"
};
runConformTest(sources);
}
@Test
public void testGreclipse1515() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"C.groovy",
"import groovy.transform.CompileStatic;\n" +
"import java.util.regex.Pattern\n" +
"\n" +
"@CompileStatic\n" +
"class C {\n" +
" void validate() {\n" +
" for (String validationKey : [:].keySet()) {\n" +
" String regex\n" +
" Pattern pattern = ~regex\n" + // NPE on this bitwise negation
" }\n" +
" }\n" +
"}"
};
runConformTest(sources);
}
@Test
public void testGreclipse1521() {
assumeTrue(isAtLeastGroovy(20));
String[] sources = {
"Foo.groovy",
"\n"+
"@groovy.transform.CompileStatic\n"+
"class Foo {\n"+
" enum Status { ON, OFF }\n"+
"}"
};
runConformTest(sources);
}
}