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>"test.mode" </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 "serial.reference". 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>"RESOURCE_DIR" </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