Home | History | Annotate | Download | only in serialization
      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      */
     94     public SerializationTest(String name) {
     95         super(name);
     96     }
     97 
     98     /**
     99      * Default c-tor inherited from superclass.
    100      */
    101     public SerializationTest() {
    102         super();
    103     }
    104 
    105     /**
    106      * Depending on testing mode, produces golden files or performs testing.
    107      */
    108     @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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          */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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