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