• 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 java.io.ByteArrayInputStream;
26 import java.io.ByteArrayOutputStream;
27 import java.io.File;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.ObjectInputStream;
32 import java.io.ObjectOutputStream;
33 import java.io.OutputStream;
34 import java.io.Serializable;
35 import java.lang.reflect.Method;
36 import java.security.Permission;
37 import java.security.PermissionCollection;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import junit.framework.TestCase;
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 
102         verifyGolden(this, getData());
103     }
104 
105     /**
106      * Returns golden file for an object being tested.
107      *
108      * @param index array index of tested data (as returned by
109      *        {@link #getData() getData()})
110      * @return corresponding golden file
111      */
getDataFile(int index)112     protected File getDataFile(int index) {
113         String name = this.getClass().getName();
114         int dot = name.lastIndexOf(".");
115         String path = name.substring(0, dot).replace('.', File.separatorChar);
116         if (outputPath != null && outputPath.length() != 0) {
117             path = outputPath + File.separator + path;
118         }
119 
120         return new File(path, name.substring(dot + 1) + "." + index + ".dat");
121     }
122 
123     /**
124      * Working method for files generation mode. Serializes test objects
125      * returned by {@link #getData() getData()}to golden files, each object to
126      * a separate file.
127      *
128      * @throws IOException
129      */
produceGoldenFiles()130     protected void produceGoldenFiles() throws IOException {
131 
132         String goldenPath = outputPath + File.separatorChar
133                 + getClass().getName().replace('.', File.separatorChar)
134                 + ".golden.";
135 
136         Object[] data = getData();
137         for (int i = 0; i < data.length; i++) {
138 
139             File goldenFile = new File(goldenPath + i + ".ser");
140             goldenFile.getParentFile().mkdirs();
141             goldenFile.createNewFile();
142 
143             putObjectToStream(data[i], new FileOutputStream(goldenFile));
144         }
145     }
146 
147     /**
148      * Serializes specified object to an output stream.
149      */
putObjectToStream(Object obj, OutputStream os)150     public static void putObjectToStream(Object obj, OutputStream os)
151             throws IOException {
152         ObjectOutputStream oos = new ObjectOutputStream(os);
153         oos.writeObject(obj);
154         oos.flush();
155         oos.close();
156     }
157 
158     /**
159      * Deserializes single object from an input stream.
160      */
getObjectFromStream(InputStream is)161     public static Serializable getObjectFromStream(InputStream is)
162             throws IOException, ClassNotFoundException {
163         ObjectInputStream ois = new ObjectInputStream(is);
164         Object result = ois.readObject();
165         ois.close();
166         return (Serializable)result;
167     }
168 
169     /**
170      * Interface to compare (de)serialized objects
171      *
172      * Should be implemented if a class under test does not provide specific
173      * equals() method and it's instances should to be compared manually.
174      */
175     public interface SerializableAssert {
176 
177         /**
178          * Compares deserialized and reference objects.
179          *
180          * @param initial - initial object used for creating serialized form
181          * @param deserialized - deserialized object
182          */
assertDeserialized(Serializable initial, Serializable deserialized)183         void assertDeserialized(Serializable initial, Serializable deserialized);
184     }
185 
186     // default comparator for a class that has equals(Object) method
187     private final static SerializableAssert DEFAULT_COMPARATOR = new SerializableAssert() {
188         public void assertDeserialized(Serializable initial, Serializable deserialized) {
189             assertEquals(initial, deserialized);
190         }
191     };
192 
193     /**
194      * Comparator for verifying that deserialized object is the same as initial.
195      */
196     public final static SerializableAssert SAME_COMPARATOR = new SerializableAssert() {
197         public void assertDeserialized(Serializable initial, Serializable deserialized) {
198             assertSame(initial, deserialized);
199         }
200     };
201 
202     /**
203      * Comparator for Throwable objects
204      */
205     public final static SerializableAssert THROWABLE_COMPARATOR = new SerializableAssert() {
206         public void assertDeserialized(Serializable initial, Serializable deserialized) {
207             Throwable initThr = (Throwable) initial;
208             Throwable dserThr = (Throwable) deserialized;
209 
210             // verify class
211             assertEquals(initThr.getClass(), dserThr.getClass());
212 
213             // verify message
214             assertEquals(initThr.getMessage(), dserThr.getMessage());
215 
216             // verify cause
217             if (initThr.getCause() == null) {
218                 assertNull(dserThr.getCause());
219             } else {
220                 assertNotNull(dserThr.getCause());
221                 THROWABLE_COMPARATOR.assertDeserialized(initThr.getCause(),
222                         dserThr.getCause());
223             }
224         }
225     };
226 
227     /**
228      * Comparator for PermissionCollection objects
229      */
230     public final static SerializableAssert PERMISSION_COLLECTION_COMPARATOR = new SerializableAssert() {
231         public void assertDeserialized(Serializable initial, Serializable deserialized) {
232 
233             PermissionCollection initPC = (PermissionCollection) initial;
234             PermissionCollection dserPC = (PermissionCollection) deserialized;
235 
236             // verify class
237             assertEquals(initPC.getClass(), dserPC.getClass());
238 
239             // verify 'readOnly' field
240             assertEquals(initPC.isReadOnly(), dserPC.isReadOnly());
241 
242             // verify collection of permissions
243             Collection<Permission> refCollection = new HashSet<Permission>(
244                     Collections.list(initPC.elements()));
245             Collection<Permission> tstCollection = new HashSet<Permission>(
246                     Collections.list(dserPC.elements()));
247 
248             assertEquals(refCollection, tstCollection);
249         }
250     };
251 
252     /**
253      * Returns <code>comparator</code> for provided serializable
254      * <code>object</code>.
255      *
256      * The <code>comparator</code> is searched in the following order: <br>
257      * - if <code>test</code> implements SerializableAssert interface then it is
258      * selected as </code>comparator</code>.<br>- if passed <code>object</code>
259      * has class in its classes hierarchy that overrides <code>equals(Object)</code>
260      * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the
261      * method tries to select one of known comparators basing on <code>object's</code>
262      * class,for example, if passed <code>object</code> is instance of
263      * Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>
264      * - otherwise RuntimeException is thrown
265      *
266      * @param test - test case
267      * @param object - object to be compared
268      * @return object's comparator
269      */
defineComparator(TestCase test, Object object)270     public static SerializableAssert defineComparator(TestCase test, Object object)
271             throws Exception {
272 
273         if (test instanceof SerializableAssert) {
274             return (SerializableAssert) test;
275         }
276 
277         Method m = object.getClass().getMethod("equals", new Class[] { Object.class });
278         if (m.getDeclaringClass() != Object.class) {
279             // one of classes overrides Object.equals(Object) method
280             // use default comparator
281             return DEFAULT_COMPARATOR;
282         }
283 
284         // TODO use generics to detect comparator
285         // instead of 'instanceof' for the first element
286         if (object instanceof Throwable) {
287             return THROWABLE_COMPARATOR;
288         }
289         if (object instanceof PermissionCollection) {
290             return PERMISSION_COLLECTION_COMPARATOR;
291         }
292         throw new RuntimeException("Failed to detect comparator");
293     }
294 
295     /**
296      * Verifies that object deserialized from golden file correctly.
297      *
298      * The method invokes <br>
299      * verifyGolden(test, object, defineComparator(test, object));
300      *
301      * @param test - test case
302      * @param object - to be compared
303      */
verifyGolden(TestCase test, Object object)304     public static void verifyGolden(TestCase test, Object object) throws Exception {
305         verifyGolden(test, object, defineComparator(test, object));
306     }
307 
308     /**
309      * Verifies that object deserialized from golden file correctly.
310      *
311      * The method loads "<code>testName</code>.golden.ser" resource file
312      * from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
313      * folder, reads an object from the loaded file and compares it with
314      * <code>object</code> using specified <code>comparator</code>.
315      *
316      * @param test - test case
317      * @param object - to be compared
318      * @param comparator - for comparing (de)serialized objects
319      */
verifyGolden(TestCase test, Object object, SerializableAssert comparator)320     public static void verifyGolden(TestCase test, Object object, SerializableAssert comparator)
321             throws Exception {
322         assertNotNull("Null comparator", comparator);
323         Serializable deserialized = getObject(test, ".golden.ser");
324         comparator.assertDeserialized((Serializable) object, deserialized);
325     }
326 
327     /**
328      * Verifies that objects from array deserialized from golden files
329      * correctly.
330      *
331      * The method invokes <br>
332      * verifyGolden(test, objects, defineComparator(test, object[0]));
333      *
334      * @param test - test case
335      * @param objects - array of objects to be compared
336      */
verifyGolden(TestCase test, Object[] objects)337     public static void verifyGolden(TestCase test, Object[] objects) throws Exception {
338         assertFalse("Empty array", objects.length == 0);
339         verifyGolden(test, objects, defineComparator(test, objects[0]));
340     }
341 
342     /**
343      * Verifies that objects from array deserialized from golden files
344      * correctly.
345      *
346      * The method loads "<code>testName</code>.golden.<code>N</code>.ser"
347      * resource files from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
348      * folder, from each loaded file it reads an object from and compares it
349      * with corresponding object in provided array (i.e. <code>objects[N]</code>)
350      * using specified <code>comparator</code>. (<code>N</code> is index
351      * in object's array.)
352      *
353      * @param test - test case
354      * @param objects - array of objects to be compared
355      * @param comparator - for comparing (de)serialized objects
356      */
verifyGolden(TestCase test, Object[] objects, SerializableAssert comparator)357     public static void verifyGolden(TestCase test, Object[] objects, SerializableAssert comparator)
358             throws Exception {
359         assertFalse("Empty array", objects.length == 0);
360         for (int i = 0; i < objects.length; i++) {
361             Serializable deserialized = getObject(test, ".golden." + i + ".ser");
362             comparator.assertDeserialized((Serializable) objects[i], deserialized);
363         }
364     }
365 
366     /**
367      * Verifies that object can be smoothly serialized/deserialized.
368      *
369      * The method invokes <br>
370      * verifySelf(object, defineComparator(null, object));
371      *
372      * @param object - to be serialized/deserialized
373      */
verifySelf(Object object)374     public static void verifySelf(Object object) throws Exception {
375         verifySelf(object, defineComparator(null, object));
376     }
377 
378     /**
379      * Verifies that object can be smoothly serialized/deserialized.
380      *
381      * The method serialize/deserialize <code>object</code> and compare it
382      * with initial <code>object</code>.
383      *
384      * @param object - object to be serialized/deserialized
385      * @param comparator - for comparing serialized/deserialized object with initial object
386      */
verifySelf(Object object, SerializableAssert comparator)387     public static void verifySelf(Object object, SerializableAssert comparator) throws Exception {
388         Serializable initial = (Serializable) object;
389         comparator.assertDeserialized(initial, copySerializable(initial));
390     }
391 
392     /**
393      * Verifies that that objects from array can be smoothly
394      * serialized/deserialized.
395      *
396      * The method invokes <br>
397      * verifySelf(objects, defineComparator(null, object[0]));
398      *
399      * @param objects - array of objects to be serialized/deserialized
400      */
verifySelf(Object[] objects)401     public static void verifySelf(Object[] objects) throws Exception {
402         assertFalse("Empty array", objects.length == 0);
403         verifySelf(objects, defineComparator(null, objects[0]));
404     }
405 
406     /**
407      * Verifies that that objects from array can be smoothly
408      * serialized/deserialized.
409      *
410      * The method serialize/deserialize each object in <code>objects</code>
411      * array and compare it with initial object.
412      *
413      * @param objects - array of objects to be serialized/deserialized
414      * @param comparator - for comparing serialized/deserialized object with initial object
415      */
verifySelf(Object[] objects, SerializableAssert comparator)416     public static void verifySelf(Object[] objects, SerializableAssert comparator)
417             throws Exception {
418         assertFalse("Empty array", objects.length == 0);
419         for (Object entry: objects){
420             verifySelf(entry, comparator);
421         }
422     }
423 
getObject(TestCase test, String toAppend)424     private static Serializable getObject(TestCase test, String toAppend) throws Exception {
425         StringBuilder path = new StringBuilder("/serialization");
426         path.append(File.separatorChar);
427         path.append(test.getClass().getName().replace('.', File.separatorChar));
428         path.append(toAppend);
429 
430         InputStream in = SerializationTest.class.getResourceAsStream(path.toString());
431         assertNotNull("Failed to load serialization resource file: " + path, in);
432         return getObjectFromStream(in);
433     }
434 
435     /**
436      * Creates golden file.
437      *
438      * The folder for created file is: <code>root + test's package name</code>.
439      * The file name is: <code>test's name + "golden.ser"</code>
440      *
441      * @param root - root directory for serialization resource files
442      * @param test - test case
443      * @param object - to be serialized
444      * @throws IOException - if I/O error
445      */
createGoldenFile(String root, TestCase test, Object object)446     public static void createGoldenFile(String root, TestCase test, Object object)
447             throws IOException {
448         String goldenPath = (test.getClass().getName().replace('.', File.separatorChar)
449                              + ".golden.ser");
450         if (root != null) {
451             goldenPath = root + File.separatorChar + goldenPath;
452         }
453 
454         File goldenFile = new File(goldenPath);
455         goldenFile.getParentFile().mkdirs();
456         assertTrue("Could not create " + goldenFile.getParentFile(),
457                    goldenFile.getParentFile().isDirectory());
458         goldenFile.createNewFile();
459         putObjectToStream(object, new FileOutputStream(goldenFile));
460 
461         // don't forget to remove it from test case after using
462         fail("Generating golden file. Golden file name: " + goldenFile.getAbsolutePath());
463     }
464 
465     /**
466      * Copies an object by serializing/deserializing it.
467      *
468      * @param initial - an object to be copied
469      * @return copy of provided object
470      */
copySerializable(Serializable initial)471     public static Serializable copySerializable(Serializable initial)
472             throws IOException, ClassNotFoundException {
473         ByteArrayOutputStream out = new ByteArrayOutputStream();
474         putObjectToStream(initial, out);
475         ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
476         return getObjectFromStream(in);
477     }
478 }
479