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 */
31
32 import java.io.ByteArrayInputStream;
33 import java.io.ByteArrayOutputStream;
34 import java.io.Externalizable;
35 import java.io.IOException;
36 import java.io.ObjectInput;
37 import java.io.ObjectInputStream;
38 import java.io.ObjectOutput;
39 import java.io.ObjectOutputStream;
40 import java.io.ObjectStreamClass;
41 import java.io.Serializable;
42 import org.testng.annotations.DataProvider;
43 import org.testng.annotations.Test;
44 import static java.lang.System.out;
45 import static org.testng.Assert.assertEquals;
46 import static org.testng.Assert.fail;
47
48 /**
49 * Serializes and deserializes record classes. Ensures that the SUID is 0.
50 */
51 public class RecordClassTest {
52
53 record Foo () implements Serializable { }
54
55 record Bar (int x) implements Serializable {
56 private static final long serialVersionUID = 987654321L;
57 }
58
59 record Baz (Foo foo, Bar bar, int i) implements Serializable { }
60
61 interface ThrowingExternalizable extends Externalizable {
62 default void writeExternal(ObjectOutput out) {
63 fail("should not reach here");
64 }
65 default void readExternal(ObjectInput in) {
66 fail("should not reach here");
67 }
68 }
69
70 record Wibble () implements ThrowingExternalizable {
71 private static final long serialVersionUID = 12345678L;
72 }
73
74 record Wobble (long l) implements ThrowingExternalizable { }
75
76 record Wubble (Wobble wobble, Wibble wibble, String s) implements ThrowingExternalizable { }
77
78 @DataProvider(name = "recordClasses")
79 public Object[][] recordClasses() {
80 return new Object[][] {
81 new Object[] { Foo.class , 0L },
82 new Object[] { Bar.class , 987654321L },
83 new Object[] { Baz.class , 0L },
84 new Object[] { Wibble.class , 12345678L },
85 new Object[] { Wobble.class , 0L },
86 new Object[] { Wubble.class , 0L },
87 };
88 }
89
90 /** Tests that the serialized and deserialized instances are equal. */
91 @Test(dataProvider = "recordClasses")
92 public void testClassSerialization(Class<?> recordClass, long unused)
93 throws Exception
94 {
95 out.println("\n---");
96 out.println("serializing : " + recordClass);
97 var deserializedClass = serializeDeserialize(recordClass);
98 out.println("deserialized: " + deserializedClass);
99 assertEquals(recordClass, deserializedClass);
100 assertEquals(deserializedClass, recordClass);
101 }
102
103 /** Tests that the SUID is always 0 unless explicitly declared. */
104 @Test(dataProvider = "recordClasses")
105 public void testSerialVersionUID(Class<?> recordClass, long expectedUID) {
106 out.println("\n---");
107 ObjectStreamClass osc = ObjectStreamClass.lookup(recordClass);
108 out.println("ObjectStreamClass::lookup : " + osc);
109 assertEquals(osc.getSerialVersionUID(), expectedUID);
110
111 osc = ObjectStreamClass.lookupAny(recordClass);
112 out.println("ObjectStreamClass::lookupAny: " + osc);
113 assertEquals(osc.getSerialVersionUID(), expectedUID);
114 }
115
116 // --- not Serializable
117
118 record NotSerializable1() { }
119
120 record NotSerializable2(int x) { }
121
122 record NotSerializable3<T>(T t) { }
123
124 @DataProvider(name = "notSerRecordClasses")
125 public Object[][] notSerRecordClasses() {
126 return new Object[][] {
127 new Object[] { NotSerializable1.class },
128 new Object[] { NotSerializable2.class },
129 new Object[] { NotSerializable3.class },
130 };
131 }
132
133 /** Tests that the generated SUID is always 0 for all non-Serializable record classes. */
134 @Test(dataProvider = "notSerRecordClasses")
135 public void testSerialVersionUIDNonSer(Class<?> recordClass) {
136 out.println("\n---");
137 ObjectStreamClass osc = ObjectStreamClass.lookup(recordClass);
138 out.println("ObjectStreamClass::lookup : " + osc);
139 assertEquals(osc, null);
140
141 osc = ObjectStreamClass.lookupAny(recordClass);
142 out.println("ObjectStreamClass::lookupAny: " + osc);
143 assertEquals(osc.getSerialVersionUID(), 0L);
144 }
145
146 // --- infra
147
148 static <T> byte[] serialize(T obj) throws IOException {
149 ByteArrayOutputStream baos = new ByteArrayOutputStream();
150 ObjectOutputStream oos = new ObjectOutputStream(baos);
151 oos.writeObject(obj);
152 oos.close();
153 return baos.toByteArray();
154 }
155
156 @SuppressWarnings("unchecked")
157 static <T> T deserialize(byte[] streamBytes)
158 throws IOException, ClassNotFoundException
159 {
160 ByteArrayInputStream bais = new ByteArrayInputStream(streamBytes);
161 ObjectInputStream ois = new ObjectInputStream(bais);
162 return (T) ois.readObject();
163 }
164
165 static <T> T serializeDeserialize(T obj)
166 throws IOException, ClassNotFoundException
167 {
168 return deserialize(serialize(obj));
169 }
170 }