1 /*
2  * Copyright (c) 2019, 2020, 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
27  * @summary Basic tests for serializing and deserializing record classes
28  * @run testng RecordClassTest
29  * @run testng/othervm/java.security.policy=empty_security.policy RecordClassTest
30  */
31 package test.java.io.Serializable.records;
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 {
writeExternal(ObjectOutput out)63         default void writeExternal(ObjectOutput out) {
64             fail("should not reach here");
65         }
readExternal(ObjectInput in)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")
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")
testClassSerialization(Class<?> recordClass, long unused)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")
testSerialVersionUID(Class<?> recordClass, long expectedUID)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 
NotSerializable1()119     record NotSerializable1() { }
120 
NotSerializable2(int x)121     record NotSerializable2(int x) { }
122 
NotSerializable3(T t)123     record NotSerializable3<T>(T t) { }
124 
125     @DataProvider(name = "notSerRecordClasses")
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")
testSerialVersionUIDNonSer(Class<?> recordClass)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 
serialize(T obj)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")
deserialize(byte[] streamBytes)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 
serializeDeserialize(T obj)166     static <T> T serializeDeserialize(T obj)
167         throws IOException, ClassNotFoundException
168     {
169         return deserialize(serialize(obj));
170     }
171 }
172