• 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 dalvik.annotation.BrokenTest;
26 import dalvik.annotation.TestLevel;
27 import dalvik.annotation.TestTargetClass;
28 import dalvik.annotation.TestTargetNew;
29 
30 import java.io.ByteArrayInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.File;
33 import java.io.FileOutputStream;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.ObjectInputStream;
37 import java.io.ObjectOutputStream;
38 import java.io.OutputStream;
39 import java.io.Serializable;
40 import java.lang.reflect.Method;
41 import java.security.Permission;
42 import java.security.PermissionCollection;
43 import java.util.Collection;
44 import java.util.Collections;
45 import java.util.HashSet;
46 
47 import junit.framework.Assert;
48 import junit.framework.TestCase;
49 
50 /**
51  * Framework for serialization testing. Subclasses only need to override
52  * getData() method and, optionally, assertDeserialized() method. The first one
53  * returns array of objects to be de/serialized in tests, and the second
54  * compares reference and deserialized objects (needed only if tested objects do
55  * not provide specific method equals()). <br>
56  * There are two modes of test run: <b>reference generation mode </b> and
57  * <b>testing mode </b>. The actual mode is selected via
58  * <b>&quot;test.mode&quot; </b> system property. The <b>testing mode </b> is
59  * the default mode. <br>
60  * To turn on the <b>reference generation mode </b>, the test.mode property
61  * should be set to value &quot;serial.reference&quot;. In this mode, no testing
62  * is performed but golden files are produced, which contain reference
63  * serialized objects. This mode should be run on a pure
64  * Implementation classes, which are targeted for compartibility. <br>
65  * The location of golden files (in both modes) is controlled via
66  * <b>&quot;RESOURCE_DIR&quot; </b> system property.
67  *
68  */
69 @TestTargetClass(Serializable.class)
70 public abstract class SerializationTest extends TestCase {
71 
72     /**
73      * Property name for the testing mode.
74      */
75     public static final String MODE_KEY = "test.mode";
76 
77 
78     /**
79      * Testing mode.
80      */
81     public static String mode = System.getProperty(MODE_KEY);
82 
83     /**
84      * Reference files generation mode switch.
85      */
86     public static final String SERIAL_REFERENCE_MODE = "serial.reference";
87 
88     /**
89      * Key to a system property defining root location of golden files.
90      */
91     public static final String GOLDEN_PATH = "RESOURCE_DIR";
92 
93     private static final String outputPath = System.getProperty(GOLDEN_PATH,
94             "src/test/resources/serialization");
95 
96     /**
97      * Parameterized c-tor inherited from superclass.
98      */
SerializationTest(String name)99     public SerializationTest(String name) {
100         super(name);
101     }
102 
103     /**
104      * Default c-tor inherited from superclass.
105      */
SerializationTest()106     public SerializationTest() {
107         super();
108     }
109 
110     /**
111      * Depending on testing mode, produces golden files or performs testing.
112      */
113     @Override
runBare()114     public void runBare() throws Throwable {
115 
116         if (mode != null && mode.equals(SERIAL_REFERENCE_MODE)) {
117             produceGoldenFiles();
118         } else {
119             super.runBare();
120         }
121     }
122 
123     /**
124      * This is the main working method of this framework. Subclasses must
125      * override it to provide actual objects for testing.
126      *
127      * @return array of objects to be de/serialized in tests.
128      */
getData()129     protected abstract Object[] getData();
130 
131     /**
132      * Tests that data objects can be serialized and deserialized without
133      * exceptions, and that deserialization really produces deeply cloned
134      * objects.
135      */
136     @TestTargetNew(
137         level = TestLevel.ADDITIONAL,
138         notes = "",
139         method = "!Serialization",
140         args = {}
141     )
testSelf()142     public void testSelf() throws Throwable {
143 
144         if (this instanceof SerializableAssert) {
145             verifySelf(getData(), (SerializableAssert) this);
146         } else {
147             verifySelf(getData());
148 
149         }
150     }
151 
152     /**
153      * Tests that data objects can be deserialized from golden files, to verify
154      * compatibility with Reference Implementation.
155      */
156 
157     @TestTargetNew(
158         level = TestLevel.ADDITIONAL,
159         notes = "",
160         method = "!Serialization",
161         args = {}
162     )
testGolden()163     public void testGolden() throws Throwable {
164 
165         verifyGolden(this, getData());
166     }
167 
168     /**
169      * Returns golden file for an object being tested.
170      *
171      * @param index array index of tested data (as returned by
172      *        {@link #getData() getData()})
173      * @return corresponding golden file
174      */
getDataFile(int index)175     protected File getDataFile(int index) {
176         String name = this.getClass().getName();
177         int dot = name.lastIndexOf(".");
178         String path = name.substring(0, dot).replace('.', File.separatorChar);
179         if (outputPath != null && outputPath.length() != 0) {
180             path = outputPath + File.separator + path;
181         }
182 
183         return new File(path, name.substring(dot + 1) + "." + index + ".dat");
184     }
185 
186     /**
187      * Working method for files generation mode. Serializes test objects
188      * returned by {@link #getData() getData()}to golden files, each object to
189      * a separate file.
190      *
191      * @throws IOException
192      */
produceGoldenFiles()193     protected void produceGoldenFiles() throws IOException {
194 
195         String goldenPath = outputPath + File.separatorChar
196                 + getClass().getName().replace('.', File.separatorChar)
197                 + ".golden.";
198 
199         Object[] data = getData();
200         for (int i = 0; i < data.length; i++) {
201 
202             File goldenFile = new File(goldenPath + i + ".ser");
203             goldenFile.getParentFile().mkdirs();
204             goldenFile.createNewFile();
205 
206             putObjectToStream(data[i], new FileOutputStream(goldenFile));
207         }
208     }
209 
210     /**
211      * Serializes specified object to an output stream.
212      */
putObjectToStream(Object obj, OutputStream os)213     public static void putObjectToStream(Object obj, OutputStream os)
214         throws IOException {
215         ObjectOutputStream oos = new ObjectOutputStream(os);
216         oos.writeObject(obj);
217         oos.flush();
218         oos.close();
219     }
220 
221     /**
222      * Deserializes single object from an input stream.
223      */
getObjectFromStream(InputStream is)224     public static Serializable getObjectFromStream(InputStream is) throws IOException,
225         ClassNotFoundException {
226         ObjectInputStream ois = new ObjectInputStream(is);
227         Object result = ois.readObject();
228         ois.close();
229         return (Serializable)result;
230     }
231 
232     /**
233      * Interface to compare (de)serialized objects
234      *
235      * Should be implemented if a class under test does not provide specific
236      * equals() method and it's instances should to be compared manually.
237      */
238     public interface SerializableAssert {
239 
240         /**
241          * Compares deserialized and reference objects.
242          *
243          * @param initial -
244          *            initial object used for creating serialized form
245          * @param deserialized -
246          *            deserialized object
247          */
assertDeserialized(Serializable initial, Serializable deserialized)248         void assertDeserialized(Serializable initial, Serializable deserialized);
249     }
250 
251     // default comparator for a class that has equals(Object) method
252     private final static SerializableAssert DEFAULT_COMPARATOR = new SerializableAssert() {
253         public void assertDeserialized(Serializable initial,
254                 Serializable deserialized) {
255 
256             Assert.assertEquals(initial, deserialized);
257         }
258     };
259 
260     /**
261      * Comparator for verifying that deserialized object is the same as initial.
262      */
263     public final static SerializableAssert SAME_COMPARATOR = new SerializableAssert() {
264         public void assertDeserialized(Serializable initial,
265                 Serializable deserialized) {
266 
267             Assert.assertSame(initial, deserialized);
268         }
269     };
270 
271     /**
272      * Comparator for java.lang.Throwable objects
273      */
274     public final static SerializableAssert THROWABLE_COMPARATOR = new SerializableAssert() {
275         public void assertDeserialized(Serializable initial, Serializable deserialized) {
276 
277             Throwable initThr = (Throwable) initial;
278             Throwable dserThr = (Throwable) deserialized;
279 
280             // verify class
281             Assert.assertEquals(initThr.getClass(), dserThr.getClass());
282 
283             // verify message
284             Assert.assertEquals(initThr.getMessage(), dserThr.getMessage());
285 
286             // verify cause
287             if (initThr.getCause() == null) {
288                 Assert.assertNull(dserThr.getCause());
289             } else {
290                 Assert.assertNotNull(dserThr.getCause());
291 
292                 THROWABLE_COMPARATOR.assertDeserialized(initThr.getCause(),
293                         dserThr.getCause());
294             }
295         }
296     };
297 
298     /**
299      * Comparator for java.security.PermissionCollection objects
300      */
301     public final static SerializableAssert PERMISSION_COLLECTION_COMPARATOR = new SerializableAssert() {
302         public void assertDeserialized(Serializable initial, Serializable deserialized) {
303 
304             PermissionCollection initPC = (PermissionCollection) initial;
305             PermissionCollection dserPC = (PermissionCollection) deserialized;
306 
307             // verify class
308             Assert.assertEquals(initPC.getClass(), dserPC.getClass());
309 
310             // verify 'readOnly' field
311             Assert.assertEquals(initPC.isReadOnly(), dserPC.isReadOnly());
312 
313             // verify collection of permissions
314             Collection<Permission> refCollection = new HashSet<Permission>(
315                     Collections.list(initPC.elements()));
316             Collection<Permission> tstCollection = new HashSet<Permission>(
317                     Collections.list(dserPC.elements()));
318 
319             Assert.assertEquals(refCollection, tstCollection);
320         }
321     };
322 
323     /**
324      * Returns <code>comparator</code> for provided serializable
325      * <code>object</code>.
326      *
327      * The <code>comparator</code> is searched in the following order: <br>-
328      * if <code>test</code> implements SerializableAssert interface then it is
329      * selected as </code>comparator</code>.<br>- if passed <code>object</code>
330      * has class in its classes hierarchy that overrides <code>equals(Object)</code>
331      * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the
332      * method tries to select one of known comparators basing on <code>object's</code>
333      * class,for example, if passed <code>object</code> is instance of
334      * java.lang.Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>-
335      * otherwise RuntimeException is thrown
336      *
337      * @param test -
338      *            test case
339      * @param object -
340      *            object to be compared
341      * @return object's comparator
342      */
defineComparator(TestCase test, Object object)343     public static SerializableAssert defineComparator(TestCase test,
344             Object object) throws Exception {
345 
346         if (test instanceof SerializableAssert) {
347             return (SerializableAssert) test;
348         }
349 
350         Method m = object.getClass().getMethod("equals",
351                 new Class[] { Object.class });
352 
353         if (m.getDeclaringClass() != Object.class) {
354             // one of classes overrides Object.equals(Object) method
355             // use default comparator
356             return DEFAULT_COMPARATOR;
357         }
358 
359         // TODO use generics to detect comparator
360         // instead of 'instanceof' for the first element
361         if (object instanceof java.lang.Throwable) {
362             return THROWABLE_COMPARATOR;
363         } else if (object instanceof java.security.PermissionCollection) {
364             return PERMISSION_COLLECTION_COMPARATOR;
365         }
366 
367         throw new RuntimeException("Failed to detect comparator");
368     }
369 
370     /**
371      * Verifies that object deserialized from golden file correctly.
372      *
373      * The method invokes <br>
374      * verifyGolden(test, object, defineComparator(test, object));
375      *
376      * @param test -
377      *            test case
378      * @param object -
379      *            to be compared
380      */
verifyGolden(TestCase test, Object object)381     public static void verifyGolden(TestCase test, Object object)
382             throws Exception {
383 
384         verifyGolden(test, object, defineComparator(test, object));
385     }
386 
387     /**
388      * Verifies that object deserialized from golden file correctly.
389      *
390      * The method loads "<code>testName</code>.golden.ser" resource file
391      * from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
392      * folder, reads an object from the loaded file and compares it with
393      * <code>object</code> using specified <code>comparator</code>.
394      *
395      * @param test-
396      *            test case
397      * @param object-
398      *            to be compared
399      * @param comparator -
400      *            for comparing (de)serialized objects
401      */
verifyGolden(TestCase test, Object object, SerializableAssert comparator)402     public static void verifyGolden(TestCase test, Object object,
403             SerializableAssert comparator) throws Exception {
404 
405         Assert.assertNotNull("Null comparator", comparator);
406 
407         Serializable deserialized = getObject(test, ".golden.ser");
408 
409         comparator.assertDeserialized((Serializable) object, deserialized);
410     }
411 
412     /**
413      * Verifies that objects from array deserialized from golden files
414      * correctly.
415      *
416      * The method invokes <br>
417      * verifyGolden(test, objects, defineComparator(test, object[0]));
418      *
419      * @param test -
420      *            test case
421      * @param objects -
422      *            array of objects to be compared
423      */
verifyGolden(TestCase test, Object[] objects)424     public static void verifyGolden(TestCase test, Object[] objects)
425             throws Exception {
426 
427         Assert.assertFalse("Empty array", objects.length == 0);
428         verifyGolden(test, objects, defineComparator(test, objects[0]));
429     }
430 
431     /**
432      * Verifies that objects from array deserialized from golden files
433      * correctly.
434      *
435      * The method loads "<code>testName</code>.golden.<code>N</code>.ser"
436      * resource files from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
437      * folder, from each loaded file it reads an object from and compares it
438      * with corresponding object in provided array (i.e. <code>objects[N]</code>)
439      * using specified <code>comparator</code>. (<code>N</code> is index
440      * in object's array.)
441      *
442      * @param test-
443      *            test case
444      * @param objects -
445      *            array of objects to be compared
446      * @param comparator -
447      *            for comparing (de)serialized objects
448      */
verifyGolden(TestCase test, Object[] objects, SerializableAssert comparator)449     public static void verifyGolden(TestCase test, Object[] objects,
450             SerializableAssert comparator) throws Exception {
451 
452         Assert.assertFalse("Empty array", objects.length == 0);
453         for (int i = 0; i < objects.length; i++) {
454             Serializable deserialized = getObject(test, ".golden." + i + ".ser");
455             comparator.assertDeserialized((Serializable) objects[i],
456                     deserialized);
457         }
458     }
459 
460     /**
461      * Verifies that object can be smoothly serialized/deserialized.
462      *
463      * The method invokes <br>
464      * verifySelf(object, defineComparator(null, object));
465      *
466      * @param object -
467      *            to be serialized/deserialized
468      */
verifySelf(Object object)469     public static void verifySelf(Object object)
470             throws Exception {
471 
472         verifySelf(object, defineComparator(null, object));
473     }
474 
475     /**
476      * Verifies that object can be smoothly serialized/deserialized.
477      *
478      * The method serialize/deserialize <code>object</code> and compare it
479      * with initial <code>object</code>.
480      *
481      * @param object -
482      *            object to be serialized/deserialized
483      * @param comparator -
484      *            for comparing serialized/deserialized object with initial
485      *            object
486      */
verifySelf(Object object, SerializableAssert comparator)487     public static void verifySelf(Object object, SerializableAssert comparator)
488             throws Exception {
489 
490         Serializable initial = (Serializable) object;
491 
492         comparator.assertDeserialized(initial, copySerializable(initial));
493     }
494 
495     /**
496      * Verifies that that objects from array can be smoothly
497      * serialized/deserialized.
498      *
499      * The method invokes <br>
500      * verifySelf(objects, defineComparator(null, object[0]));
501      *
502      * @param objects -
503      *            array of objects to be serialized/deserialized
504      */
verifySelf(Object[] objects)505     public static void verifySelf(Object[] objects)
506             throws Exception {
507 
508         Assert.assertFalse("Empty array", objects.length == 0);
509         verifySelf(objects, defineComparator(null, objects[0]));
510     }
511 
512     /**
513      * Verifies that that objects from array can be smoothly
514      * serialized/deserialized.
515      *
516      * The method serialize/deserialize each object in <code>objects</code>
517      * array and compare it with initial object.
518      *
519      * @param objects -
520      *            array of objects to be serialized/deserialized
521      * @param comparator -
522      *            for comparing serialized/deserialized object with initial
523      *            object
524      */
verifySelf(Object[] objects, SerializableAssert comparator)525     public static void verifySelf(Object[] objects, SerializableAssert comparator)
526             throws Exception {
527 
528         Assert.assertFalse("Empty array", objects.length == 0);
529         for(Object entry: objects){
530             verifySelf(entry, comparator);
531         }
532     }
533 
getObject(TestCase test, String toAppend)534     private static Serializable getObject(TestCase test, String toAppend)
535             throws Exception {
536 
537         StringBuilder path = new StringBuilder("/serialization");
538 
539         path.append(File.separatorChar);
540         path.append(test.getClass().getName().replace('.', File.separatorChar));
541         path.append(toAppend);
542 
543         InputStream in = SerializationTest.class
544                 .getResourceAsStream(path.toString());
545 
546         Assert.assertNotNull("Failed to load serialization resource file: "
547                 + path, in);
548 
549         return getObjectFromStream(in);
550     }
551 
552     /**
553      * Creates golden file.
554      *
555      * The folder for created file is: <code>root + test's package name</code>.
556      * The file name is: <code>test's name + "golden.ser"</code>
557      *
558      * @param root -
559      *            root directory for serialization resource files
560      * @param test -
561      *            test case
562      * @param object -
563      *            object to be serialized
564      * @throws IOException -
565      *             if I/O error
566      */
createGoldenFile(String root, TestCase test, Object object)567     public static void createGoldenFile(String root, TestCase test,
568             Object object) throws IOException {
569 
570         String goldenPath = test.getClass().getName().replace('.',
571                 File.separatorChar)
572                 + ".golden.ser";
573 
574         if (root != null) {
575             goldenPath = root + File.separatorChar + goldenPath;
576         }
577 
578 
579         File goldenFile = new File(goldenPath);
580         goldenFile.getParentFile().mkdirs();
581         goldenFile.createNewFile();
582 
583         putObjectToStream(object, new FileOutputStream(goldenFile));
584 
585         // don't forget to remove it from test case after using
586         Assert.fail("Generating golden file.\nGolden file name:"
587                 + goldenFile.getAbsolutePath());
588     }
589 
590     /**
591      * Copies an object by serializing/deserializing it.
592      *
593      * @param initial -
594      *            an object to be copied
595      * @return copy of provided object
596      */
copySerializable(Serializable initial)597     public static Serializable copySerializable(Serializable initial)
598             throws IOException, ClassNotFoundException {
599 
600         ByteArrayOutputStream out = new ByteArrayOutputStream();
601         putObjectToStream(initial, out);
602         ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
603 
604         return getObjectFromStream(in);
605     }
606 }
607