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