• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 
18 /**
19 * @author Alexey V. Varlamov
20 * @version $Revision$
21 */
22 
23 package org.apache.harmony.testframework.serialization;
24 
25 import junit.framework.TestCase;
26 import java.io.ByteArrayInputStream;
27 import java.io.ByteArrayOutputStream;
28 import java.io.File;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.ObjectInputStream;
33 import java.io.ObjectOutputStream;
34 import java.io.OutputStream;
35 import java.io.Serializable;
36 import java.lang.reflect.Method;
37 import java.security.Permission;
38 import java.security.PermissionCollection;
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.HashSet;
42 
43 /**
44  * Framework for serialization testing. Subclasses only need to override
45  * getData() method and, optionally, assertDeserialized() method. The first one
46  * returns array of objects to be de/serialized in tests, and the second
47  * compares reference and deserialized objects (needed only if tested objects do
48  * not provide specific method equals()). <br>
49  * There are two modes of test run: <b>reference generation mode </b> and
50  * <b>testing mode </b>. The actual mode is selected via
51  * <b>&quot;test.mode&quot; </b> system property. The <b>testing mode </b> is
52  * the default mode. <br>
53  * To turn on the <b>reference generation mode </b>, the test.mode property
54  * should be set to value &quot;serial.reference&quot;. In this mode, no testing
55  * is performed but golden files are produced, which contain reference
56  * serialized objects. This mode should be run on a pure
57  * Implementation classes, which are targeted for compartibility. <br>
58  * The location of golden files (in both modes) is controlled via
59  * <b>&quot;RESOURCE_DIR&quot; </b> system property.
60  *
61  */
62 public abstract class SerializationTest extends TestCase {
63 
64     /**
65      * Key to a system property defining root location of golden files.
66      */
67     public static final String GOLDEN_PATH = "RESOURCE_DIR";
68 
69     private static final String outputPath = System.getProperty(GOLDEN_PATH,
70             "src/test/resources/serialization");
71 
72     /**
73      * This is the main working method of this framework. Subclasses must
74      * override it to provide actual objects for testing.
75      *
76      * @return array of objects to be de/serialized in tests.
77      */
getData()78     protected abstract Object[] getData();
79 
80     /**
81      * Tests that data objects can be serialized and deserialized without
82      * exceptions, and that deserialization really produces deeply cloned
83      * objects.
84      */
testSelf()85     public void testSelf() throws Throwable {
86 
87         if (this instanceof SerializableAssert) {
88             verifySelf(getData(), (SerializableAssert) this);
89         } else {
90             verifySelf(getData());
91 
92         }
93     }
94 
95     /**
96      * Tests that data objects can be deserialized from golden files, to verify
97      * compatibility with Reference Implementation.
98      */
99 
testGolden()100     public void testGolden() throws Throwable {
101         verifyGolden(this, getData());
102     }
103 
104     /**
105      * Returns golden file for an object being tested.
106      *
107      * @param index array index of tested data (as returned by
108      *        {@link #getData() getData()})
109      * @return corresponding golden file
110      */
getDataFile(int index)111     protected File getDataFile(int index) {
112         String name = this.getClass().getName();
113         int dot = name.lastIndexOf(".");
114         String path = name.substring(0, dot).replace('.', File.separatorChar);
115         if (outputPath != null && outputPath.length() != 0) {
116             path = outputPath + File.separator + path;
117         }
118 
119         return new File(path, name.substring(dot + 1) + "." + index + ".dat");
120     }
121 
122     /**
123      * Working method for files generation mode. Serializes test objects
124      * returned by {@link #getData() getData()}to golden files, each object to
125      * a separate file.
126      *
127      * @throws IOException
128      */
produceGoldenFiles()129     protected void produceGoldenFiles() throws IOException {
130 
131         String goldenPath = outputPath + File.separatorChar
132                 + getClass().getName().replace('.', File.separatorChar)
133                 + ".golden.";
134 
135         Object[] data = getData();
136         for (int i = 0; i < data.length; i++) {
137 
138             File goldenFile = new File(goldenPath + i + ".ser");
139             goldenFile.getParentFile().mkdirs();
140             goldenFile.createNewFile();
141 
142             putObjectToStream(data[i], new FileOutputStream(goldenFile));
143         }
144     }
145 
146     /**
147      * Serializes specified object to an output stream.
148      */
putObjectToStream(Object obj, OutputStream os)149     public static void putObjectToStream(Object obj, OutputStream os)
150             throws IOException {
151         ObjectOutputStream oos = new ObjectOutputStream(os);
152         oos.writeObject(obj);
153         oos.flush();
154         oos.close();
155     }
156 
157     /**
158      * Deserializes single object from an input stream.
159      */
getObjectFromStream(InputStream is)160     public static Serializable getObjectFromStream(InputStream is)
161             throws IOException, ClassNotFoundException {
162         ObjectInputStream ois = new ObjectInputStream(is);
163         Object result = ois.readObject();
164         ois.close();
165         return (Serializable)result;
166     }
167 
168     /**
169      * Interface to compare (de)serialized objects
170      *
171      * Should be implemented if a class under test does not provide specific
172      * equals() method and it's instances should to be compared manually.
173      */
174     public interface SerializableAssert {
175 
176         /**
177          * Compares deserialized and reference objects.
178          *
179          * @param initial - initial object used for creating serialized form
180          * @param deserialized - deserialized object
181          */
assertDeserialized(Serializable initial, Serializable deserialized)182         void assertDeserialized(Serializable initial, Serializable deserialized);
183     }
184 
185     // default comparator for a class that has equals(Object) method
186     private final static SerializableAssert DEFAULT_COMPARATOR = new SerializableAssert() {
187         public void assertDeserialized(Serializable initial, Serializable deserialized) {
188             assertEquals(initial, deserialized);
189         }
190     };
191 
192     /**
193      * Comparator for verifying that deserialized object is the same as initial.
194      */
195     public final static SerializableAssert SAME_COMPARATOR = new SerializableAssert() {
196         public void assertDeserialized(Serializable initial, Serializable deserialized) {
197             assertSame(initial, deserialized);
198         }
199     };
200 
201     /**
202      * Comparator for Throwable objects
203      */
204     public final static SerializableAssert THROWABLE_COMPARATOR = new SerializableAssert() {
205         public void assertDeserialized(Serializable initial, Serializable deserialized) {
206             Throwable initThr = (Throwable) initial;
207             Throwable dserThr = (Throwable) deserialized;
208 
209             // verify class
210             assertEquals(initThr.getClass(), dserThr.getClass());
211 
212             // verify message
213             assertEquals(initThr.getMessage(), dserThr.getMessage());
214 
215             // verify cause
216             if (initThr.getCause() == null) {
217                 assertNull(dserThr.getCause());
218             } else {
219                 assertNotNull(dserThr.getCause());
220                 THROWABLE_COMPARATOR.assertDeserialized(initThr.getCause(),
221                         dserThr.getCause());
222             }
223         }
224     };
225 
226     /**
227      * Comparator for PermissionCollection objects
228      */
229     public final static SerializableAssert PERMISSION_COLLECTION_COMPARATOR = new SerializableAssert() {
230         public void assertDeserialized(Serializable initial, Serializable deserialized) {
231 
232             PermissionCollection initPC = (PermissionCollection) initial;
233             PermissionCollection dserPC = (PermissionCollection) deserialized;
234 
235             // verify class
236             assertEquals(initPC.getClass(), dserPC.getClass());
237 
238             // verify 'readOnly' field
239             assertEquals(initPC.isReadOnly(), dserPC.isReadOnly());
240 
241             // verify collection of permissions
242             Collection<Permission> refCollection = new HashSet<Permission>(
243                     Collections.list(initPC.elements()));
244             Collection<Permission> tstCollection = new HashSet<Permission>(
245                     Collections.list(dserPC.elements()));
246 
247             assertEquals(refCollection, tstCollection);
248         }
249     };
250 
251     /**
252      * Returns <code>comparator</code> for provided serializable
253      * <code>object</code>.
254      *
255      * The <code>comparator</code> is searched in the following order: <br>
256      * - if <code>test</code> implements SerializableAssert interface then it is
257      * selected as </code>comparator</code>.<br>- if passed <code>object</code>
258      * has class in its classes hierarchy that overrides <code>equals(Object)</code>
259      * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the
260      * method tries to select one of known comparators basing on <code>object's</code>
261      * class,for example, if passed <code>object</code> is instance of
262      * Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>
263      * - otherwise RuntimeException is thrown
264      *
265      * @param test - test case
266      * @param object - object to be compared
267      * @return object's comparator
268      */
defineComparator(Object test, Object object)269     public static SerializableAssert defineComparator(Object test, Object object)
270             throws Exception {
271 
272         if (test instanceof SerializableAssert) {
273             return (SerializableAssert) test;
274         }
275 
276         Method m = object.getClass().getMethod("equals", new Class[] { Object.class });
277         if (m.getDeclaringClass() != Object.class) {
278             // one of classes overrides Object.equals(Object) method
279             // use default comparator
280             return DEFAULT_COMPARATOR;
281         }
282 
283         // TODO use generics to detect comparator
284         // instead of 'instanceof' for the first element
285         if (object instanceof Throwable) {
286             return THROWABLE_COMPARATOR;
287         }
288         if (object instanceof PermissionCollection) {
289             return PERMISSION_COLLECTION_COMPARATOR;
290         }
291         throw new RuntimeException("Failed to detect comparator");
292     }
293 
294     /**
295      * Verifies that object deserialized from golden file correctly.
296      *
297      * The method invokes <br>
298      * verifyGolden(test, object, defineComparator(test, object));
299      *
300      * @param test - test case
301      * @param object - to be compared
302      */
verifyGolden(Object test, Object object)303     public static void verifyGolden(Object test, Object object) throws Exception {
304         verifyGolden(test, object, defineComparator(test, object));
305     }
306 
307     /**
308      * Verifies that object deserialized from golden file correctly.
309      *
310      * The method loads "<code>testName</code>.golden.ser" resource file
311      * from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
312      * folder, reads an object from the loaded file and compares it with
313      * <code>object</code> using specified <code>comparator</code>.
314      *
315      * @param test - test case
316      * @param object - to be compared
317      * @param comparator - for comparing (de)serialized objects
318      */
verifyGolden(Object test, Object object, SerializableAssert comparator)319     public static void verifyGolden(Object test, Object object, SerializableAssert comparator)
320             throws Exception {
321         assertNotNull("Null comparator", comparator);
322         Serializable deserialized = getObject(test, ".golden.ser");
323         comparator.assertDeserialized((Serializable) object, deserialized);
324     }
325 
326     /**
327      * Verifies that objects from array deserialized from golden files
328      * correctly.
329      *
330      * The method invokes <br>
331      * verifyGolden(test, objects, defineComparator(test, object[0]));
332      *
333      * @param test - test case
334      * @param objects - array of objects to be compared
335      */
verifyGolden(Object test, Object[] objects)336     public static void verifyGolden(Object test, Object[] objects) throws Exception {
337         assertFalse("Empty array", objects.length == 0);
338         verifyGolden(test, objects, defineComparator(test, objects[0]));
339     }
340 
341     /**
342      * Verifies that objects from array deserialized from golden files
343      * correctly.
344      *
345      * The method loads "<code>testName</code>.golden.<code>N</code>.ser"
346      * resource files from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
347      * folder, from each loaded file it reads an object from and compares it
348      * with corresponding object in provided array (i.e. <code>objects[N]</code>)
349      * using specified <code>comparator</code>. (<code>N</code> is index
350      * in object's array.)
351      *
352      * @param test - test case
353      * @param objects - array of objects to be compared
354      * @param comparator - for comparing (de)serialized objects
355      */
verifyGolden(Object test, Object[] objects, SerializableAssert comparator)356     public static void verifyGolden(Object test, Object[] objects, SerializableAssert comparator)
357             throws Exception {
358         assertFalse("Empty array", objects.length == 0);
359         for (int i = 0; i < objects.length; i++) {
360             Serializable deserialized = getObject(test, ".golden." + i + ".ser");
361             comparator.assertDeserialized((Serializable) objects[i], deserialized);
362         }
363     }
364 
365     /**
366      * Verifies that object can be smoothly serialized/deserialized.
367      *
368      * The method invokes <br>
369      * verifySelf(object, defineComparator(null, object));
370      *
371      * @param object - to be serialized/deserialized
372      */
verifySelf(Object object)373     public static void verifySelf(Object object) throws Exception {
374         verifySelf(object, defineComparator(null, object));
375     }
376 
377     /**
378      * Verifies that object can be smoothly serialized/deserialized.
379      *
380      * The method serialize/deserialize <code>object</code> and compare it
381      * with initial <code>object</code>.
382      *
383      * @param object - object to be serialized/deserialized
384      * @param comparator - for comparing serialized/deserialized object with initial object
385      */
verifySelf(Object object, SerializableAssert comparator)386     public static void verifySelf(Object object, SerializableAssert comparator) throws Exception {
387         Serializable initial = (Serializable) object;
388         comparator.assertDeserialized(initial, copySerializable(initial));
389     }
390 
391     /**
392      * Verifies that that objects from array can be smoothly
393      * serialized/deserialized.
394      *
395      * The method invokes <br>
396      * verifySelf(objects, defineComparator(null, object[0]));
397      *
398      * @param objects - array of objects to be serialized/deserialized
399      */
verifySelf(Object[] objects)400     public static void verifySelf(Object[] objects) throws Exception {
401         assertFalse("Empty array", objects.length == 0);
402         verifySelf(objects, defineComparator(null, objects[0]));
403     }
404 
405     /**
406      * Verifies that that objects from array can be smoothly
407      * serialized/deserialized.
408      *
409      * The method serialize/deserialize each object in <code>objects</code>
410      * array and compare it with initial object.
411      *
412      * @param objects - array of objects to be serialized/deserialized
413      * @param comparator - for comparing serialized/deserialized object with initial object
414      */
verifySelf(Object[] objects, SerializableAssert comparator)415     public static void verifySelf(Object[] objects, SerializableAssert comparator)
416             throws Exception {
417         assertFalse("Empty array", objects.length == 0);
418         for (Object entry: objects){
419             verifySelf(entry, comparator);
420         }
421     }
422 
getObject(Object test, String toAppend)423     private static Serializable getObject(Object test, String toAppend) throws Exception {
424         StringBuilder path = new StringBuilder("/serialization");
425         path.append(File.separatorChar);
426         path.append(test.getClass().getName().replace('.', File.separatorChar));
427         path.append(toAppend);
428 
429         String pathString = path.toString();
430 
431         InputStream in = SerializationTest.class.getResourceAsStream(pathString);
432         assertNotNull("Failed to load serialization resource file: " + path, in);
433         return getObjectFromStream(in);
434     }
435 
436     /**
437      * Creates golden file.
438      *
439      * The folder for created file is: <code>root + test's package name</code>.
440      * The file name is: <code>test's name + "golden.ser"</code>
441      *
442      * @param root - root directory for serialization resource files
443      * @param test - test case
444      * @param object - to be serialized
445      * @throws IOException - if I/O error
446      */
createGoldenFile(String root, TestCase test, Object object)447     public static void createGoldenFile(String root, TestCase test, Object object)
448             throws IOException {
449         String goldenPath = (test.getClass().getName().replace('.', File.separatorChar)
450                              + ".golden.ser");
451         if (root != null) {
452             goldenPath = root + File.separatorChar + goldenPath;
453         }
454 
455         File goldenFile = new File(goldenPath);
456         goldenFile.getParentFile().mkdirs();
457         assertTrue("Could not create " + goldenFile.getParentFile(),
458                    goldenFile.getParentFile().isDirectory());
459         goldenFile.createNewFile();
460         putObjectToStream(object, new FileOutputStream(goldenFile));
461 
462         // don't forget to remove it from test case after using
463         fail("Generating golden file. Golden file name: " + goldenFile.getAbsolutePath());
464     }
465 
466     /**
467      * Copies an object by serializing/deserializing it.
468      *
469      * @param initial - an object to be copied
470      * @return copy of provided object
471      */
copySerializable(Serializable initial)472     public static Serializable copySerializable(Serializable initial)
473             throws IOException, ClassNotFoundException {
474         ByteArrayOutputStream out = new ByteArrayOutputStream();
475         putObjectToStream(initial, out);
476         ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
477         return getObjectFromStream(in);
478     }
479 }
480