From 7d90a4c0ce77847c4b874cff9044c0b6b338eaa2 Mon Sep 17 00:00:00 2001 From: Thomas Heigl Date: Tue, 15 Nov 2022 19:05:27 +0100 Subject: [PATCH] Improve tests for JDK 17 (#886) * Improve tests for JDK 17 * Fall back on resolving `SerializedLambda` via capturing class if `SerializedLambda#readResolve` is not accessible * Fix `--ad-opens` * Do not compare static and synthetic fields * Remove broken custom `equals` from test data * Use custom `equals` contract if available * Remove now unncessary `--add-opens` arguments --- README.md | 3 +- pom.xml | 10 +- .../kryo/serializers/ClosureSerializer.java | 30 ++++-- src/com/esotericsoftware/kryo/util/Util.java | 4 + .../ImmutableCollectionsSerializersTest.java | 2 +- .../esotericsoftware/kryo/TestDataJava14.java | 14 ++- .../esotericsoftware/kryo/KryoTestCase.java | 83 ++++++++-------- .../esotericsoftware/kryo/ReferenceTest.java | 99 +++---------------- .../kryo/ReflectionAssert.java | 26 ++++- .../kryo/SerializationBenchmarkTest.java | 6 +- .../kryo/SerializationCompatTestData.java | 8 -- test/com/esotericsoftware/kryo/Unsafe.java | 34 +++++++ .../io/UnsafeByteBufferInputOutputTest.java | 2 + .../kryo/io/UnsafeInputOutputTest.java | 2 + .../serializers/ClosureSerializerTest.java | 11 ++- .../CompatibleFieldSerializerTest.java | 2 - .../kryo/serializers/FieldSerializerTest.java | 3 +- .../kryo/serializers/GenericsTest.java | 30 +++--- .../serializers/OptionalSerializersTest.java | 2 +- 19 files changed, 196 insertions(+), 175 deletions(-) create mode 100644 test/com/esotericsoftware/kryo/Unsafe.java diff --git a/README.md b/README.md index 534811748..06b49b8dd 100644 --- a/README.md +++ b/README.md @@ -617,12 +617,11 @@ Kryo `isFinal` is used to determine if a class is final. This method can be over Kryo can serialize Java 8+ closures that implement java.io.Serializable, with some caveats. Closures serialized on one JVM may fail to be deserialized on a different JVM. -Kryo `isClosure` is used to determine if a class is a closure. If so, then ClosureSerializer.Closure is used to find the class registration instead of the closure's class. To serialize closures, the following classes must be registered: ClosureSerializer.Closure, SerializedLambda, Object[], and Class. Additionally, the closure's capturing class must be registered. +Kryo `isClosure` is used to determine if a class is a closure. If so, then ClosureSerializer.Closure is used to find the class registration instead of the closure's class. To serialize closures, the following classes must be registered: ClosureSerializer.Closure, Object[], and Class. Additionally, the closure's capturing class must be registered. ```java kryo.register(Object[].class); kryo.register(Class.class); -kryo.register(SerializedLambda.class); kryo.register(ClosureSerializer.Closure.class, new ClosureSerializer()); kryo.register(CapturingClass.class); diff --git a/pom.xml b/pom.xml index 13ddb18f3..9518c3538 100644 --- a/pom.xml +++ b/pom.xml @@ -436,15 +436,9 @@ --enable-preview - --add-opens java.base/java.lang=ALL-UNNAMED - --add-opens java.base/java.lang.invoke=ALL-UNNAMED - --add-opens=java.base/java.net=ALL-UNNAMED - --add-opens=java.base/java.nio=ALL-UNNAMED - --add-opens=java.base/java.time=ALL-UNNAMED - --add-opens=java.base/java.util=ALL-UNNAMED + --add-opens java.base/sun.nio.ch=ALL-UNNAMED + --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util.concurrent.atomic=ALL-UNNAMED - --add-opens=java.base/sun.nio.ch=ALL-UNNAMED - --add-opens=java.base/sun.util.calendar=ALL-UNNAMED diff --git a/src/com/esotericsoftware/kryo/serializers/ClosureSerializer.java b/src/com/esotericsoftware/kryo/serializers/ClosureSerializer.java index 8f8c19fc5..977e51d0f 100644 --- a/src/com/esotericsoftware/kryo/serializers/ClosureSerializer.java +++ b/src/com/esotericsoftware/kryo/serializers/ClosureSerializer.java @@ -27,6 +27,7 @@ import com.esotericsoftware.kryo.Serializer; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.minlog.Log; import java.io.Serializable; import java.lang.invoke.SerializedLambda; @@ -36,7 +37,6 @@ *

* kryo.register(Object[].class); * kryo.register(Class.class); - * kryo.register(java.lang.invoke.SerializedLambda.class);
* kryo.register(ClosureSerializer.Closure.class, new ClosureSerializer());
*

* Also, the closure's capturing class must be registered. @@ -56,7 +56,9 @@ public ClosureSerializer () { readResolve = SerializedLambda.class.getDeclaredMethod("readResolve"); readResolve.setAccessible(true); } catch (Exception ex) { - throw new KryoException("Unable to obtain SerializedLambda#readResolve via reflection.", ex); + readResolve = null; + Log.warn("Unable to obtain SerializedLambda#readResolve via reflection. " + + "Falling back on resolving lambdas via capturing class.", ex); } } } @@ -87,11 +89,12 @@ public Object read (Kryo kryo, Input input, Class type) { Object[] capturedArgs = new Object[count]; for (int i = 0; i < count; i++) capturedArgs[i] = kryo.readClassAndObject(input); - SerializedLambda serializedLambda = new SerializedLambda(kryo.readClass(input).getType(), input.readString(), + Class capturingClass = kryo.readClass(input).getType(); + SerializedLambda serializedLambda = new SerializedLambda(capturingClass, input.readString(), input.readString(), input.readString(), input.readVarInt(true), input.readString(), input.readString(), input.readString(), input.readString(), capturedArgs); try { - return readResolve.invoke(serializedLambda); + return readResolve(capturingClass, serializedLambda); } catch (Exception ex) { throw new KryoException("Error reading closure.", ex); } @@ -99,12 +102,25 @@ public Object read (Kryo kryo, Input input, Class type) { public Object copy (Kryo kryo, Object original) { try { - return readResolve.invoke(toSerializedLambda(original)); + SerializedLambda lambda = toSerializedLambda(original); + Class capturingClass = Class.forName(lambda.getCapturingClass().replace('/', '.')); + return readResolve(capturingClass, lambda); } catch (Exception ex) { throw new KryoException("Error copying closure.", ex); } } + private Object readResolve (Class capturingClass, SerializedLambda lambda) throws Exception { + if (readResolve != null) { + return readResolve.invoke(lambda); + } + + // See SerializedLambda#readResolve + Method m = capturingClass.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class); + m.setAccessible(true); + return m.invoke(null, lambda); + } + private SerializedLambda toSerializedLambda (Object object) { Object replacement; try { @@ -118,9 +134,7 @@ private SerializedLambda toSerializedLambda (Object object) { try { return (SerializedLambda)replacement; } catch (Exception ex) { - throw new KryoException( - "writeReplace must return a SerializedLambda: " + (replacement == null ? null : className(replacement.getClass())), - ex); + throw new KryoException("writeReplace must return a SerializedLambda: " + className(replacement.getClass()), ex); } } } diff --git a/src/com/esotericsoftware/kryo/util/Util.java b/src/com/esotericsoftware/kryo/util/Util.java index 52a345b33..f724b2ea9 100644 --- a/src/com/esotericsoftware/kryo/util/Util.java +++ b/src/com/esotericsoftware/kryo/util/Util.java @@ -68,6 +68,10 @@ public class Util { primitiveWrappers.put(short.class, Short.class); } + public static boolean isUnsafeAvailable () { + return unsafe; + } + public static boolean isClassAvailable (String className) { try { Class.forName(className); diff --git a/test-jdk11/com/esotericsoftware/kryo/serializers/ImmutableCollectionsSerializersTest.java b/test-jdk11/com/esotericsoftware/kryo/serializers/ImmutableCollectionsSerializersTest.java index 6c338793b..c19f89479 100644 --- a/test-jdk11/com/esotericsoftware/kryo/serializers/ImmutableCollectionsSerializersTest.java +++ b/test-jdk11/com/esotericsoftware/kryo/serializers/ImmutableCollectionsSerializersTest.java @@ -82,7 +82,7 @@ void testImmutableSet () { roundTrip(6, Set.of(1, 2, 3)); } - static class TestClass { + public static class TestClass { List list; Map map; Set set; diff --git a/test-jdk14/com/esotericsoftware/kryo/TestDataJava14.java b/test-jdk14/com/esotericsoftware/kryo/TestDataJava14.java index 99d129a98..55f8ea84d 100644 --- a/test-jdk14/com/esotericsoftware/kryo/TestDataJava14.java +++ b/test-jdk14/com/esotericsoftware/kryo/TestDataJava14.java @@ -19,14 +19,24 @@ package com.esotericsoftware.kryo; -import com.esotericsoftware.kryo.SerializationCompatTestData; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; /** Test data for {@link com.esotericsoftware.kryo.serializers.RecordSerializerTest}. * @author Julia Boes * @author Chris Hegarty */ public class TestDataJava14 extends SerializationCompatTestData.TestData { - public record Rec (byte b, short s, int i, long l, float f, double d, boolean bool, char c, String str, Integer[] n) { }; + public record Rec (byte b, short s, int i, long l, float f, double d, boolean bool, char c, String str, Integer[] n) { + // Overriden because of https://stackoverflow.com/questions/61261226/java-14-records-and-arrays + public boolean equals(Object o) { + return EqualsBuilder.reflectionEquals(this, o); + } + + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + }; private Rec rec; diff --git a/test/com/esotericsoftware/kryo/KryoTestCase.java b/test/com/esotericsoftware/kryo/KryoTestCase.java index 1cf7dcd2d..3dd96682a 100644 --- a/test/com/esotericsoftware/kryo/KryoTestCase.java +++ b/test/com/esotericsoftware/kryo/KryoTestCase.java @@ -30,6 +30,7 @@ import com.esotericsoftware.kryo.unsafe.UnsafeByteBufferOutput; import com.esotericsoftware.kryo.unsafe.UnsafeInput; import com.esotericsoftware.kryo.unsafe.UnsafeOutput; +import com.esotericsoftware.kryo.util.Util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -139,50 +140,54 @@ public Input createInput (byte[] buffer) { } }); - roundTripWithBufferFactory(length, object1, new BufferFactory() { - public Output createOutput (OutputStream os) { - return new UnsafeOutput(os); - } - - public Output createOutput (OutputStream os, int size) { - return new UnsafeOutput(os, size); - } + if (Util.isUnsafeAvailable()) { + roundTripWithBufferFactory(length, object1, new BufferFactory() { + public Output createOutput(OutputStream os) { + return new UnsafeOutput(os); + } - public Output createOutput (int size, int limit) { - return new UnsafeOutput(size, limit); - } + public Output createOutput(OutputStream os, int size) { + return new UnsafeOutput(os, size); + } - public Input createInput (InputStream os, int size) { - return new UnsafeInput(os, size); - } + public Output createOutput(int size, int limit) { + return new UnsafeOutput(size, limit); + } - public Input createInput (byte[] buffer) { - return new UnsafeInput(buffer); - } - }); + public Input createInput(InputStream os, int size) { + return new UnsafeInput(os, size); + } - roundTripWithBufferFactory(length, object1, new BufferFactory() { - public Output createOutput (OutputStream os) { - return new UnsafeByteBufferOutput(os); - } - - public Output createOutput (OutputStream os, int size) { - return new UnsafeByteBufferOutput(os, size); - } - - public Output createOutput (int size, int limit) { - return new UnsafeByteBufferOutput(size, limit); - } - - public Input createInput (InputStream os, int size) { - return new UnsafeByteBufferInput(os, size); - } + public Input createInput(byte[] buffer) { + return new UnsafeInput(buffer); + } + }); + } - public Input createInput (byte[] buffer) { - ByteBuffer byteBuffer = allocateByteBuffer(buffer); - return new UnsafeByteBufferInput(byteBuffer.asReadOnlyBuffer()); - } - }); + if (Util.isUnsafeAvailable()) { + roundTripWithBufferFactory(length, object1, new BufferFactory() { + public Output createOutput(OutputStream os) { + return new UnsafeByteBufferOutput(os); + } + + public Output createOutput(OutputStream os, int size) { + return new UnsafeByteBufferOutput(os, size); + } + + public Output createOutput(int size, int limit) { + return new UnsafeByteBufferOutput(size, limit); + } + + public Input createInput(InputStream os, int size) { + return new UnsafeByteBufferInput(os, size); + } + + public Input createInput(byte[] buffer) { + ByteBuffer byteBuffer = allocateByteBuffer(buffer); + return new UnsafeByteBufferInput(byteBuffer.asReadOnlyBuffer()); + } + }); + } return object2; } diff --git a/test/com/esotericsoftware/kryo/ReferenceTest.java b/test/com/esotericsoftware/kryo/ReferenceTest.java index 924d6c3cd..0eeb2844b 100644 --- a/test/com/esotericsoftware/kryo/ReferenceTest.java +++ b/test/com/esotericsoftware/kryo/ReferenceTest.java @@ -25,9 +25,9 @@ import com.esotericsoftware.kryo.io.Output; import com.esotericsoftware.kryo.serializers.MapSerializer; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.TreeMap; import org.junit.jupiter.api.Test; @@ -89,19 +89,13 @@ void testReadingNestedObjectsFirst () { list.add("2"); list.add("1"); list.add("1"); - List subList = list.subList(0, 5); + ArrayListHolder subList = new ArrayListHolder(list); kryo.setRegistrationRequired(false); kryo.register(ArrayList.class); - Class subListClass = (Class)subList.getClass(); - if (subListClass.getName().equals("java.util.ArrayList$SubList")) { - // This is JDK > = 1.7 - kryo.register(subList.getClass(), new ArraySubListSerializer()); - } else { - kryo.register(subList.getClass(), new SubListSerializer()); + kryo.register(ArrayListHolder.class); - } - roundTrip(23, subList); + roundTrip(15, subList); } @Test @@ -118,86 +112,25 @@ void testReadInvalidReference() { fail("Exception was expected"); } - public static class SubListSerializer extends Serializer { - private Field listField, offsetField, sizeField; - - public SubListSerializer () { - try { - Class sublistClass = Class.forName("java.util.SubList"); - listField = sublistClass.getDeclaredField("l"); - offsetField = sublistClass.getDeclaredField("offset"); - sizeField = sublistClass.getDeclaredField("size"); - listField.setAccessible(true); - offsetField.setAccessible(true); - sizeField.setAccessible(true); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - public void write (Kryo kryo, Output output, List list) { - try { - kryo.writeClassAndObject(output, listField.get(list)); - int fromIndex = offsetField.getInt(list); - int count = sizeField.getInt(list); - output.writeInt(fromIndex); - output.writeInt(count); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - public List read (Kryo kryo, Input input, Class type) { - List list = (List)kryo.readClassAndObject(input); - int fromIndex = input.readInt(); - int count = input.readInt(); - return list.subList(fromIndex, fromIndex + count); - } - } + public static class ArrayListHolder { + private List parent; - public static class ArraySubListSerializer extends Serializer { - private Field parentField, offsetField, sizeField; - - public ArraySubListSerializer () { - try { - Class sublistClass = Class.forName("java.util.ArrayList$SubList"); - parentField = getParentField(sublistClass); - offsetField = sublistClass.getDeclaredField("offset"); - sizeField = sublistClass.getDeclaredField("size"); - parentField.setAccessible(true); - offsetField.setAccessible(true); - sizeField.setAccessible(true); - } catch (Exception ex) { - throw new RuntimeException(ex); - } + public ArrayListHolder () { } - private Field getParentField(Class sublistClass) throws NoSuchFieldException { - try { - // java 9+ - return sublistClass.getDeclaredField("root"); - } catch(NoSuchFieldException e) { - return sublistClass.getDeclaredField("parent"); - } + public ArrayListHolder (List parent) { + this.parent = parent; } - public void write (Kryo kryo, Output output, List list) { - try { - kryo.writeClassAndObject(output, parentField.get(list)); - int offset = offsetField.getInt(list); - int size = sizeField.getInt(list); - output.writeInt(offset); - output.writeInt(size); - } catch (Exception ex) { - throw new RuntimeException(ex); - } + public boolean equals (Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ArrayListHolder that = (ArrayListHolder)o; + return Objects.equals(parent, that.parent); } - public List read (Kryo kryo, Input input, Class type) { - List list = (List)kryo.readClassAndObject(input); - int offset = input.readInt(); - int size = input.readInt(); - return list.subList(offset, offset + size); + public int hashCode () { + return Objects.hash(parent); } } } diff --git a/test/com/esotericsoftware/kryo/ReflectionAssert.java b/test/com/esotericsoftware/kryo/ReflectionAssert.java index c048b1ecb..b3ca742d5 100644 --- a/test/com/esotericsoftware/kryo/ReflectionAssert.java +++ b/test/com/esotericsoftware/kryo/ReflectionAssert.java @@ -128,6 +128,12 @@ private static void assertReflectionEquals (final Object one, final Object anoth assertCollectionEquals((Collection)one, (Collection)another, requireMatchingCollectionClasses, alreadyChecked, path); return; } + + if (one instanceof StringBuilder || one instanceof StringBuffer) { + assertEquals(((CharSequence)one).toString(), ((CharSequence)another).toString(), + "Values not equals for path '" + (StringUtils.isEmpty(path) ? "." : path) + "' - "); + return; + } if (one instanceof Currency) { // Check that the transient field defaultFractionDigits is initialized @@ -143,6 +149,11 @@ private static void assertReflectionEquals (final Object one, final Object anoth } Class clazz = one.getClass(); + if (hasCustomEquals(clazz)) { + assertEquals(one, another, "Values not equals for path '" + (StringUtils.isEmpty(path) ? "." : path) + "' - "); + return; + } + while (clazz != null) { assertEqualDeclaredFields(clazz, one, another, requireMatchingCollectionClasses, alreadyChecked, path); clazz = clazz.getSuperclass(); @@ -164,6 +175,17 @@ private static boolean oneIsAssignable (final Object one, final Object another, return false; } + private static boolean hasCustomEquals(Class c) { + while (!Object.class.equals(c)) { + try { + c.getDeclaredMethod("equals", Object.class); + return true; + } catch (Exception ignored) {} + c = c.getSuperclass(); + } + return false; + } + /* * TODO (MG): this assumes same iteration order, which must not be given for sets. There could be a specialized implementation * for sets. @@ -194,8 +216,8 @@ private static void assertMapEquals (final Map m1, final Map m2, fin private static void assertEqualDeclaredFields (final Class clazz, final Object one, final Object another, final boolean requireMatchingClasses, final Map alreadyChecked, final String path) { for (final Field field : clazz.getDeclaredFields()) { - field.setAccessible(true); - if (!Modifier.isTransient(field.getModifiers())) { + if (!Modifier.isTransient(field.getModifiers()) && !Modifier.isStatic(field.getModifiers()) && !field.isSynthetic()) { + field.setAccessible(true); try { assertReflectionEquals(field.get(one), field.get(another), requireMatchingClasses, alreadyChecked, path + "." + field.getName()); diff --git a/test/com/esotericsoftware/kryo/SerializationBenchmarkTest.java b/test/com/esotericsoftware/kryo/SerializationBenchmarkTest.java index 251393740..496faa5af 100644 --- a/test/com/esotericsoftware/kryo/SerializationBenchmarkTest.java +++ b/test/com/esotericsoftware/kryo/SerializationBenchmarkTest.java @@ -109,6 +109,7 @@ void testByteBufferOutputFixed () throws Exception { } @Test + @Unsafe void testUnsafeOutput () throws Exception { UnsafeOutput output = new UnsafeOutput(OUTPUT_BUFFER_SIZE); UnsafeInput input = new UnsafeInput(output.getBuffer()); @@ -117,6 +118,7 @@ void testUnsafeOutput () throws Exception { } @Test + @Unsafe void testUnsafeOutputFixed () throws Exception { UnsafeOutput output = new UnsafeOutput(OUTPUT_BUFFER_SIZE); UnsafeInput input = new UnsafeInput(output.getBuffer()); @@ -127,6 +129,7 @@ void testUnsafeOutputFixed () throws Exception { } @Test + @Unsafe void testUnsafeByteBufferOutput () throws Exception { UnsafeByteBufferOutput output = new UnsafeByteBufferOutput(OUTPUT_BUFFER_SIZE); UnsafeByteBufferInput input = new UnsafeByteBufferInput(output.getByteBuffer()); @@ -135,6 +138,7 @@ void testUnsafeByteBufferOutput () throws Exception { } @Test + @Unsafe void testUnsafeByteBufferOutputFixed () throws Exception { UnsafeByteBufferOutput output = new UnsafeByteBufferOutput(OUTPUT_BUFFER_SIZE); UnsafeByteBufferInput input = new UnsafeByteBufferInput(output.getByteBuffer()); @@ -198,7 +202,7 @@ public void setUp () throws Exception { Log.WARN(); } - private static class SampleObject implements Externalizable, KryoSerializable { + public static class SampleObject implements Externalizable, KryoSerializable { private int intValue; public float floatValue; protected Short shortValue; diff --git a/test/com/esotericsoftware/kryo/SerializationCompatTestData.java b/test/com/esotericsoftware/kryo/SerializationCompatTestData.java index 7fa58f3a6..1730191d3 100644 --- a/test/com/esotericsoftware/kryo/SerializationCompatTestData.java +++ b/test/com/esotericsoftware/kryo/SerializationCompatTestData.java @@ -277,14 +277,6 @@ public TestData () { _public = new PublicClass(new PrivateClass("foo")); } - public int hashCode () { - return HashCodeBuilder.reflectionHashCode(this); - } - - public boolean equals (Object obj) { - return EqualsBuilder.reflectionEquals(this, obj); - } - } static class Generic { diff --git a/test/com/esotericsoftware/kryo/Unsafe.java b/test/com/esotericsoftware/kryo/Unsafe.java new file mode 100644 index 000000000..0ebb77e34 --- /dev/null +++ b/test/com/esotericsoftware/kryo/Unsafe.java @@ -0,0 +1,34 @@ +/* Copyright (c) 2008-2022, Nathan Sweet + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided with the distribution. + * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +package com.esotericsoftware.kryo; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.condition.EnabledIf; + +/** Conditional annotation that runs a test only if Unsafe is available */ +@Target(value = {ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@EnabledIf("com.esotericsoftware.kryo.util.Util#isUnsafeAvailable") +public @interface Unsafe { +} diff --git a/test/com/esotericsoftware/kryo/io/UnsafeByteBufferInputOutputTest.java b/test/com/esotericsoftware/kryo/io/UnsafeByteBufferInputOutputTest.java index 890ca1590..232f4a29d 100644 --- a/test/com/esotericsoftware/kryo/io/UnsafeByteBufferInputOutputTest.java +++ b/test/com/esotericsoftware/kryo/io/UnsafeByteBufferInputOutputTest.java @@ -22,6 +22,7 @@ import static com.esotericsoftware.kryo.KryoAssert.*; import static org.junit.jupiter.api.Assertions.*; +import com.esotericsoftware.kryo.Unsafe; import com.esotericsoftware.kryo.unsafe.UnsafeByteBufferInput; import com.esotericsoftware.kryo.unsafe.UnsafeByteBufferOutput; import com.esotericsoftware.kryo.unsafe.UnsafeUtil; @@ -33,6 +34,7 @@ import org.junit.jupiter.api.Test; /** @author Roman Levenstein */ +@Unsafe @SuppressWarnings("restriction") class UnsafeByteBufferInputOutputTest { diff --git a/test/com/esotericsoftware/kryo/io/UnsafeInputOutputTest.java b/test/com/esotericsoftware/kryo/io/UnsafeInputOutputTest.java index a8fe1eea3..90841efae 100644 --- a/test/com/esotericsoftware/kryo/io/UnsafeInputOutputTest.java +++ b/test/com/esotericsoftware/kryo/io/UnsafeInputOutputTest.java @@ -22,6 +22,7 @@ import static com.esotericsoftware.kryo.KryoAssert.*; import static org.junit.jupiter.api.Assertions.*; +import com.esotericsoftware.kryo.Unsafe; import com.esotericsoftware.kryo.unsafe.UnsafeInput; import com.esotericsoftware.kryo.unsafe.UnsafeOutput; @@ -32,6 +33,7 @@ import org.junit.jupiter.api.Test; /** @author Nathan Sweet */ +@Unsafe class UnsafeInputOutputTest { @Test void testOutputStream () { diff --git a/test/com/esotericsoftware/kryo/serializers/ClosureSerializerTest.java b/test/com/esotericsoftware/kryo/serializers/ClosureSerializerTest.java index 4980a7951..30b6ff826 100644 --- a/test/com/esotericsoftware/kryo/serializers/ClosureSerializerTest.java +++ b/test/com/esotericsoftware/kryo/serializers/ClosureSerializerTest.java @@ -25,7 +25,6 @@ import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; -import java.lang.invoke.SerializedLambda; import java.util.concurrent.Callable; import org.junit.jupiter.api.BeforeEach; @@ -40,7 +39,6 @@ public void setUp () throws Exception { kryo.register(Object[].class); kryo.register(Class.class); kryo.register(getClass()); // The closure's capturing class must be registered. - kryo.register(SerializedLambda.class); kryo.register(ClosureSerializer.Closure.class, new ClosureSerializer()); } @@ -60,6 +58,15 @@ void testSerializableClosure () { doAssertEquals(closure1, closure2); } + @Test + void testCopyClosure() { + Callable closure1 = (Callable & java.io.Serializable)( () -> 72363); + + final Callable closure2 = kryo.copy(closure1); + + doAssertEquals(closure1, closure2); + } + protected void doAssertEquals (Object object1, Object object2) { try { assertEquals(((Callable)object1).call(), ((Callable)object2).call()); diff --git a/test/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializerTest.java b/test/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializerTest.java index a0f6a0b29..9cafdc64c 100644 --- a/test/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializerTest.java +++ b/test/com/esotericsoftware/kryo/serializers/CompatibleFieldSerializerTest.java @@ -29,7 +29,6 @@ import com.esotericsoftware.kryo.io.Output; import java.io.Serializable; -import java.lang.invoke.SerializedLambda; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -499,7 +498,6 @@ void testClassWithLambdaField () { CompatibleFieldSerializer.CompatibleFieldSerializerConfig config = new CompatibleFieldSerializer.CompatibleFieldSerializerConfig(); kryo.setDefaultSerializer(new CompatibleFieldSerializerFactory(config)); kryo.register(ClassWithLambdaField.class); - kryo.register(SerializedLambda.class); kryo.register(ClosureSerializer.Closure.class, new ClosureSerializer()); roundTrip(236, new ClassWithLambdaField()); diff --git a/test/com/esotericsoftware/kryo/serializers/FieldSerializerTest.java b/test/com/esotericsoftware/kryo/serializers/FieldSerializerTest.java index 1a749e5d9..67f9f54b3 100644 --- a/test/com/esotericsoftware/kryo/serializers/FieldSerializerTest.java +++ b/test/com/esotericsoftware/kryo/serializers/FieldSerializerTest.java @@ -38,6 +38,7 @@ import com.esotericsoftware.kryo.serializers.FieldSerializer.NotNull; import com.esotericsoftware.kryo.serializers.FieldSerializer.Optional; import com.esotericsoftware.kryo.serializers.MapSerializer.BindMap; +import com.esotericsoftware.kryo.util.Util; import java.io.ByteArrayOutputStream; import java.util.ArrayList; @@ -402,7 +403,7 @@ void testDefaultInstantiatorStrategy () { kryo.register(HasPrivateConstructor.class); roundTrip(4, test); - assertEquals(20, HasPrivateConstructor.invocations, "Wrong number of constructor invocations"); + assertEquals(Util.isUnsafeAvailable() ? 20 : 10, HasPrivateConstructor.invocations, "Wrong number of constructor invocations"); } /** This test uses StdInstantiatorStrategy and should bypass invocation of no-arg constructor, even if it is provided. **/ diff --git a/test/com/esotericsoftware/kryo/serializers/GenericsTest.java b/test/com/esotericsoftware/kryo/serializers/GenericsTest.java index 104f2a0fe..dc9ee364a 100644 --- a/test/com/esotericsoftware/kryo/serializers/GenericsTest.java +++ b/test/com/esotericsoftware/kryo/serializers/GenericsTest.java @@ -186,11 +186,11 @@ void testClassHierarchyWithMissingTypeVariables () { roundTrip(168, o); } - private interface Holder { + interface Holder { V getValue (); } - private abstract static class AbstractValueHolder implements Holder { + abstract static class AbstractValueHolder implements Holder { private final V value; AbstractValueHolder (V value) { @@ -210,13 +210,13 @@ public boolean equals (Object o) { } } - private abstract static class AbstractValueListHolder extends AbstractValueHolder> { + abstract static class AbstractValueListHolder extends AbstractValueHolder> { AbstractValueListHolder (List value) { super(value); } } - private static class LongHolder extends AbstractValueHolder { + static class LongHolder extends AbstractValueHolder { /** Kryo Constructor */ LongHolder () { super(null); @@ -227,7 +227,7 @@ private static class LongHolder extends AbstractValueHolder { } } - private static class LongListHolder extends AbstractValueListHolder { + static class LongListHolder extends AbstractValueListHolder { /** Kryo Constructor */ LongListHolder () { super(null); @@ -272,7 +272,7 @@ public boolean equals (Object o) { } // A simple serializable class. - private static class SerializableObjectFoo implements Serializable { + public static class SerializableObjectFoo implements Serializable { String name; SerializableObjectFoo (String name) { @@ -295,18 +295,18 @@ public boolean equals (Object obj) { } } - private static class BaseGeneric { + static class BaseGeneric { // The type of this field cannot be derived from the context. // Therefore, Kryo should consider it to be Object. private final List listPayload; /** Kryo Constructor */ - protected BaseGeneric () { + BaseGeneric () { super(); this.listPayload = null; } - protected BaseGeneric (final List listPayload) { + BaseGeneric (final List listPayload) { super(); // Defensive copy, listPayload is mutable this.listPayload = new ArrayList(listPayload); @@ -330,7 +330,7 @@ public boolean equals (Object obj) { } // This is a non-generic class with a generic superclass. - private static class ConcreteClass2 extends BaseGeneric { + static class ConcreteClass2 extends BaseGeneric { /** Kryo Constructor */ ConcreteClass2 () { super(); @@ -341,7 +341,7 @@ public ConcreteClass2 (final List listPayload) { } } - private static class ConcreteClass1 extends ConcreteClass2 { + static class ConcreteClass1 extends ConcreteClass2 { /** Kryo Constructor */ ConcreteClass1 () { super(); @@ -352,7 +352,7 @@ public ConcreteClass1 (final List listPayload) { } } - private static class ConcreteClass extends ConcreteClass1 { + static class ConcreteClass extends ConcreteClass1 { /** Kryo Constructor */ ConcreteClass () { super(); @@ -363,7 +363,7 @@ public ConcreteClass (final List listPayload) { } } - public static class SuperGenerics { + static class SuperGenerics { public static class RootSuper { public ValueSuper rootSuperField; @@ -396,7 +396,7 @@ public boolean equals (Object o) { } } - public static class ClassWithMap { + static class ClassWithMap { public final Map> values = new HashMap(); public boolean equals (Object obj) { @@ -419,7 +419,7 @@ public String toString () { } } - public static class A { + static class A { public static class B extends A { } diff --git a/test/com/esotericsoftware/kryo/serializers/OptionalSerializersTest.java b/test/com/esotericsoftware/kryo/serializers/OptionalSerializersTest.java index 7725a092c..1c15eaff5 100644 --- a/test/com/esotericsoftware/kryo/serializers/OptionalSerializersTest.java +++ b/test/com/esotericsoftware/kryo/serializers/OptionalSerializersTest.java @@ -75,7 +75,7 @@ void testOptionalDouble () { roundTrip(10, OptionalDouble.of(Double.MAX_VALUE)); } - static class TestClass { + public static class TestClass { Optional maybe; public TestClass () {