| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 groovy |
| |
| import groovy.test.GroovyTestCase |
| import org.codehaus.groovy.control.MultipleCompilationErrorsException |
| |
| import static groovy.lang.Closure.IDENTITY |
| |
| /** |
| * Tests Closures in Groovy |
| */ |
| class ClosureTest extends GroovyTestCase { |
| |
| def count |
| |
| void testSimpleBlockCall() { |
| count = 0 |
| |
| def block = { owner -> owner.incrementCallCount() } |
| |
| assertClosure(block) |
| assert count == 1 |
| |
| assertClosure({ owner -> owner.incrementCallCount() }) |
| assert count == 2 |
| } |
| |
| void testVariableLengthParameterList() { |
| |
| def c1 = { Object[] args -> args.each { count += it } } |
| |
| count = 0 |
| c1(1, 2, 3) |
| assert count == 6 |
| |
| count = 0 |
| c1(1) |
| assert count == 1 |
| |
| count = 0 |
| c1([1, 2, 3] as Object[]) |
| assert count == 6 |
| |
| def c2 = { a, Object[] args -> count += a; args.each { count += it } } |
| |
| count = 0 |
| c2(1, 2, 3) |
| assert count == 6 |
| |
| count = 0 |
| c2(1) |
| assert count == 1 |
| |
| count = 0 |
| c2(1, [2, 3] as Object[]) |
| assert count == 6 |
| } |
| |
| void testBlockAsParameter() { |
| count = 0 |
| |
| callBlock(5, { owner -> owner.incrementCallCount() }) |
| assert count == 6 |
| |
| callBlock2(5, { owner -> owner.incrementCallCount() }) |
| assert count == 12 |
| } |
| |
| void testMethodClosure() { |
| def block = this.&incrementCallCount |
| |
| count = 0 |
| |
| block.call() |
| |
| assert count == 1 |
| |
| block = Math.&min |
| |
| assert block.call(3, 7) == 3 |
| } |
| |
| def incrementCallCount() { |
| //System.out.println("invoked increment method!") |
| count = count + 1 |
| } |
| |
| def assertClosure(Closure block) { |
| assert block != null |
| block.call(this) |
| } |
| |
| protected void callBlock(Integer num, Closure block) { |
| for (i in 0..num) { |
| block.call(this) |
| } |
| } |
| |
| protected void callBlock2(num, block) { |
| for (i in 0..num) { |
| block.call(this) |
| } |
| } |
| |
| |
| int numAgents = 4 |
| boolean testDone = false |
| |
| void testIntFieldAccess() { |
| def agents = new ArrayList(); |
| numAgents.times { |
| TinyAgent btn = new TinyAgent() |
| testDone = true |
| btn.x = numAgents |
| agents.add(btn) |
| } |
| assert agents.size() == numAgents |
| } |
| |
| void testWithIndex() { |
| def str = '' |
| def sum = 0 |
| ['a', 'b', 'c', 'd'].eachWithIndex { item, index -> str += item; sum += index } |
| assert str == 'abcd' && sum == 6 |
| } |
| |
| void testMapWithEntryIndex() { |
| def keyStr = '' |
| def valStr = '' |
| def sum = 0 |
| ['a': 'z', 'b': 'y', 'c': 'x', 'd': 'w'].eachWithIndex { entry, index -> |
| keyStr += entry.key |
| valStr += entry.value |
| sum += index |
| } |
| assert keyStr == 'abcd' && valStr == 'zyxw' && sum == 6 |
| } |
| |
| void testMapWithKeyValueIndex() { |
| def keyStr = '' |
| def valStr = '' |
| def sum = 0 |
| ['a': 'z', 'b': 'y', 'c': 'x', 'd': 'w'].eachWithIndex { k, v, index -> |
| keyStr += k |
| valStr += v |
| sum += index |
| } |
| assert keyStr == 'abcd' && valStr == 'zyxw' && sum == 6 |
| } |
| |
| /** |
| * Test access to Closure's properties |
| * cf GROOVY-2089 |
| */ |
| void testGetProperties() { |
| def c = { println it } |
| |
| assert c.delegate == c.getDelegate() |
| assert c.owner == c.getOwner() |
| assert c.maximumNumberOfParameters == c.getMaximumNumberOfParameters() |
| assert c.parameterTypes == c.getParameterTypes() |
| assert c.class == c.getClass() |
| assert c.directive == c.getDirective() |
| assert c.resolveStrategy == c.getResolveStrategy() |
| assert c.thisObject == c.getThisObject() |
| |
| // no idea why this one fails |
| // assert c.metaClass == c.getMetaClass() |
| } |
| |
| void testGetPropertiesGenerically() { |
| // ensure closure metaclass is the original one |
| Closure.metaClass = null |
| |
| Closure.metaClass.properties.each { property -> |
| def closure = { println it } |
| closure."$property.name" == closure."${MetaProperty.getGetterName(property.name, property.type)}"() |
| } |
| } |
| |
| void testSetProperties() { |
| def c = { println it } |
| |
| def myDelegate = new Object() |
| c.delegate = myDelegate |
| assert c.getDelegate() == myDelegate |
| |
| c.resolveStrategy = Closure.DELEGATE_ONLY |
| assert c.getResolveStrategy() == Closure.DELEGATE_ONLY |
| |
| c.directive = Closure.DONE |
| assert c.directive == Closure.DONE |
| |
| // like in testGetProperties(), don't know how to test metaClass property |
| } |
| |
| /** |
| * GROOVY-2150 ensure list call is available on closure |
| */ |
| void testCallClosureWithlist() { |
| def list = [1, 2] |
| def cl = { a, b -> a + b } |
| assert cl(list) == 3 |
| } |
| |
| /** |
| * GROOVY-4484 ensure variable can be used in assignment inside closure |
| */ |
| void testDeclarationOutsideWithAssignmentInsideAndReferenceInNestedClosure() { |
| assertScript """ |
| class Dummy{} |
| Dummy foo(arg){new Dummy()} |
| |
| def phasePicker |
| def c = { |
| phasePicker = foo(bar: {phasePicker}) |
| } |
| |
| assert c() instanceof Dummy |
| """ |
| } |
| |
| void testIdentity() { |
| assert IDENTITY(42) == 42 |
| assert IDENTITY([42, true, 'foo']) == [42, true, 'foo'] |
| |
| def items = [0, 1, 2, '', 'foo', [], ['bar'], true, false] |
| assert items.grep(IDENTITY) == [1, 2, 'foo', ['bar'], true] |
| assert items.findAll(IDENTITY) == [1, 2, 'foo', ['bar'], true] |
| assert items.grep(IDENTITY).groupBy(IDENTITY) == [1: [1], 2: [2], 'foo': ['foo'], ['bar']: [['bar']], (true): [true]] |
| assert items.collect(IDENTITY) == items |
| |
| def twice = { it + it } |
| def alsoTwice = twice >> IDENTITY |
| assert alsoTwice(6) == 12 |
| def twiceToo = IDENTITY >> twice |
| assert twiceToo(6) == 12 |
| |
| def fortyTwo = IDENTITY.curry(42) |
| assert fortyTwo() == 42 |
| def foo = IDENTITY.rcurry('foo') |
| assert foo() == 'foo' |
| |
| def map = [a: 1, b: 2] |
| assert map.collectEntries(IDENTITY) == map |
| } |
| |
| void testEachWithArray() { |
| def l = [] |
| l << ([1, 2] as Object[]) |
| l.each { |
| assert it == [1, 2] as Object[] |
| } |
| } |
| |
| void testClosureDehydrateAndRehydrate() { |
| def closure = { 'Hello' } |
| assert closure.delegate != null |
| assert closure.owner != null |
| assert closure.thisObject != null |
| assert closure() == 'Hello' |
| |
| def serializable = closure.dehydrate() |
| assert !serializable.is(closure) |
| assert serializable.delegate == null |
| assert serializable.owner == null |
| assert serializable.thisObject == null |
| assert serializable() == 'Hello' |
| |
| def rehydrate = serializable.rehydrate(closure.delegate, closure.owner, closure.thisObject) |
| assert !rehydrate.is(serializable) |
| assert !rehydrate.is(closure) |
| assert rehydrate.delegate.is(closure.delegate) |
| assert rehydrate.owner.is(closure.owner) |
| assert rehydrate.thisObject.is(closure.thisObject) |
| assert rehydrate() == 'Hello' |
| |
| } |
| |
| // GROOVY-5151 |
| void testClosureSerialization() { |
| // without dehydrate, as Controller is not serializable, the serialization will fail |
| shouldFail(NotSerializableException) { |
| assertScript ''' |
| class Controller { // not Serializable |
| def action = { 'Hello' } |
| def action2 = { action() } // call to other closure |
| } |
| def ctrl = new Controller() |
| def bos = new ByteArrayOutputStream() |
| bos.withObjectOutputStream { |
| it << ctrl.action |
| } |
| ''' |
| } |
| |
| // dehydrated action1 should be serializable |
| assertScript ''' |
| class Controller { // not Serializable |
| def action = { 'Hello' } |
| def action2 = { action() } // call to other closure |
| } |
| def ctrl = new Controller() |
| def a1 = ctrl.action.dehydrate() |
| def bos = new ByteArrayOutputStream() |
| bos.withObjectOutputStream { |
| it << a1 |
| } |
| ''' |
| |
| // dehydrated action2 should be serializable |
| assertScript ''' |
| class Controller { // not Serializable |
| def action = { 'Hello' } |
| def action2 = { action() } // call to other closure |
| } |
| def ctrl = new Controller() |
| def a2 = ctrl.action2.dehydrate() |
| def bos = new ByteArrayOutputStream() |
| bos.withObjectOutputStream { |
| it << a2 |
| } |
| ''' |
| |
| // restore action |
| assertScript ''' |
| class Controller { // not Serializable |
| def action = { 'Hello' } |
| def action2 = { action() } // call to other closure |
| } |
| def ctrl = new Controller() |
| def a1 = ctrl.action.dehydrate() |
| def bos = new ByteArrayOutputStream() |
| bos.withObjectOutputStream { |
| it << a1 |
| } |
| byte[] arr = bos.toByteArray() |
| def rehyd |
| new ByteArrayInputStream(arr).withObjectInputStream(this.class.classLoader) { |
| it.eachObject { o -> rehyd = o } |
| } |
| assert rehyd() == 'Hello' |
| ''' |
| |
| // restore action2 |
| assertScript ''' |
| class Controller { // not Serializable |
| def action = { 'Hello' } |
| def action2 = { action() } // call to other closure |
| } |
| def ctrl = new Controller() |
| def a2 = ctrl.action2.dehydrate() |
| def bos = new ByteArrayOutputStream() |
| bos.withObjectOutputStream { |
| it << a2 |
| } |
| byte[] arr = bos.toByteArray() |
| def rehyd |
| new ByteArrayInputStream(arr).withObjectInputStream(this.class.classLoader) { |
| it.eachObject { o -> rehyd = o } |
| } |
| ctrl = new Controller() // assert new instance |
| rehyd = rehyd.rehydrate(ctrl,ctrl,ctrl) |
| assert rehyd() == 'Hello' |
| ''' |
| |
| shouldFail(NotSerializableException) { |
| assertScript ''' |
| class X{} |
| def x = new X () |
| def cl = {x} |
| def dehyd = cl.dehydrate() // true means to dehydrate non serializable fields too |
| def bos = new ByteArrayOutputStream() |
| bos.withObjectOutputStream { |
| it << dehyd |
| } |
| ''' |
| } |
| } |
| |
| // GROOVY-5875 |
| void testStaticInnerClassDelegateFirstAccess() { |
| assertScript ''' |
| class Owner { |
| Object delegate |
| String ownerProp = "owner" |
| |
| void run() { |
| def c = { |
| delegateProp = ownerProp |
| } |
| c.delegate = delegate |
| c.resolveStrategy = Closure.DELEGATE_FIRST |
| c() |
| assert c.delegate.delegateProp == ownerProp |
| } |
| } |
| |
| class Container { |
| static class Delegate { |
| String delegateProp = "delegate" |
| } |
| } |
| |
| def owner = new Owner() |
| owner.delegate = new Container.Delegate() |
| owner.run() |
| ''' |
| } |
| |
| void testStaticInnerClassOwnerFirstAccess() { |
| assertScript ''' |
| class Owner { |
| Object delegate |
| String ownerProp = "owner" |
| |
| void run() { |
| def c = { |
| delegateProp = ownerProp |
| } |
| c.delegate = delegate |
| c.resolveStrategy = Closure.OWNER_FIRST |
| c() |
| assert c.delegate.delegateProp == ownerProp |
| } |
| } |
| |
| class Container { |
| static class Delegate { |
| String delegateProp = "delegate" |
| } |
| } |
| |
| def owner = new Owner() |
| owner.delegate = new Container.Delegate() |
| owner.run() |
| ''' |
| } |
| |
| void testStaticInnerClassOwnerWithPropertyMissingImplementation() { |
| def gcl = new GroovyClassLoader() |
| def msg = shouldFail MultipleCompilationErrorsException, { |
| gcl.parseClass(''' |
| public class ClosureTestA { |
| static class ClosureTestB { |
| def propertyMissing(String myName, Object myValue) { |
| return myValue |
| } |
| |
| def propertyMissing(String myName) { |
| return 42 |
| } |
| |
| def methodMissing(String myName, Object myArgs) { |
| return 42 |
| } |
| } |
| } |
| ''') |
| } |
| |
| assert msg.contains('"methodMissing" implementations are not supported on static inner classes as a synthetic version of "methodMissing" is added during compilation for the purpose of outer class delegation.') |
| assert msg.contains('"propertyMissing" implementations are not supported on static inner classes as a synthetic version of "propertyMissing" is added during compilation for the purpose of outer class delegation.') |
| } |
| |
| void testInnerClassOwnerWithPropertyMissingImplementation() { |
| def gcl = new GroovyClassLoader() |
| gcl.parseClass(''' |
| public class ClosureTestA { |
| class ClosureTestB { |
| def propertyMissing(String myName, Object myValue) { |
| return myValue |
| } |
| |
| def propertyMissing(String myName) { |
| return 42 |
| } |
| |
| def methodMissing(String myName, Object myArgs) { |
| return 42 |
| } |
| } |
| } |
| ''') |
| } |
| |
| void testStaticInnerClassHierarchyWithMethodMissing() { |
| def gcl = new GroovyClassLoader() |
| def msg = shouldFail MultipleCompilationErrorsException, { |
| gcl.parseClass(''' |
| public class ClosureTestA { |
| static class ClosureTestB { |
| def methodMissing(String myName, Object myArgs) { |
| return 42 |
| } |
| } |
| |
| static class ClosureTestB1 extends ClosureTestB { |
| |
| } |
| } |
| ''') |
| } |
| assert msg.contains('"methodMissing" implementations are not supported on static inner classes as a synthetic version of "methodMissing" is added during compilation for the purpose of outer class delegation.') |
| } |
| |
| // GROOVY-6989 |
| void testEachCall() { |
| assertScript ''' |
| Object[] arr = new Object[1] |
| arr[0] = "1" |
| List list = new ArrayList() |
| list.add(arr) |
| |
| list.each { def obj -> |
| assert obj[0] == "1" |
| } |
| |
| list.each { Object[] obj -> |
| assert obj[0] == "1" |
| } |
| ''' |
| } |
| } |
| |
| public class TinyAgent { |
| int x |
| } |