001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: /**
019: * @author Alexey V. Varlamov
020: * @version $Revision$
021: */package org.apache.harmony.testframework.serialization;
022:
023: import java.io.ByteArrayInputStream;
024: import java.io.ByteArrayOutputStream;
025: import java.io.File;
026: import java.io.FileOutputStream;
027: import java.io.IOException;
028: import java.io.InputStream;
029: import java.io.ObjectInputStream;
030: import java.io.ObjectOutputStream;
031: import java.io.OutputStream;
032: import java.io.Serializable;
033: import java.lang.reflect.Method;
034: import java.security.Permission;
035: import java.security.PermissionCollection;
036: import java.util.Collection;
037: import java.util.Collections;
038: import java.util.HashSet;
039:
040: import junit.framework.Assert;
041: import junit.framework.TestCase;
042:
043: /**
044: * Framework for serialization testing. Subclasses only need to override
045: * getData() method and, optionally, assertDeserialized() method. The first one
046: * returns array of objects to be de/serialized in tests, and the second
047: * compares reference and deserialized objects (needed only if tested objects do
048: * not provide specific method equals()). <br>
049: * There are two modes of test run: <b>reference generation mode </b> and
050: * <b>testing mode </b>. The actual mode is selected via
051: * <b>"test.mode" </b> system property. The <b>testing mode </b> is
052: * the default mode. <br>
053: * To turn on the <b>reference generation mode </b>, the test.mode property
054: * should be set to value "serial.reference". In this mode, no testing
055: * is performed but golden files are produced, which contain reference
056: * serialized objects. This mode should be run on a pure
057: * Implementation classes, which are targeted for compartibility. <br>
058: * The location of golden files (in both modes) is controlled via
059: * <b>"RESOURCE_DIR" </b> system property.
060: *
061: */
062: public abstract class SerializationTest extends TestCase {
063:
064: /**
065: * Property name for the testing mode.
066: */
067: public static final String MODE_KEY = "test.mode";
068:
069: /**
070: * Testing mode.
071: */
072: public static String mode = System.getProperty(MODE_KEY);
073:
074: /**
075: * Reference files generation mode switch.
076: */
077: public static final String SERIAL_REFERENCE_MODE = "serial.reference";
078:
079: /**
080: * Key to a system property defining root location of golden files.
081: */
082: public static final String GOLDEN_PATH = "RESOURCE_DIR";
083:
084: private static final String outputPath = System.getProperty(
085: GOLDEN_PATH, "src/test/resources/serialization");
086:
087: /**
088: * Parameterized c-tor inherited from superclass.
089: */
090: public SerializationTest(String name) {
091: super (name);
092: }
093:
094: /**
095: * Default c-tor inherited from superclass.
096: */
097: public SerializationTest() {
098: super ();
099: }
100:
101: /**
102: * Depending on testing mode, produces golden files or performs testing.
103: */
104: @Override
105: public void runBare() throws Throwable {
106:
107: if (mode != null && mode.equals(SERIAL_REFERENCE_MODE)) {
108: produceGoldenFiles();
109: } else {
110: super .runBare();
111: }
112: }
113:
114: /**
115: * This is the main working method of this framework. Subclasses must
116: * override it to provide actual objects for testing.
117: *
118: * @return array of objects to be de/serialized in tests.
119: */
120: protected abstract Object[] getData();
121:
122: /**
123: * Tests that data objects can be serialized and deserialized without
124: * exceptions, and that deserialization really produces deeply cloned
125: * objects.
126: */
127: public void testSelf() throws Throwable {
128:
129: if (this instanceof SerializableAssert) {
130: verifySelf(getData(), (SerializableAssert) this );
131: } else {
132: verifySelf(getData());
133:
134: }
135: }
136:
137: /**
138: * Tests that data objects can be deserialized from golden files, to verify
139: * compartibility with Reference Implementation.
140: */
141: public void testGolden() throws Throwable {
142:
143: verifyGolden(this , getData());
144: }
145:
146: /**
147: * Returns golden file for an object being tested.
148: *
149: * @param index array index of tested data (as returned by
150: * {@link #getData() getData()})
151: * @return corresponding golden file
152: */
153: protected File getDataFile(int index) {
154: String name = this .getClass().getName();
155: int dot = name.lastIndexOf(".");
156: String path = name.substring(0, dot).replace('.',
157: File.separatorChar);
158: if (outputPath != null && outputPath.length() != 0) {
159: path = outputPath + File.separator + path;
160: }
161:
162: return new File(path, name.substring(dot + 1) + "." + index
163: + ".dat");
164: }
165:
166: /**
167: * Working method for files generation mode. Serializes test objects
168: * returned by {@link #getData() getData()}to golden files, each object to
169: * a separate file.
170: *
171: * @throws IOException
172: */
173: protected void produceGoldenFiles() throws IOException {
174:
175: String goldenPath = outputPath + File.separatorChar
176: + getClass().getName().replace('.', File.separatorChar)
177: + ".golden.";
178:
179: Object[] data = getData();
180: for (int i = 0; i < data.length; i++) {
181:
182: File goldenFile = new File(goldenPath + i + ".ser");
183: goldenFile.getParentFile().mkdirs();
184: goldenFile.createNewFile();
185:
186: putObjectToStream(data[i], new FileOutputStream(goldenFile));
187: }
188: }
189:
190: /**
191: * Serializes specified object to an output stream.
192: */
193: public static void putObjectToStream(Object obj, OutputStream os)
194: throws IOException {
195: ObjectOutputStream oos = new ObjectOutputStream(os);
196: oos.writeObject(obj);
197: oos.flush();
198: oos.close();
199: }
200:
201: /**
202: * Deserializes single object from an input stream.
203: */
204: public static Serializable getObjectFromStream(InputStream is)
205: throws IOException, ClassNotFoundException {
206: ObjectInputStream ois = new ObjectInputStream(is);
207: Object result = ois.readObject();
208: ois.close();
209: return (Serializable) result;
210: }
211:
212: /**
213: * Interface to compare (de)serialized objects
214: *
215: * Should be implemented if a class under test does not provide specific
216: * equals() method and it's instances should to be compared manually.
217: */
218: public interface SerializableAssert {
219:
220: /**
221: * Compares deserialized and reference objects.
222: *
223: * @param initial -
224: * initial object used for creating serialized form
225: * @param deserialized -
226: * deserialized object
227: */
228: void assertDeserialized(Serializable initial,
229: 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,
257: 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
267: .getMessage());
268:
269: // verify cause
270: if (initThr.getCause() == null) {
271: Assert.assertNull(dserThr.getCause());
272: } else {
273: Assert.assertNotNull(dserThr.getCause());
274:
275: THROWABLE_COMPARATOR.assertDeserialized(initThr
276: .getCause(), dserThr.getCause());
277: }
278: }
279: };
280:
281: /**
282: * Comparator for java.security.PermissionCollection objects
283: */
284: public final static SerializableAssert PERMISSION_COLLECTION_COMPARATOR = new SerializableAssert() {
285: public void assertDeserialized(Serializable initial,
286: Serializable deserialized) {
287:
288: PermissionCollection initPC = (PermissionCollection) initial;
289: PermissionCollection dserPC = (PermissionCollection) deserialized;
290:
291: // verify class
292: Assert.assertEquals(initPC.getClass(), dserPC.getClass());
293:
294: // verify 'readOnly' field
295: Assert.assertEquals(initPC.isReadOnly(), dserPC
296: .isReadOnly());
297:
298: // verify collection of permissions
299: Collection<Permission> refCollection = new HashSet<Permission>(
300: Collections.list(initPC.elements()));
301: Collection<Permission> tstCollection = new HashSet<Permission>(
302: Collections.list(dserPC.elements()));
303:
304: Assert.assertEquals(refCollection, tstCollection);
305: }
306: };
307:
308: /**
309: * Returns <code>comparator</code> for provided serializable
310: * <code>object</code>.
311: *
312: * The <code>comparator</code> is searched in the following order: <br>-
313: * if <code>test</code> implements SerializableAssert interface then it is
314: * selected as </code>comparator</code>.<br>- if passed <code>object</code>
315: * has class in its classes hierarchy that overrides <code>equals(Object)</code>
316: * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the
317: * method tries to select one of known comparators basing on <code>object's</code>
318: * class,for example, if passed <code>object</code> is instance of
319: * java.lang.Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>-
320: * otherwise RuntimeException is thrown
321: *
322: * @param test -
323: * test case
324: * @param object -
325: * object to be compared
326: * @return object's comparator
327: */
328: public static SerializableAssert defineComparator(TestCase test,
329: Object object) throws Exception {
330:
331: if (test instanceof SerializableAssert) {
332: return (SerializableAssert) test;
333: }
334:
335: Method m = object.getClass().getMethod("equals",
336: new Class[] { Object.class });
337:
338: if (m.getDeclaringClass() != Object.class) {
339: // one of classes overrides Object.equals(Object) method
340: // use default comparator
341: return DEFAULT_COMPARATOR;
342: }
343:
344: // TODO use generics to detect comparator
345: // instead of 'instanceof' for the first element
346: if (object instanceof java.lang.Throwable) {
347: return THROWABLE_COMPARATOR;
348: } else if (object instanceof java.security.PermissionCollection) {
349: return PERMISSION_COLLECTION_COMPARATOR;
350: }
351:
352: throw new RuntimeException("Failed to detect comparator");
353: }
354:
355: /**
356: * Verifies that object deserialized from golden file correctly.
357: *
358: * The method invokes <br>
359: * verifyGolden(test, object, defineComparator(test, object));
360: *
361: * @param test -
362: * test case
363: * @param object -
364: * to be compared
365: */
366: public static void verifyGolden(TestCase test, Object object)
367: throws Exception {
368:
369: verifyGolden(test, object, defineComparator(test, object));
370: }
371:
372: /**
373: * Verifies that object deserialized from golden file correctly.
374: *
375: * The method loads "<code>testName</code>.golden.ser" resource file
376: * from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
377: * folder, reads an object from the loaded file and compares it with
378: * <code>object</code> using specified <code>comparator</code>.
379: *
380: * @param test-
381: * test case
382: * @param object-
383: * to be compared
384: * @param comparator -
385: * for comparing (de)serialized objects
386: */
387: public static void verifyGolden(TestCase test, Object object,
388: SerializableAssert comparator) throws Exception {
389:
390: Assert.assertNotNull("Null comparator", comparator);
391:
392: Serializable deserialized = getObject(test, ".golden.ser");
393:
394: comparator.assertDeserialized((Serializable) object,
395: deserialized);
396: }
397:
398: /**
399: * Verifies that objects from array deserialized from golden files
400: * correctly.
401: *
402: * The method invokes <br>
403: * verifyGolden(test, objects, defineComparator(test, object[0]));
404: *
405: * @param test -
406: * test case
407: * @param objects -
408: * array of objects to be compared
409: */
410: public static void verifyGolden(TestCase test, Object[] objects)
411: throws Exception {
412:
413: Assert.assertFalse("Empty array", objects.length == 0);
414: verifyGolden(test, objects, defineComparator(test, objects[0]));
415: }
416:
417: /**
418: * Verifies that objects from array deserialized from golden files
419: * correctly.
420: *
421: * The method loads "<code>testName</code>.golden.<code>N</code>.ser"
422: * resource files from "<module root>/src/test/resources/serialization/<code>testPackage</code>"
423: * folder, from each loaded file it reads an object from and compares it
424: * with corresponding object in provided array (i.e. <code>objects[N]</code>)
425: * using specified <code>comparator</code>. (<code>N</code> is index
426: * in object's array.)
427: *
428: * @param test-
429: * test case
430: * @param objects -
431: * array of objects to be compared
432: * @param comparator -
433: * for comparing (de)serialized objects
434: */
435: public static void verifyGolden(TestCase test, Object[] objects,
436: SerializableAssert comparator) throws Exception {
437:
438: Assert.assertFalse("Empty array", objects.length == 0);
439: for (int i = 0; i < objects.length; i++) {
440: Serializable deserialized = getObject(test, ".golden." + i
441: + ".ser");
442: comparator.assertDeserialized((Serializable) objects[i],
443: deserialized);
444: }
445: }
446:
447: /**
448: * Verifies that object can be smoothly serialized/deserialized.
449: *
450: * The method invokes <br>
451: * verifySelf(object, defineComparator(null, object));
452: *
453: * @param object -
454: * to be serialized/deserialized
455: */
456: public static void verifySelf(Object object) throws Exception {
457:
458: verifySelf(object, defineComparator(null, object));
459: }
460:
461: /**
462: * Verifies that object can be smoothly serialized/deserialized.
463: *
464: * The method serialize/deserialize <code>object</code> and compare it
465: * with initial <code>object</code>.
466: *
467: * @param object -
468: * object to be serialized/deserialized
469: * @param comparator -
470: * for comparing serialized/deserialized object with initial
471: * object
472: */
473: public static void verifySelf(Object object,
474: SerializableAssert comparator) throws Exception {
475:
476: Serializable initial = (Serializable) object;
477:
478: comparator.assertDeserialized(initial,
479: copySerializable(initial));
480: }
481:
482: /**
483: * Verifies that that objects from array can be smoothly
484: * serialized/deserialized.
485: *
486: * The method invokes <br>
487: * verifySelf(objects, defineComparator(null, object[0]));
488: *
489: * @param objects -
490: * array of objects to be serialized/deserialized
491: */
492: public static void verifySelf(Object[] objects) throws Exception {
493:
494: Assert.assertFalse("Empty array", objects.length == 0);
495: verifySelf(objects, defineComparator(null, objects[0]));
496: }
497:
498: /**
499: * Verifies that that objects from array can be smoothly
500: * serialized/deserialized.
501: *
502: * The method serialize/deserialize each object in <code>objects</code>
503: * array and compare it with initial object.
504: *
505: * @param objects -
506: * array of objects to be serialized/deserialized
507: * @param comparator -
508: * for comparing serialized/deserialized object with initial
509: * object
510: */
511: public static void verifySelf(Object[] objects,
512: SerializableAssert comparator) throws Exception {
513:
514: Assert.assertFalse("Empty array", objects.length == 0);
515: for (Object entry : objects) {
516: verifySelf(entry, comparator);
517: }
518: }
519:
520: private static Serializable getObject(TestCase test, String toAppend)
521: throws Exception {
522:
523: StringBuilder path = new StringBuilder("serialization");
524:
525: path.append(File.separatorChar);
526: path.append(test.getClass().getName().replace('.',
527: File.separatorChar));
528: path.append(toAppend);
529:
530: InputStream in = ClassLoader.getSystemClassLoader()
531: .getResourceAsStream(path.toString());
532:
533: Assert.assertNotNull(
534: "Failed to load serialization resource file: " + path,
535: in);
536:
537: return getObjectFromStream(in);
538: }
539:
540: /**
541: * Creates golden file.
542: *
543: * The folder for created file is: <code>root + test's package name</code>.
544: * The file name is: <code>test's name + "golden.ser"</code>
545: *
546: * @param root -
547: * root directory for serialization resource files
548: * @param test -
549: * test case
550: * @param object -
551: * object to be serialized
552: * @throws IOException -
553: * if I/O error
554: */
555: public static void createGoldenFile(String root, TestCase test,
556: Object object) throws IOException {
557:
558: String goldenPath = test.getClass().getName().replace('.',
559: File.separatorChar)
560: + ".golden.ser";
561:
562: if (root != null) {
563: goldenPath = root + File.separatorChar + goldenPath;
564: }
565:
566: File goldenFile = new File(goldenPath);
567: goldenFile.getParentFile().mkdirs();
568: goldenFile.createNewFile();
569:
570: putObjectToStream(object, new FileOutputStream(goldenFile));
571:
572: // don't forget to remove it from test case after using
573: Assert.fail("Generating golden file.\nGolden file name:"
574: + goldenFile.getAbsolutePath());
575: }
576:
577: /**
578: * Copies an object by serializing/deserializing it.
579: *
580: * @param initial -
581: * an object to be copied
582: * @return copy of provided object
583: */
584: public static Serializable copySerializable(Serializable initial)
585: throws IOException, ClassNotFoundException {
586:
587: ByteArrayOutputStream out = new ByteArrayOutputStream();
588: putObjectToStream(initial, out);
589: ByteArrayInputStream in = new ByteArrayInputStream(out
590: .toByteArray());
591:
592: return getObjectFromStream(in);
593: }
594: }
|