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.Assert;
     42 import junit.framework.TestCase;
     43 import libcore.base.Objects;
     44 
     45 /**
     46  * Framework for serialization testing. Subclasses only need to override
     47  * getData() method and, optionally, assertDeserialized() method. The first one
     48  * returns array of objects to be de/serialized in tests, and the second
     49  * compares reference and deserialized objects (needed only if tested objects do
     50  * not provide specific method equals()). <br>
     51  * There are two modes of test run: <b>reference generation mode </b> and
     52  * <b>testing mode </b>. The actual mode is selected via
     53  * <b>&quot;test.mode&quot; </b> system property. The <b>testing mode </b> is
     54  * the default mode. <br>
     55  * To turn on the <b>reference generation mode </b>, the test.mode property
     56  * should be set to value &quot;serial.reference&quot;. In this mode, no testing
     57  * is performed but golden files are produced, which contain reference
     58  * serialized objects. This mode should be run on a pure
     59  * Implementation classes, which are targeted for compartibility. <br>
     60  * The location of golden files (in both modes) is controlled via
     61  * <b>&quot;RESOURCE_DIR&quot; </b> system property.
     62  *
     63  */
     64 public abstract class SerializationTest extends TestCase {
     65 
     66     /**
     67      * Property name for the testing mode.
     68      */
     69     public static final String MODE_KEY = "test.mode";
     70 
     71 
     72     /**
     73      * Testing mode.
     74      */
     75     public static String mode = System.getProperty(MODE_KEY);
     76 
     77     /**
     78      * Reference files generation mode switch.
     79      */
     80     public static final String SERIAL_REFERENCE_MODE = "serial.reference";
     81 
     82     /**
     83      * Key to a system property defining root location of golden files.
     84      */
     85     public static final String GOLDEN_PATH = "RESOURCE_DIR";
     86 
     87     private static final String outputPath = System.getProperty(GOLDEN_PATH,
     88             "src/test/resources/serialization");
     89 
     90     /**
     91      * Parameterized c-tor inherited from superclass.
     92      */
     93     public SerializationTest(String name) {
     94         super(name);
     95     }
     96 
     97     /**
     98      * Default c-tor inherited from superclass.
     99      */
    100     public SerializationTest() {
    101         super();
    102     }
    103 
    104     /**
    105      * Depending on testing mode, produces golden files or performs testing.
    106      */
    107     @Override
    108     public void runBare() throws Throwable {
    109 
    110         if (Objects.equal(mode, SERIAL_REFERENCE_MODE)) {
    111             produceGoldenFiles();
    112         } else {
    113             super.runBare();
    114         }
    115     }
    116 
    117     /**
    118      * This is the main working method of this framework. Subclasses must
    119      * override it to provide actual objects for testing.
    120      *
    121      * @return array of objects to be de/serialized in tests.
    122      */
    123     protected abstract Object[] getData();
    124 
    125     /**
    126      * Tests that data objects can be serialized and deserialized without
    127      * exceptions, and that deserialization really produces deeply cloned
    128      * objects.
    129      */
    130     public void testSelf() throws Throwable {
    131 
    132         if (this instanceof SerializableAssert) {
    133             verifySelf(getData(), (SerializableAssert) this);
    134         } else {
    135             verifySelf(getData());
    136 
    137         }
    138     }
    139 
    140     /**
    141      * Tests that data objects can be deserialized from golden files, to verify
    142      * compatibility with Reference Implementation.
    143      */
    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, tstCollection);
    302         }
    303     };
    304 
    305     /**
    306      * Returns <code>comparator</code> for provided serializable
    307      * <code>object</code>.
    308      *
    309      * The <code>comparator</code> is searched in the following order: <br>-
    310      * if <code>test</code> implements SerializableAssert interface then it is
    311      * selected as </code>comparator</code>.<br>- if passed <code>object</code>
    312      * has class in its classes hierarchy that overrides <code>equals(Object)</code>
    313      * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the
    314      * method tries to select one of known comparators basing on <code>object's</code>
    315      * class,for example, if passed <code>object</code> is instance of
    316      * java.lang.Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>-
    317      * otherwise RuntimeException is thrown
    318      *
    319      * @param test -
    320      *            test case
    321      * @param object -
    322      *            object to be compared
    323      * @return object's comparator
    324      */
    325     public static SerializableAssert defineComparator(TestCase test,
    326             Object object) throws Exception {
    327 
    328         if (test instanceof SerializableAssert) {
    329             return (SerializableAssert) test;
    330         }
    331 
    332         Method m = object.getClass().getMethod("equals",
    333                 new Class[] { Object.class });
    334 
    335         if (m.getDeclaringClass() != Object.class) {
    336             // one of classes overrides Object.equals(Object) method
    337             // use default comparator
    338             return DEFAULT_COMPARATOR;
    339         }
    340 
    341         // TODO use generics to detect comparator
    342         // instead of 'instanceof' for the first element
    343         if (object instanceof java.lang.Throwable) {
    344             return THROWABLE_COMPARATOR;
    345         } else if (object instanceof java.security.PermissionCollection) {
    346             return PERMISSION_COLLECTION_COMPARATOR;
    347         }
    348 
    349         throw new RuntimeException("Failed to detect comparator");
    350     }
    351 
    352     /**
    353      * Verifies that object deserialized from golden file correctly.
    354      *
    355      * The method invokes <br>
    356      * verifyGolden(test, object, defineComparator(test, object));
    357      *
    358      * @param test -
    359      *            test case
    360      * @param object -
    361      *            to be compared
    362      */
    363     public static void verifyGolden(TestCase test, Object object)
    364             throws Exception {
    365 
    366         verifyGolden(test, object, defineComparator(test, object));
    367     }
    368 
    369     /**
    370      * Verifies that object deserialized from golden file correctly.
    371      *
    372      * The method loads "<code>testName</code>.golden.ser" resource file
    373      * from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
    374      * folder, reads an object from the loaded file and compares it with
    375      * <code>object</code> using specified <code>comparator</code>.
    376      *
    377      * @param test-
    378      *            test case
    379      * @param object-
    380      *            to be compared
    381      * @param comparator -
    382      *            for comparing (de)serialized objects
    383      */
    384     public static void verifyGolden(TestCase test, Object object,
    385             SerializableAssert comparator) throws Exception {
    386 
    387         Assert.assertNotNull("Null comparator", comparator);
    388 
    389         Serializable deserialized = getObject(test, ".golden.ser");
    390 
    391         comparator.assertDeserialized((Serializable) object, deserialized);
    392     }
    393 
    394     /**
    395      * Verifies that objects from array deserialized from golden files
    396      * correctly.
    397      *
    398      * The method invokes <br>
    399      * verifyGolden(test, objects, defineComparator(test, object[0]));
    400      *
    401      * @param test -
    402      *            test case
    403      * @param objects -
    404      *            array of objects to be compared
    405      */
    406     public static void verifyGolden(TestCase test, Object[] objects)
    407             throws Exception {
    408 
    409         Assert.assertFalse("Empty array", objects.length == 0);
    410         verifyGolden(test, objects, defineComparator(test, objects[0]));
    411     }
    412 
    413     /**
    414      * Verifies that objects from array deserialized from golden files
    415      * correctly.
    416      *
    417      * The method loads "<code>testName</code>.golden.<code>N</code>.ser"
    418      * resource files from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
    419      * folder, from each loaded file it reads an object from and compares it
    420      * with corresponding object in provided array (i.e. <code>objects[N]</code>)
    421      * using specified <code>comparator</code>. (<code>N</code> is index
    422      * in object's array.)
    423      *
    424      * @param test-
    425      *            test case
    426      * @param objects -
    427      *            array of objects to be compared
    428      * @param comparator -
    429      *            for comparing (de)serialized objects
    430      */
    431     public static void verifyGolden(TestCase test, Object[] objects,
    432             SerializableAssert comparator) throws Exception {
    433 
    434         Assert.assertFalse("Empty array", objects.length == 0);
    435         for (int i = 0; i < objects.length; i++) {
    436             Serializable deserialized = getObject(test, ".golden." + i + ".ser");
    437             comparator.assertDeserialized((Serializable) objects[i],
    438                     deserialized);
    439         }
    440     }
    441 
    442     /**
    443      * Verifies that object can be smoothly serialized/deserialized.
    444      *
    445      * The method invokes <br>
    446      * verifySelf(object, defineComparator(null, object));
    447      *
    448      * @param object -
    449      *            to be serialized/deserialized
    450      */
    451     public static void verifySelf(Object object)
    452             throws Exception {
    453 
    454         verifySelf(object, defineComparator(null, object));
    455     }
    456 
    457     /**
    458      * Verifies that object can be smoothly serialized/deserialized.
    459      *
    460      * The method serialize/deserialize <code>object</code> and compare it
    461      * with initial <code>object</code>.
    462      *
    463      * @param object -
    464      *            object to be serialized/deserialized
    465      * @param comparator -
    466      *            for comparing serialized/deserialized object with initial
    467      *            object
    468      */
    469     public static void verifySelf(Object object, SerializableAssert comparator)
    470             throws Exception {
    471 
    472         Serializable initial = (Serializable) object;
    473 
    474         comparator.assertDeserialized(initial, copySerializable(initial));
    475     }
    476 
    477     /**
    478      * Verifies that that objects from array can be smoothly
    479      * serialized/deserialized.
    480      *
    481      * The method invokes <br>
    482      * verifySelf(objects, defineComparator(null, object[0]));
    483      *
    484      * @param objects -
    485      *            array of objects to be serialized/deserialized
    486      */
    487     public static void verifySelf(Object[] objects)
    488             throws Exception {
    489 
    490         Assert.assertFalse("Empty array", objects.length == 0);
    491         verifySelf(objects, defineComparator(null, objects[0]));
    492     }
    493 
    494     /**
    495      * Verifies that that objects from array can be smoothly
    496      * serialized/deserialized.
    497      *
    498      * The method serialize/deserialize each object in <code>objects</code>
    499      * array and compare it with initial object.
    500      *
    501      * @param objects -
    502      *            array of objects to be serialized/deserialized
    503      * @param comparator -
    504      *            for comparing serialized/deserialized object with initial
    505      *            object
    506      */
    507     public static void verifySelf(Object[] objects, SerializableAssert comparator)
    508             throws Exception {
    509 
    510         Assert.assertFalse("Empty array", objects.length == 0);
    511         for(Object entry: objects){
    512             verifySelf(entry, comparator);
    513         }
    514     }
    515 
    516     private static Serializable getObject(TestCase test, String toAppend)
    517             throws Exception {
    518 
    519         StringBuilder path = new StringBuilder("/serialization");
    520 
    521         path.append(File.separatorChar);
    522         path.append(test.getClass().getName().replace('.', File.separatorChar));
    523         path.append(toAppend);
    524 
    525         InputStream in = SerializationTest.class
    526                 .getResourceAsStream(path.toString());
    527 
    528         Assert.assertNotNull("Failed to load serialization resource file: "
    529                 + path, in);
    530 
    531         return getObjectFromStream(in);
    532     }
    533 
    534     /**
    535      * Creates golden file.
    536      *
    537      * The folder for created file is: <code>root + test's package name</code>.
    538      * The file name is: <code>test's name + "golden.ser"</code>
    539      *
    540      * @param root -
    541      *            root directory for serialization resource files
    542      * @param test -
    543      *            test case
    544      * @param object -
    545      *            object to be serialized
    546      * @throws IOException -
    547      *             if I/O error
    548      */
    549     public static void createGoldenFile(String root, TestCase test,
    550             Object object) throws IOException {
    551 
    552         String goldenPath = test.getClass().getName().replace('.',
    553                 File.separatorChar)
    554                 + ".golden.ser";
    555 
    556         if (root != null) {
    557             goldenPath = root + File.separatorChar + goldenPath;
    558         }
    559 
    560 
    561         File goldenFile = new File(goldenPath);
    562         goldenFile.getParentFile().mkdirs();
    563         goldenFile.createNewFile();
    564 
    565         putObjectToStream(object, new FileOutputStream(goldenFile));
    566 
    567         // don't forget to remove it from test case after using
    568         Assert.fail("Generating golden file.\nGolden file name:"
    569                 + goldenFile.getAbsolutePath());
    570     }
    571 
    572     /**
    573      * Copies an object by serializing/deserializing it.
    574      *
    575      * @param initial -
    576      *            an object to be copied
    577      * @return copy of provided object
    578      */
    579     public static Serializable copySerializable(Serializable initial)
    580             throws IOException, ClassNotFoundException {
    581 
    582         ByteArrayOutputStream out = new ByteArrayOutputStream();
    583         putObjectToStream(initial, out);
    584         ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
    585 
    586         return getObjectFromStream(in);
    587     }
    588 }
    589