blob: dc887efc687a7505d43ce9928c97b4717d05a829 [file] [log] [blame]
/*
* 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.
*/
import groovy.test.GroovyTestCase
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
/**
* Specification tests for the traits feature
*/
class TraitsSpecificationTest extends GroovyTestCase {
void testTraitDeclaration() {
assertScript '''// tag::flying_simple[]
trait FlyingAbility { // <1>
String fly() { "I'm flying!" } // <2>
}
// end::flying_simple[]
// tag::bird[]
class Bird implements FlyingAbility {} // <1>
def b = new Bird() // <2>
assert b.fly() == "I'm flying!" // <3>
// end::bird[]
'''
}
void testAbstractMethodInTrait() {
assertScript '''// tag::greetable[]
trait Greetable {
abstract String name() // <1>
String greeting() { "Hello, ${name()}!" } // <2>
}
// end::greetable[]
// tag::greetable_person[]
class Person implements Greetable { // <1>
String name() { 'Bob' } // <2>
}
def p = new Person()
assert p.greeting() == 'Hello, Bob!' // <3>
// end::greetable_person[]
'''
}
void testTraitImplementingInterface() {
assertScript '''// tag::trait_implementing_interface[]
interface Named { // <1>
String name()
}
trait Greetable implements Named { // <2>
String greeting() { "Hello, ${name()}!" }
}
class Person implements Greetable { // <3>
String name() { 'Bob' } // <4>
}
def p = new Person()
assert p.greeting() == 'Hello, Bob!' // <5>
assert p instanceof Named // <6>
assert p instanceof Greetable // <7>
// end::trait_implementing_interface[]
'''
}
void testTraitWithProperty() {
assertScript '''// tag::trait_with_property[]
trait Named {
String name // <1>
}
class Person implements Named {} // <2>
def p = new Person(name: 'Bob') // <3>
assert p.name == 'Bob' // <4>
assert p.getName() == 'Bob' // <5>
// end::trait_with_property[]
'''
}
void testCompositionOfTraits() {
assertScript '''trait FlyingAbility {
String fly() { "I'm flying!" }
}
// tag::speaking_simple[]
trait SpeakingAbility {
String speak() { "I'm speaking!" }
}
// end::speaking_simple[]
// tag::speakingduck[]
class Duck implements FlyingAbility, SpeakingAbility {} // <1>
def d = new Duck() // <2>
assert d.fly() == "I'm flying!" // <3>
assert d.speak() == "I'm speaking!" // <4>
// end::speakingduck[]
'''
}
void testOverridableMethod() {
assertScript '''trait FlyingAbility {
String fly() { "I'm flying!" }
}
trait SpeakingAbility {
String speak() { "I'm speaking!" }
}
// tag::quackingduck[]
class Duck implements FlyingAbility, SpeakingAbility {
String quack() { "Quack!" } // <1>
String speak() { quack() } // <2>
}
def d = new Duck()
assert d.fly() == "I'm flying!" // <3>
assert d.quack() == "Quack!" // <4>
assert d.speak() == "Quack!" // <5>
// end::quackingduck[]
'''
}
void testPrivateMethodInTrait() {
assertScript '''// tag::private_method_in_trait[]
trait Greeter {
private String greetingMessage() { // <1>
'Hello from a private method!'
}
String greet() {
def m = greetingMessage() // <2>
println m
m
}
}
class GreetingMachine implements Greeter {} // <3>
def g = new GreetingMachine()
assert g.greet() == "Hello from a private method!" // <4>
try {
assert g.greetingMessage() // <5>
} catch (MissingMethodException e) {
println "greetingMessage is private in trait"
}
// end::private_method_in_trait[]
'''
}
void testTraitWithPrivateField() {
assertScript '''// tag::trait_with_private_field[]
trait Counter {
private int count = 0 // <1>
int count() { count += 1; count } // <2>
}
class Foo implements Counter {} // <3>
def f = new Foo()
assert f.count() == 1 // <4>
assert f.count() == 2
// end::trait_with_private_field[]
'''
}
void testTraitWithPublicField() {
assertScript '''// tag::trait_with_public_field[]
trait Named {
public String name // <1>
}
class Person implements Named {} // <2>
def p = new Person() // <3>
p.Named__name = 'Bob' // <4>
// end::trait_with_public_field[]
'''
}
void testRemappedName() {
def clazz = new ClassNode("my.package.Foo", 0, ClassHelper.OBJECT_TYPE)
assert org.codehaus.groovy.transform.trait.Traits.remappedFieldName(clazz, "bar") == 'my_package_Foo__bar'
}
void testDuckTyping() {
assertScript '''// tag::ducktyping[]
trait SpeakingDuck {
String speak() { quack() } // <1>
}
class Duck implements SpeakingDuck {
String methodMissing(String name, args) {
"${name.capitalize()}!" // <2>
}
}
def d = new Duck()
assert d.speak() == 'Quack!' // <3>
// end::ducktyping[]
'''
}
void testTraitInheritance() {
assertScript '''// tag::trait_inherit[]
trait Named {
String name // <1>
}
trait Polite extends Named { // <2>
String introduce() { "Hello, I am $name" } // <3>
}
class Person implements Polite {}
def p = new Person(name: 'Alice') // <4>
assert p.introduce() == 'Hello, I am Alice' // <5>
// end::trait_inherit[]
'''
}
void testMultipleTraitInheritance() {
assertScript '''// tag::trait_multiple_inherit[]
trait WithId { // <1>
Long id
}
trait WithName { // <2>
String name
}
trait Identified implements WithId, WithName {} // <3>
// end::trait_multiple_inherit[]
class Bean implements Identified {}
def b = new Bean(id: 123, name: 'Foo')
'''
}
void testMethodMissingInTrait() {
assertScript '''// tag::dynamicobject[]
trait DynamicObject { // <1>
private Map props = [:]
def methodMissing(String name, args) {
name.toUpperCase()
}
def propertyMissing(String prop) {
props[prop]
}
void setProperty(String prop, Object value) {
props[prop] = value
}
}
class Dynamic implements DynamicObject {
String existingProperty = 'ok' // <2>
String existingMethod() { 'ok' } // <3>
}
def d = new Dynamic()
assert d.existingProperty == 'ok' // <4>
assert d.foo == null // <5>
d.foo = 'bar' // <6>
assert d.foo == 'bar' // <7>
assert d.existingMethod() == 'ok' // <8>
assert d.someMethod() == 'SOMEMETHOD' // <9>
// end::dynamicobject[]
'''
}
void testDefaultMultipleInheritanceResolution() {
assertScript '''// tag::multiple_inherit_default[]
trait A {
String exec() { 'A' } // <1>
}
trait B {
String exec() { 'B' } // <2>
}
class C implements A,B {} // <3>
// end::multiple_inherit_default[]
// tag::multiple_inherit_default_assert[]
def c = new C()
assert c.exec() == 'B'
// end::multiple_inherit_default_assert[]
'''
}
void testUserMultipleInheritanceResolution() {
assertScript '''trait A {
String exec() { 'A' }
}
trait B {
String exec() { 'B' }
}
// tag::multiple_inherit_user[]
class C implements A,B {
String exec() { A.super.exec() } // <1>
}
def c = new C()
assert c.exec() == 'A' // <2>
// end::multiple_inherit_user[]
'''
}
void testRuntimeCoercion() {
assertScript '''
// tag::runtime_header[]
trait Extra {
String extra() { "I'm an extra method" } // <1>
}
class Something { // <2>
String doSomething() { 'Something' } // <3>
}
// end::runtime_header[]
try {
// tag::runtime_fail[]
def s = new Something()
s.extra()
// end::runtime_fail[]
} catch (MissingMethodException e) {}
// tag::runtime_success[]
def s = new Something() as Extra // <1>
s.extra() // <2>
s.doSomething() // <3>
// end::runtime_success[]
'''
}
void testWithTraits() {
assertScript '''// tag::withtraits_header[]
trait A { void methodFromA() {} }
trait B { void methodFromB() {} }
class C {}
def c = new C()
// end::withtraits_header[]
try {
// tag::withtraits_fail[]
c.methodFromA() // <1>
c.methodFromB() // <2>
// end::withtraits_fail[]
} catch (MissingMethodException e) {}
// tag::withtraits_success[]
def d = c.withTraits A, B // <3>
d.methodFromA() // <4>
d.methodFromB() // <5>
// end::withtraits_success[]
'''
}
void testSAMCoercion() {
assertScript '''import org.codehaus.groovy.runtime.Greeter
// tag::sam_trait[]
trait Greeter {
String greet() { "Hello $name" } // <1>
abstract String getName() // <2>
}
// end::sam_trait[]
// tag::sam_trait_assignment[]
Greeter greeter = { 'Alice' } // <1>
// end::sam_trait_assignment[]
// tag::sam_trait_method[]
void greet(Greeter g) { println g.greet() } // <1>
greet { 'Alice' } // <2>
// end::sam_trait_method[]
'''
}
void testTraitOverrideBehavior() {
assertScript '''
// tag::forceoverride_header[]
import groovy.test.GroovyTestCase
import groovy.transform.CompileStatic
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer
import org.codehaus.groovy.control.customizers.ImportCustomizer
class SomeTest extends GroovyTestCase {
def config
def shell
void setup() {
config = new CompilerConfiguration()
shell = new GroovyShell(config)
}
void testSomething() {
assert shell.evaluate('1+1') == 2
}
void otherTest() { /* ... */ }
}
// end::forceoverride_header[]
class SomeTest2 extends SomeTest {}
/*
// tag::forceoverride_extends[]
class AnotherTest extends SomeTest {
void setup() {
config = new CompilerConfiguration()
config.addCompilationCustomizers( ... )
shell = new GroovyShell(config)
}
}
// end::forceoverride_extends[]
// tag::forceoverride_extends2[]
class YetAnotherTest extends SomeTest {
void setup() {
config = new CompilerConfiguration()
config.addCompilationCustomizers( ... )
shell = new GroovyShell(config)
}
}
// end::forceoverride_extends2[]
*/
// tag::forceoverride_trait[]
trait MyTestSupport {
void setup() {
config = new CompilerConfiguration()
config.addCompilationCustomizers( new ASTTransformationCustomizer(CompileStatic) )
shell = new GroovyShell(config)
}
}
// end::forceoverride_trait[]
// tag::forceoverride_miss_header[]
class AnotherTest extends SomeTest implements MyTestSupport {}
class YetAnotherTest extends SomeTest2 implements MyTestSupport {}
// end::forceoverride_miss_header[]
def t = new AnotherTest()
t.setup()
assert !t.config.compilationCustomizers.empty
'''
}
void testRuntimeOverride() {
assertScript '''// tag::runtime_forceoverride[]
class Person {
String name // <1>
}
trait Bob {
String getName() { 'Bob' } // <2>
}
def p = new Person(name: 'Alice')
assert p.name == 'Alice' // <3>
def p2 = p as Bob // <4>
assert p2.name == 'Bob' // <5>
// end::runtime_forceoverride[]
'''
}
void testDifferenceWithMixin() {
assertScript '''// tag::diff_mixin[]
class A { String methodFromA() { 'A' } } // <1>
class B { String methodFromB() { 'B' } } // <2>
A.metaClass.mixin B // <3>
def o = new A()
assert o.methodFromA() == 'A' // <4>
assert o.methodFromB() == 'B' // <5>
assert o instanceof A // <6>
assert !(o instanceof B) // <7>
// end::diff_mixin[]
'''
}
void testMeaningOfThis() {
assertScript '''// tag::meaningofthis_header[]
trait Introspector {
def whoAmI() { this }
}
class Foo implements Introspector {}
def foo = new Foo()
// end::meaningofthis_header[]
// tag::meaningofthis_snippet[]
foo.whoAmI()
// end::meaningofthis_snippet[]
// tag::meaningofthis_assert[]
assert foo.whoAmI().is(foo)
// end::meaningofthis_assert[]
'''
}
void testPublicStaticFieldInTrait() {
assertScript '''// tag::staticfield_header[]
trait TestHelper {
public static boolean CALLED = false // <1>
static void init() { // <2>
CALLED = true // <3>
}
}
class Foo implements TestHelper {}
Foo.init() // <4>
assert Foo.TestHelper__CALLED // <5>
// end::staticfield_header[]
try {
// tag::staticfield_notontrait[]
Foo.CALLED = true
// end::staticfield_notontrait[]
} catch (Exception e){}
// tag::staticfield_distinct[]
class Bar implements TestHelper {} // <1>
class Baz implements TestHelper {} // <2>
Bar.init() // <3>
assert Bar.TestHelper__CALLED // <4>
assert !Baz.TestHelper__CALLED // <5>
// end::staticfield_distinct[]
'''
}
void testPrePostfixIsDisallowed() {
def message = shouldFail '''
// tag::prefix_postfix[]
trait Counting {
int x
void inc() {
x++ // <1>
}
void dec() {
--x // <2>
}
}
class Counter implements Counting {}
def c = new Counter()
c.inc()
// end::prefix_postfix[]
'''
assert message.contains('Postfix expressions on trait fields/properties are not supported')
assert message.contains('Prefix expressions on trait fields/properties are not supported')
}
void testStackableTraits() {
assertScript '''import TraitsSpecificationTest.PrintCategory as PC
String filterTags(String src) {
StringBuilder out = new StringBuilder()
src.eachLine {
if (!it.startsWith('//')) {
if (out) { out.append('\\n')}
out.append(it)
}
}
out
}
// tag::messagehandler[]
interface MessageHandler {
void on(String message, Map payload)
}
// end::messagehandler[]
// tag::defaulthandler[]
trait DefaultHandler implements MessageHandler {
void on(String message, Map payload) {
println "Received $message with payload $payload"
}
}
// end::defaulthandler[]
// tag::logginghandler[]
trait LoggingHandler implements MessageHandler { // <1>
void on(String message, Map payload) {
println "Seeing $message with payload $payload" // <2>
super.on(message, payload) // <3>
}
}
// end::logginghandler[]
// tag::handlerwithlogger[]
class HandlerWithLogger implements DefaultHandler, LoggingHandler {}
// end::handlerwithlogger[]
use(PC) {
// tag::handlerwithlogger_assert[]
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('test logging', [:])
// end::handlerwithlogger_assert[]
def expect = """// tag::handlerwithlogger_assert_output[]
Seeing test logging with payload [:]
Received test logging with payload [:]
// end::handlerwithlogger_assert_output[]
"""
assert PC.BUFFER.toString() == filterTags(expect)
PC.reset()
}
// tag::sayhandler[]
trait SayHandler implements MessageHandler {
void on(String message, Map payload) {
if (message.startsWith("say")) { // <1>
println "I say ${message - 'say'}!"
} else {
super.on(message, payload) // <2>
}
}
}
// end::sayhandler[]
// tag::simplehandler[]
class SimpleHandler implements DefaultHandler {}
// end::simplehandler[]
// tag::simplehandlerwithlogging[]
class SimpleHandlerWithLogging implements DefaultHandler {
void on(String message, Map payload) { // <1>
println "Seeing $message with payload $payload" // <2>
DefaultHandler.super.on(message, payload) // <3>
}
}
// end::simplehandlerwithlogging[]
// tag::implementinghandler[]
class Handler implements DefaultHandler, SayHandler, LoggingHandler {}
// end::implementinghandler[]
use (PC) {
// tag::implementinghandler_assert[]
def h = new Handler()
h.on('foo', [:])
h.on('sayHello', [:])
// end::implementinghandler_assert[]
def expect = """// tag::implementinghandler_output[]
Seeing foo with payload [:]
Received foo with payload [:]
Seeing sayHello with payload [:]
I say Hello!
// end::implementinghandler_output[]
"""
assert PC.BUFFER.toString() == filterTags(expect)
PC.reset()
}
// tag::alternatehandler[]
class AlternateHandler implements DefaultHandler, LoggingHandler, SayHandler {}
// end::alternatehandler[]
use (PC) {
// tag::alternatehandler_assert[]
h = new AlternateHandler()
h.on('foo', [:])
h.on('sayHello', [:])
// end::alternatehandler_assert[]
def expect = """// tag::alternatehandler_output[]
Seeing foo with payload [:]
Received foo with payload [:]
I say Hello!
// end::alternatehandler_output[]
"""
assert PC.BUFFER.toString() == filterTags(expect)
PC.reset()
PC.reset()
}
'''
}
void testDecorateFinalClass() {
assertScript '''
// tag::decoratefinalclass[]
trait Filtering { // <1>
StringBuilder append(String str) { // <2>
def subst = str.replace('o','') // <3>
super.append(subst) // <4>
}
String toString() { super.toString() } // <5>
}
def sb = new StringBuilder().withTraits Filtering // <6>
sb.append('Groovy')
assert sb.toString() == 'Grvy' // <7>
// end::decoratefinalclass[]
'''
}
void testInheritanceOfState() {
assertScript '''// tag::intcouple[]
trait IntCouple {
int x = 1
int y = 2
int sum() { x+y }
}
// end::intcouple[]
// tag::intcouple_impl[]
class BaseElem implements IntCouple {
int f() { sum() }
}
def base = new BaseElem()
assert base.f() == 3
// end::intcouple_impl[]
// tag::intcouple_impl_override[]
class Elem implements IntCouple {
int x = 3 // <1>
int y = 4 // <2>
int f() { sum() } // <3>
}
def elem = new Elem()
// end::intcouple_impl_override[]
// tag::intcouple_impl_override_assert[]
assert elem.f() == 3
// end::intcouple_impl_override_assert[]
'''
assertScript '''
// tag::intcouple_impl_override_directgetter[]
trait IntCouple {
int x = 1
int y = 2
int sum() { getX()+getY() }
}
class Elem implements IntCouple {
int x = 3
int y = 4
int f() { sum() }
}
def elem = new Elem()
assert elem.f() == 7
// end::intcouple_impl_override_directgetter[]
'''
}
void testSelfType() {
assertScript '''
// tag::selftype_intro[]
class CommunicationService {
static void sendMessage(String from, String to, String message) { // <1>
println "$from sent [$message] to $to"
}
}
class Device { String id } // <2>
trait Communicating {
void sendMessage(Device to, String message) {
CommunicationService.sendMessage(id, to.id, message) // <3>
}
}
class MyDevice extends Device implements Communicating {} // <4>
def bob = new MyDevice(id:'Bob')
def alice = new MyDevice(id:'Alice')
bob.sendMessage(alice,'secret') // <5>
// end::selftype_intro[]
// tag::selftype_securityservice[]
class SecurityService {
static void check(Device d) { if (d.id==null) throw new SecurityException() }
}
// end::selftype_securityservice[]
'''
assertScript '''import groovy.transform.SelfType
import groovy.transform.CompileStatic
class CommunicationService {
static void sendMessage(String from, String to, String message) {
println "$from sent [$message] to $to"
}
}
class Device { String id }
// tag::selftype_fixed[]
@SelfType(Device)
@CompileStatic
trait Communicating {
void sendMessage(Device to, String message) {
SecurityService.check(this)
CommunicationService.sendMessage(id, to.id, message)
}
}
// end::selftype_fixed[]
class MyDevice extends Device implements Communicating {}
def bob = new MyDevice(id:'Bob')
def alice = new MyDevice(id:'Alice')
bob.sendMessage(alice,'secret')
class SecurityService {
static void check(Device d) { if (d.id==null) throw new SecurityException() }
}
'''
def message = shouldFail '''import groovy.transform.SelfType
import groovy.transform.CompileStatic
class CommunicationService {
static void sendMessage(String from, String to, String message) {
println "$from sent [$message] to $to"
}
}
class Device { String id }
@SelfType(Device)
@CompileStatic
trait Communicating {
void sendMessage(Device to, String message) {
SecurityService.check(this)
CommunicationService.sendMessage(id, to.id, message)
}
}
// tag::selftype_compiletimeerror[]
class MyDevice implements Communicating {} // forgot to extend Device
// end::selftype_compiletimeerror[]
class SecurityService {
static void check(Device d) { if (d.id==null) throw new SecurityException() }
}
'''
assert message.contains("class 'MyDevice' implements trait 'Communicating' but does not extend self type class 'Device'")
}
static class PrintCategory {
static StringBuilder BUFFER = new StringBuilder()
static void println(Object self, String message) {
if (BUFFER.length()>0) BUFFER.append('\n')
BUFFER.append(message)
}
static void reset() { BUFFER.setLength(0) }
}
}