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