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