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 
 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 }