1 /* 2 * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 8246774 8326879 27 * @summary Basic tests for serializing and deserializing record classes 28 * @run testng RecordClassTest 29 * @run testng/othervm --enable-preview RecordClassTest 30 * @run testng/othervm/java.security.policy=empty_security.policy RecordClassTest 31 */ 32 33 import java.io.ByteArrayInputStream; 34 import java.io.ByteArrayOutputStream; 35 import java.io.Externalizable; 36 import java.io.IOException; 37 import java.io.ObjectInput; 38 import java.io.ObjectInputStream; 39 import java.io.ObjectOutput; 40 import java.io.ObjectOutputStream; 41 import java.io.ObjectStreamClass; 42 import java.io.Serializable; 43 import org.testng.annotations.DataProvider; 44 import org.testng.annotations.Test; 45 import static java.lang.System.out; 46 import static org.testng.Assert.assertEquals; 47 import static org.testng.Assert.fail; 48 49 /** 50 * Serializes and deserializes record classes. Ensures that the SUID is 0. 51 */ 52 public class RecordClassTest { 53 54 record Foo () implements Serializable { } 55 56 record Bar (int x) implements Serializable { 57 private static final long serialVersionUID = 987654321L; 58 } 59 60 record Baz (Foo foo, Bar bar, int i) implements Serializable { } 61 62 interface ThrowingExternalizable extends Externalizable { 63 default void writeExternal(ObjectOutput out) { 64 fail("should not reach here"); 65 } 66 default void readExternal(ObjectInput in) { 67 fail("should not reach here"); 68 } 69 } 70 71 record Wibble () implements ThrowingExternalizable { 72 private static final long serialVersionUID = 12345678L; 73 } 74 75 record Wobble (long l) implements ThrowingExternalizable { } 76 77 record Wubble (Wobble wobble, Wibble wibble, String s) implements ThrowingExternalizable { } 78 79 @DataProvider(name = "recordClasses") 80 public Object[][] recordClasses() { 81 return new Object[][] { 82 new Object[] { Foo.class , 0L }, 83 new Object[] { Bar.class , 987654321L }, 84 new Object[] { Baz.class , 0L }, 85 new Object[] { Wibble.class , 12345678L }, 86 new Object[] { Wobble.class , 0L }, 87 new Object[] { Wubble.class , 0L }, 88 }; 89 } 90 91 /** Tests that the serialized and deserialized instances are equal. */ 92 @Test(dataProvider = "recordClasses") 93 public void testClassSerialization(Class<?> recordClass, long unused) 94 throws Exception 95 { 96 out.println("\n---"); 97 out.println("serializing : " + recordClass); 98 var deserializedClass = serializeDeserialize(recordClass); 99 out.println("deserialized: " + deserializedClass); 100 assertEquals(recordClass, deserializedClass); 101 assertEquals(deserializedClass, recordClass); 102 } 103 104 /** Tests that the SUID is always 0 unless explicitly declared. */ 105 @Test(dataProvider = "recordClasses") 106 public void testSerialVersionUID(Class<?> recordClass, long expectedUID) { 107 out.println("\n---"); 108 ObjectStreamClass osc = ObjectStreamClass.lookup(recordClass); 109 out.println("ObjectStreamClass::lookup : " + osc); 110 assertEquals(osc.getSerialVersionUID(), expectedUID); 111 112 osc = ObjectStreamClass.lookupAny(recordClass); 113 out.println("ObjectStreamClass::lookupAny: " + osc); 114 assertEquals(osc.getSerialVersionUID(), expectedUID); 115 } 116 117 // --- not Serializable 118 119 record NotSerializable1() { } 120 121 record NotSerializable2(int x) { } 122 123 record NotSerializable3<T>(T t) { } 124 125 @DataProvider(name = "notSerRecordClasses") 126 public Object[][] notSerRecordClasses() { 127 return new Object[][] { 128 new Object[] { NotSerializable1.class }, 129 new Object[] { NotSerializable2.class }, 130 new Object[] { NotSerializable3.class }, 131 }; 132 } 133 134 /** Tests that the generated SUID is always 0 for all non-Serializable record classes. */ 135 @Test(dataProvider = "notSerRecordClasses") 136 public void testSerialVersionUIDNonSer(Class<?> recordClass) { 137 out.println("\n---"); 138 ObjectStreamClass osc = ObjectStreamClass.lookup(recordClass); 139 out.println("ObjectStreamClass::lookup : " + osc); 140 assertEquals(osc, null); 141 142 osc = ObjectStreamClass.lookupAny(recordClass); 143 out.println("ObjectStreamClass::lookupAny: " + osc); 144 assertEquals(osc.getSerialVersionUID(), 0L); 145 } 146 147 // --- infra 148 149 static <T> byte[] serialize(T obj) throws IOException { 150 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 151 ObjectOutputStream oos = new ObjectOutputStream(baos); 152 oos.writeObject(obj); 153 oos.close(); 154 return baos.toByteArray(); 155 } 156 157 @SuppressWarnings("unchecked") 158 static <T> T deserialize(byte[] streamBytes) 159 throws IOException, ClassNotFoundException 160 { 161 ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes); 162 ObjectInputStream ois = new ObjectInputStream(bais); 163 return (T) ois.readObject(); 164 } 165 166 static <T> T serializeDeserialize(T obj) 167 throws IOException, ClassNotFoundException 168 { 169 return deserialize(serialize(obj)); 170 } 171 }