blob: 05fc9bf4d3228c1085e4782621d5736563bb7032 [file] [log] [blame]
import groovy.test.GroovyTestCase
/*
* 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.
*/
class ClosuresSpecTest extends GroovyTestCase {
static void sink(Closure cl) {}
void testClosureSyntax() {
sink
// tag::closure_syntax_1[]
{ item++ } // <1>
// end::closure_syntax_1[]
sink
// tag::closure_syntax_1bis[]
{ -> item++ } // <2>
// end::closure_syntax_1bis[]
sink
// tag::closure_syntax_2[]
{ println it } // <3>
// end::closure_syntax_2[]
sink
// tag::closure_syntax_3[]
{ it -> println it } // <4>
// end::closure_syntax_3[]
sink
// tag::closure_syntax_4[]
{ name -> println name } // <5>
// end::closure_syntax_4[]
sink
// tag::closure_syntax_5[]
{ String x, int y -> // <6>
println "hey ${x} the value is ${y}"
}
// end::closure_syntax_5[]
sink
// tag::closure_syntax_6[]
{ reader -> // <7>
def line = reader.readLine()
line.trim()
}
// end::closure_syntax_6[]
}
void testAssignClosureToAVariable() {
// tag::closure_is_an_instance_of_Closure[]
def listener = { e -> println "Clicked on $e.source" } // <1>
assert listener instanceof Closure
Closure callback = { println 'Done!' } // <2>
Closure<Boolean> isTextFile = {
File it -> it.name.endsWith('.txt') // <3>
}
// end::closure_is_an_instance_of_Closure[]
}
void testCallClosure() {
// tag::closure_call_1[]
def code = { 123 }
// end::closure_call_1[]
// tag::closure_call_1_direct[]
assert code() == 123
// end::closure_call_1_direct[]
// tag::closure_call_1_explicit[]
assert code.call() == 123
// end::closure_call_1_explicit[]
// tag::closure_call_2[]
def isOdd = { int i -> i%2 != 0 } // <1>
assert isOdd(3) == true // <2>
assert isOdd.call(2) == false // <3>
def isEven = { it%2 == 0 } // <4>
assert isEven(3) == false // <5>
assert isEven.call(2) == true // <6>
// end::closure_call_2[]
}
void testClosureParameters() {
// tag::closure_param_declaration[]
def closureWithOneArg = { str -> str.toUpperCase() }
assert closureWithOneArg('groovy') == 'GROOVY'
def closureWithOneArgAndExplicitType = { String str -> str.toUpperCase() }
assert closureWithOneArgAndExplicitType('groovy') == 'GROOVY'
def closureWithTwoArgs = { a,b -> a+b }
assert closureWithTwoArgs(1,2) == 3
def closureWithTwoArgsAndExplicitTypes = { int a, int b -> a+b }
assert closureWithTwoArgsAndExplicitTypes(1,2) == 3
def closureWithTwoArgsAndOptionalTypes = { a, int b -> a+b }
assert closureWithTwoArgsAndOptionalTypes(1,2) == 3
def closureWithTwoArgAndDefaultValue = { int a, int b=2 -> a+b }
assert closureWithTwoArgAndDefaultValue(1) == 3
// end::closure_param_declaration[]
}
void testImplicitIt() {
assertScript '''
// tag::implicit_it[]
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
// end::implicit_it[]
'''
assertScript '''
// tag::implicit_it_equiv[]
def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
// end::implicit_it_equiv[]
'''
assertScript '''
// tag::closure_no_arg_def[]
def magicNumber = { -> 42 }
// end::closure_no_arg_def[]
try {
// tag::closure_no_arg_fail[]
// this call will fail because the closure doesn't accept any argument
magicNumber(11)
// end::closure_no_arg_fail[]
assert false
} catch (MissingMethodException) {
}
'''
}
void testClosureVargs() {
// tag::closure_vargs[]
def concat1 = { String... args -> args.join('') } // <1>
assert concat1('abc','def') == 'abcdef' // <2>
def concat2 = { String[] args -> args.join('') } // <3>
assert concat2('abc', 'def') == 'abcdef'
def multiConcat = { int n, String... args -> // <4>
args.join('')*n
}
assert multiConcat(2, 'abc','def') == 'abcdefabcdef'
// end::closure_vargs[]
}
void testThisObject() {
assertScript '''
// tag::closure_this[]
class Enclosing {
void run() {
def whatIsThisObject = { getThisObject() } // <1>
assert whatIsThisObject() == this // <2>
def whatIsThis = { this } // <3>
assert whatIsThis() == this // <4>
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { this } // <5>
}
void run() {
def inner = new Inner()
assert inner.cl() == inner // <6>
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { this } // <7>
cl()
}
assert nestedClosures() == this // <8>
}
}
// end::closure_this[]
new Enclosing().run()
new EnclosedInInnerClass().run()
new NestedClosures().run()
'''
assertScript '''
// tag::closure_this_call[]
class Person {
String name
int age
String toString() { "$name is $age years old" }
String dump() {
def cl = {
String msg = this.toString() // <1>
println msg
msg
}
cl()
}
}
def p = new Person(name:'Janice', age:74)
assert p.dump() == 'Janice is 74 years old'
// end::closure_this_call[]
'''
}
void testOwner() {
assertScript '''
// tag::closure_owner[]
class Enclosing {
void run() {
def whatIsOwnerMethod = { getOwner() } // <1>
assert whatIsOwnerMethod() == this // <2>
def whatIsOwner = { owner } // <3>
assert whatIsOwner() == this // <4>
}
}
class EnclosedInInnerClass {
class Inner {
Closure cl = { owner } // <5>
}
void run() {
def inner = new Inner()
assert inner.cl() == inner // <6>
}
}
class NestedClosures {
void run() {
def nestedClosures = {
def cl = { owner } // <7>
cl()
}
assert nestedClosures() == nestedClosures // <8>
}
}
// end::closure_owner[]
new Enclosing().run()
new EnclosedInInnerClass().run()
new NestedClosures().run()
'''
}
void testClosureDelegate() {
assertScript '''
// tag::delegate_is_owner[]
class Enclosing {
void run() {
def cl = { getDelegate() } // <1>
def cl2 = { delegate } // <2>
assert cl() == cl2() // <3>
assert cl() == this // <4>
def enclosed = {
{ -> delegate }.call() // <5>
}
assert enclosed() == enclosed // <6>
}
}
// end::delegate_is_owner[]
new Enclosing().run()
'''
assertScript '''
// tag::change_delegate_classes[]
class Person {
String name
}
class Thing {
String name
}
def p = new Person(name: 'Norman')
def t = new Thing(name: 'Teapot')
// end::change_delegate_classes[]
// tag::change_delegate_closure[]
def upperCasedName = { delegate.name.toUpperCase() }
// end::change_delegate_closure[]
// tag::change_delegate_asserts[]
upperCasedName.delegate = p
assert upperCasedName() == 'NORMAN'
upperCasedName.delegate = t
assert upperCasedName() == 'TEAPOT'
// end::change_delegate_asserts[]
// tag::delegate_alernative[]
def target = p
def upperCasedNameUsingVar = { target.name.toUpperCase() }
assert upperCasedNameUsingVar() == 'NORMAN'
// end::delegate_alernative[]
'''
}
void testDelegationStrategy() {
assertScript '''
// tag::delegation_strategy_intro[]
class Person {
String name
}
def p = new Person(name:'Igor')
def cl = { name.toUpperCase() } // <1>
cl.delegate = p // <2>
assert cl() == 'IGOR' // <3>
// end::delegation_strategy_intro[]
'''
}
void testOwnerFirst() {
assertScript '''
// tag::closure_owner_first[]
class Person {
String name
def pretty = { "My name is $name" } // <1>
String toString() {
pretty()
}
}
class Thing {
String name // <2>
}
def p = new Person(name: 'Sarah')
def t = new Thing(name: 'Teapot')
assert p.toString() == 'My name is Sarah' // <3>
p.pretty.delegate = t // <4>
assert p.toString() == 'My name is Sarah' // <5>
// end::closure_owner_first[]
// tag::closure_delegate_first[]
p.pretty.resolveStrategy = Closure.DELEGATE_FIRST
assert p.toString() == 'My name is Teapot'
// end::closure_delegate_first[]
'''
}
void testDelegateOnly() {
assertScript '''
// tag::delegate_only[]
class Person {
String name
int age
def fetchAge = { age }
}
class Thing {
String name
}
def p = new Person(name:'Jessica', age:42)
def t = new Thing(name:'Printer')
def cl = p.fetchAge
cl.delegate = p
assert cl() == 42
cl.delegate = t
assert cl() == 42
cl.resolveStrategy = Closure.DELEGATE_ONLY
cl.delegate = p
assert cl() == 42
cl.delegate = t
try {
cl()
assert false
} catch (MissingPropertyException ex) {
// "age" is not defined on the delegate
}
// end::delegate_only[]
'''
}
void testGStringEager() {
// tag::gstring_eager_intro[]
def x = 1
def gs = "x = ${x}"
assert gs == 'x = 1'
// end::gstring_eager_intro[]
/* do not uncomment, this is used in documentation!
// tag::gstring_eager_outro[]
x = 2
assert gs == 'x = 2'
// end::gstring_eager_outro[]
*/
x = 2
assert gs == 'x = 1'
}
void testGStringLazy() {
// tag::gstring_lazy[]
def x = 1
def gs = "x = ${-> x}"
assert gs == 'x = 1'
x = 2
assert gs == 'x = 2'
// end::gstring_lazy[]
}
void testGStringWithMutation() {
assertScript '''
// tag::gstring_mutation[]
class Person {
String name
String toString() { name } // <1>
}
def sam = new Person(name:'Sam') // <2>
def lucy = new Person(name:'Lucy') // <3>
def p = sam // <4>
def gs = "Name: ${p}" // <5>
assert gs == 'Name: Sam' // <6>
p = lucy // <7>
assert gs == 'Name: Sam' // <8>
sam.name = 'Lucy' // <9>
assert gs == 'Name: Lucy' // <10>
// end::gstring_mutation[]
'''
}
void testGStringWithoutMutation() {
assertScript '''
// tag::gstring_no_mutation[]
class Person {
String name
String toString() { name }
}
def sam = new Person(name:'Sam')
def lucy = new Person(name:'Lucy')
def p = sam
// Create a GString with lazy evaluation of "p"
def gs = "Name: ${-> p}"
assert gs == 'Name: Sam'
p = lucy
assert gs == 'Name: Lucy'
// end::gstring_no_mutation[]
'''
}
void testLeftCurry() {
// tag::left_curry[]
def nCopies = { int n, String str -> str*n } // <1>
def twice = nCopies.curry(2) // <2>
assert twice('bla') == 'blabla' // <3>
assert twice('bla') == nCopies(2, 'bla') // <4>
// end::left_curry[]
}
void testRightCurry() {
// tag::right_curry[]
def nCopies = { int n, String str -> str*n } // <1>
def blah = nCopies.rcurry('bla') // <2>
assert blah(2) == 'blabla' // <3>
assert blah(2) == nCopies(2, 'bla') // <4>
// end::right_curry[]
}
void testNCurry() {
// tag::ncurry[]
def volume = { double l, double w, double h -> l*w*h } // <1>
def fixedWidthVolume = volume.ncurry(1, 2d) // <2>
assert volume(3d, 2d, 4d) == fixedWidthVolume(3d, 4d) // <3>
def fixedWidthAndHeight = volume.ncurry(1, 2d, 4d) // <4>
assert volume(3d, 2d, 4d) == fixedWidthAndHeight(3d) // <5>
// end::ncurry[]
}
void testMemoize() {
// tag::naive_fib[]
def fib
fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }
assert fib(15) == 610 // slow!
// end::naive_fib[]
// tag::memoized_fib[]
fib = { long n -> n<2?n:fib(n-1)+fib(n-2) }.memoize()
assert fib(25) == 75025 // fast!
// end::memoized_fib[]
}
void testComposition() {
// tag::closure_composition[]
def plus2 = { it + 2 }
def times3 = { it * 3 }
def times3plus2 = plus2 << times3
assert times3plus2(3) == 11
assert times3plus2(4) == plus2(times3(4))
def plus2times3 = times3 << plus2
assert plus2times3(3) == 15
assert plus2times3(5) == times3(plus2(5))
// reverse composition
assert times3plus2(3) == (times3 >> plus2)(3)
// end::closure_composition[]
}
void testTrampoline() {
// tag::trampoline[]
def factorial
factorial = { int n, def accu = 1G ->
if (n < 2) return accu
factorial.trampoline(n - 1, n * accu)
}
factorial = factorial.trampoline()
assert factorial(1) == 1
assert factorial(3) == 1 * 2 * 3
assert factorial(1000) // == 402387260.. plus another 2560 digits
// end::trampoline[]
}
}