| /* |
| * 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. |
| */ |
| |
| |
| /** |
| * Specification tests for records |
| */ |
| |
| import org.junit.jupiter.api.Test |
| |
| import static groovy.test.GroovyAssert.assertScript |
| |
| class RecordSpecificationTest { |
| |
| @Test |
| void testSimpleRecordKeyword() { |
| assertScript ''' |
| // tag::record_message_defn[] |
| record Message(String from, String to, String body) { } |
| // end::record_message_defn[] |
| // tag::record_message_usage[] |
| def msg = new Message('me@myhost.com', 'you@yourhost.net', 'Hello!') |
| assert msg.toString() == 'Message[from=me@myhost.com, to=you@yourhost.net, body=Hello!]' |
| // end::record_message_usage[] |
| ''' |
| def equiv = ''' |
| // tag::record_message_equivalent[] |
| final class Message extends Record { |
| private final String from |
| private final String to |
| private final String body |
| private static final long serialVersionUID = 0 |
| |
| /* constructor(s) */ |
| |
| final String toString() { /*...*/ } |
| |
| final boolean equals(Object other) { /*...*/ } |
| |
| final int hashCode() { /*...*/ } |
| |
| String from() { from } |
| // other getters ... |
| } |
| // end::record_message_equivalent[] |
| ''' |
| } |
| |
| @Test |
| void testRecordDefaultsAndNamedArguments() { |
| assertScript ''' |
| import groovy.transform.* |
| import static groovy.test.GroovyAssert.shouldFail |
| |
| // tag::record_point_defn[] |
| record ColoredPoint(int x, int y = 0, String color = 'white') {} |
| // end::record_point_defn[] |
| |
| // tag::record_point_defaults[] |
| assert new ColoredPoint(5, 5, 'black').toString() == 'ColoredPoint[x=5, y=5, color=black]' |
| assert new ColoredPoint(5, 5).toString() == 'ColoredPoint[x=5, y=5, color=white]' |
| assert new ColoredPoint(5).toString() == 'ColoredPoint[x=5, y=0, color=white]' |
| // end::record_point_defaults[] |
| |
| // tag::record_point_named_args[] |
| assert new ColoredPoint(x: 5).toString() == 'ColoredPoint[x=5, y=0, color=white]' |
| assert new ColoredPoint(x: 0, y: 5).toString() == 'ColoredPoint[x=0, y=5, color=white]' |
| // end::record_point_named_args[] |
| def ex = shouldFail(ClassCastException) { new ColoredPoint(x: 0, y: null) } |
| assert ex.message.contains("Cannot cast 'null' to class 'int'") |
| ex = shouldFail(ClassCastException) { new ColoredPoint(x: null) } |
| assert ex.message.contains("Cannot cast 'null' to class 'int'") |
| ex = shouldFail { new ColoredPoint(x: 0, z: 5) } |
| assert ex.message.contains('Unrecognized namedArgKey: z') |
| // tag::record_point_named_args_off[] |
| @TupleConstructor(defaultsMode=DefaultsMode.OFF) |
| record ColoredPoint2(int x, int y, String color) {} |
| assert new ColoredPoint2(4, 5, 'red').toString() == 'ColoredPoint2[x=4, y=5, color=red]' |
| // end::record_point_named_args_off[] |
| // tag::record_point_named_args_on[] |
| @TupleConstructor(defaultsMode=DefaultsMode.ON) |
| record ColoredPoint3(int x, int y = 0, String color = 'white') {} |
| assert new ColoredPoint3(y: 5).toString() == 'ColoredPoint3[x=0, y=5, color=white]' |
| // end::record_point_named_args_on[] |
| ''' |
| } |
| |
| @Test |
| void testOverrideToString() { |
| assertScript ''' |
| // tag::record_point3d[] |
| record Point3D(int x, int y, int z) { |
| String toString() { |
| "Point3D[coords=$x,$y,$z]" |
| } |
| } |
| |
| assert new Point3D(10, 20, 30).toString() == 'Point3D[coords=10,20,30]' |
| // end::record_point3d[] |
| ''' |
| } |
| |
| @Test |
| void testCopyWith() { |
| assertScript ''' |
| import groovy.transform.RecordOptions |
| // tag::record_copywith[] |
| @RecordOptions(copyWith=true) |
| record Fruit(String name, double price) {} |
| def apple = new Fruit('Apple', 11.6) |
| assert 'Apple' == apple.name() |
| assert 11.6 == apple.price() |
| |
| def orange = apple.copyWith(name: 'Orange') |
| assert orange.toString() == 'Fruit[name=Orange, price=11.6]' |
| // end::record_copywith[] |
| ''' |
| assertScript ''' |
| import groovy.transform.RecordOptions |
| import static groovy.test.GroovyAssert.shouldFail |
| |
| @RecordOptions(copyWith=false) |
| record Fruit(String name, double price) {} |
| def apple = new Fruit('Apple', 11.6) |
| shouldFail(MissingMethodException) { |
| apple.copyWith(name: 'Orange') |
| } |
| ''' |
| } |
| |
| @Test |
| void testImmutable() { |
| assertScript ''' |
| import groovy.transform.ImmutableProperties |
| // tag::record_immutable[] |
| @ImmutableProperties |
| record Shopping(List items) {} |
| |
| def items = ['bread', 'milk'] |
| def shop = new Shopping(items) |
| items << 'chocolate' |
| assert shop.items() == ['bread', 'milk'] |
| // end::record_immutable[] |
| ''' |
| } |
| |
| @Test |
| void testToList() { |
| assertScript ''' |
| // tag::record_to_list[] |
| record Point(int x, int y, String color) { } |
| |
| def p = new Point(100, 200, 'green') |
| def (x, y, c) = p.toList() |
| assert x == 100 |
| assert y == 200 |
| assert c == 'green' |
| // end::record_to_list[] |
| ''' |
| } |
| |
| @Test |
| void testToMap() { |
| assertScript ''' |
| // tag::record_to_map[] |
| record Point(int x, int y, String color) { } |
| |
| def p = new Point(100, 200, 'green') |
| assert p.toMap() == [x: 100, y: 200, color: 'green'] |
| // end::record_to_map[] |
| ''' |
| } |
| |
| @Test |
| void testSize() { |
| assertScript ''' |
| // tag::record_size[] |
| record Point(int x, int y, String color) { } |
| |
| def p = new Point(100, 200, 'green') |
| assert p.size() == 3 |
| // end::record_size[] |
| ''' |
| } |
| |
| @Test |
| void testGetAt() { |
| assertScript ''' |
| // tag::record_get_at[] |
| record Point(int x, int y, String color) { } |
| |
| def p = new Point(100, 200, 'green') |
| assert p[1] == 200 |
| // end::record_get_at[] |
| ''' |
| } |
| |
| @Test |
| void testGenerics() { |
| assertScript ''' |
| import groovy.transform.CompileStatic |
| |
| // tag::record_generics_defn[] |
| record Coord<T extends Number>(T v1, T v2){ |
| double distFromOrigin() { Math.sqrt(v1()**2 + v2()**2 as double) } |
| } |
| // end::record_generics_defn[] |
| |
| @groovy.transform.CompileStatic def method() { |
| // tag::record_generics_usage[] |
| def r1 = new Coord<Integer>(3, 4) |
| assert r1.distFromOrigin() == 5 |
| def r2 = new Coord<Double>(6d, 2.5d) |
| assert r2.distFromOrigin() == 6.5d |
| // end::record_generics_usage[] |
| } |
| method() |
| ''' |
| } |
| |
| @Test |
| void testComponents() { |
| assert ''' |
| // tag::record_components[] |
| import groovy.transform.* |
| |
| @RecordOptions(components=true) |
| record Point(int x, int y, String color) { } |
| |
| @CompileStatic |
| def method() { |
| def p1 = new Point(100, 200, 'green') |
| def (int x1, int y1, String c1) = p1.components() |
| assert x1 == 100 |
| assert y1 == 200 |
| assert c1 == 'green' |
| |
| def p2 = new Point(10, 20, 'blue') |
| def (x2, y2, c2) = p2.components() |
| assert x2 * 10 == 100 |
| assert y2 ** 2 == 400 |
| assert c2.toUpperCase() == 'BLUE' |
| |
| def p3 = new Point(1, 2, 'red') |
| assert p3.components() instanceof Tuple3 |
| } |
| |
| method() |
| // end::record_components[] |
| ''' |
| } |
| |
| @Test |
| void testRecordCompactConstructor() { |
| assertScript ''' |
| // tag::record_compact_constructor[] |
| public record Warning(String message) { |
| public Warning { |
| Objects.requireNonNull(message) |
| message = message.toUpperCase() |
| } |
| } |
| |
| def w = new Warning('Help') |
| assert w.message() == 'HELP' |
| // end::record_compact_constructor[] |
| ''' |
| } |
| |
| @Test |
| void testToStringAnnotation() { |
| assertScript ''' |
| // tag::record_point3d_tostring_annotation[] |
| package threed |
| |
| import groovy.transform.ToString |
| |
| @ToString(ignoreNulls=true, cache=true, includeNames=true, |
| leftDelimiter='[', rightDelimiter=']', nameValueSeparator='=') |
| record Point(Integer x, Integer y, Integer z=null) { } |
| |
| assert new Point(10, 20).toString() == 'threed.Point[x=10, y=20]' |
| // end::record_point3d_tostring_annotation[] |
| ''' |
| assertScript ''' |
| // tag::record_point2d_tostring_annotation[] |
| package twod |
| |
| import groovy.transform.ToString |
| |
| @ToString(ignoreNulls=true, cache=true, includeNames=true, |
| leftDelimiter='[', rightDelimiter=']', nameValueSeparator='=') |
| record Point(Integer x, Integer y) { } |
| |
| assert new Point(10, 20).toString() == 'twod.Point[x=10, y=20]' |
| // end::record_point2d_tostring_annotation[] |
| ''' |
| } |
| |
| @Test |
| void testSimpleRecordAnnotation() { |
| assertScript ''' |
| import groovy.transform.RecordType |
| // tag::record_message_annotation_defn[] |
| @RecordType |
| class Message { |
| String from |
| String to |
| String body |
| } |
| // end::record_message_annotation_defn[] |
| def msg = new Message('me@myhost.com', 'you@yourhost.net', 'Hello!') |
| assert msg.toString() == 'Message[from=me@myhost.com, to=you@yourhost.net, body=Hello!]' |
| ''' |
| } |
| } |