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