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 23package org.apache.harmony.testframework.serialization; 24 25import dalvik.annotation.BrokenTest; 26import dalvik.annotation.TestLevel; 27import dalvik.annotation.TestTargetClass; 28import dalvik.annotation.TestTargetNew; 29 30import java.io.ByteArrayInputStream; 31import java.io.ByteArrayOutputStream; 32import java.io.File; 33import java.io.FileOutputStream; 34import java.io.IOException; 35import java.io.InputStream; 36import java.io.ObjectInputStream; 37import java.io.ObjectOutputStream; 38import java.io.OutputStream; 39import java.io.Serializable; 40import java.lang.reflect.Method; 41import java.security.Permission; 42import java.security.PermissionCollection; 43import java.util.Collection; 44import java.util.Collections; 45import java.util.HashSet; 46 47import junit.framework.Assert; 48import junit.framework.TestCase; 49 50/** 51 * Framework for serialization testing. Subclasses only need to override 52 * getData() method and, optionally, assertDeserialized() method. The first one 53 * returns array of objects to be de/serialized in tests, and the second 54 * compares reference and deserialized objects (needed only if tested objects do 55 * not provide specific method equals()). <br> 56 * There are two modes of test run: <b>reference generation mode </b> and 57 * <b>testing mode </b>. The actual mode is selected via 58 * <b>"test.mode" </b> system property. The <b>testing mode </b> is 59 * the default mode. <br> 60 * To turn on the <b>reference generation mode </b>, the test.mode property 61 * should be set to value "serial.reference". In this mode, no testing 62 * is performed but golden files are produced, which contain reference 63 * serialized objects. This mode should be run on a pure 64 * Implementation classes, which are targeted for compartibility. <br> 65 * The location of golden files (in both modes) is controlled via 66 * <b>"RESOURCE_DIR" </b> system property. 67 * 68 */ 69@TestTargetClass(Serializable.class) 70public abstract class SerializationTest extends TestCase { 71 72 /** 73 * Property name for the testing mode. 74 */ 75 public static final String MODE_KEY = "test.mode"; 76 77 78 /** 79 * Testing mode. 80 */ 81 public static String mode = System.getProperty(MODE_KEY); 82 83 /** 84 * Reference files generation mode switch. 85 */ 86 public static final String SERIAL_REFERENCE_MODE = "serial.reference"; 87 88 /** 89 * Key to a system property defining root location of golden files. 90 */ 91 public static final String GOLDEN_PATH = "RESOURCE_DIR"; 92 93 private static final String outputPath = System.getProperty(GOLDEN_PATH, 94 "src/test/resources/serialization"); 95 96 /** 97 * Parameterized c-tor inherited from superclass. 98 */ 99 public SerializationTest(String name) { 100 super(name); 101 } 102 103 /** 104 * Default c-tor inherited from superclass. 105 */ 106 public SerializationTest() { 107 super(); 108 } 109 110 /** 111 * Depending on testing mode, produces golden files or performs testing. 112 */ 113 @Override 114 public void runBare() throws Throwable { 115 116 if (mode != null && mode.equals(SERIAL_REFERENCE_MODE)) { 117 produceGoldenFiles(); 118 } else { 119 super.runBare(); 120 } 121 } 122 123 /** 124 * This is the main working method of this framework. Subclasses must 125 * override it to provide actual objects for testing. 126 * 127 * @return array of objects to be de/serialized in tests. 128 */ 129 protected abstract Object[] getData(); 130 131 /** 132 * Tests that data objects can be serialized and deserialized without 133 * exceptions, and that deserialization really produces deeply cloned 134 * objects. 135 */ 136 @TestTargetNew( 137 level = TestLevel.ADDITIONAL, 138 notes = "", 139 method = "!Serialization", 140 args = {} 141 ) 142 public void testSelf() throws Throwable { 143 144 if (this instanceof SerializableAssert) { 145 verifySelf(getData(), (SerializableAssert) this); 146 } else { 147 verifySelf(getData()); 148 149 } 150 } 151 152 /** 153 * Tests that data objects can be deserialized from golden files, to verify 154 * compatibility with Reference Implementation. 155 */ 156 157 @TestTargetNew( 158 level = TestLevel.ADDITIONAL, 159 notes = "", 160 method = "!Serialization", 161 args = {} 162 ) 163 public void testGolden() throws Throwable { 164 165 verifyGolden(this, getData()); 166 } 167 168 /** 169 * Returns golden file for an object being tested. 170 * 171 * @param index array index of tested data (as returned by 172 * {@link #getData() getData()}) 173 * @return corresponding golden file 174 */ 175 protected File getDataFile(int index) { 176 String name = this.getClass().getName(); 177 int dot = name.lastIndexOf("."); 178 String path = name.substring(0, dot).replace('.', File.separatorChar); 179 if (outputPath != null && outputPath.length() != 0) { 180 path = outputPath + File.separator + path; 181 } 182 183 return new File(path, name.substring(dot + 1) + "." + index + ".dat"); 184 } 185 186 /** 187 * Working method for files generation mode. Serializes test objects 188 * returned by {@link #getData() getData()}to golden files, each object to 189 * a separate file. 190 * 191 * @throws IOException 192 */ 193 protected void produceGoldenFiles() throws IOException { 194 195 String goldenPath = outputPath + File.separatorChar 196 + getClass().getName().replace('.', File.separatorChar) 197 + ".golden."; 198 199 Object[] data = getData(); 200 for (int i = 0; i < data.length; i++) { 201 202 File goldenFile = new File(goldenPath + i + ".ser"); 203 goldenFile.getParentFile().mkdirs(); 204 goldenFile.createNewFile(); 205 206 putObjectToStream(data[i], new FileOutputStream(goldenFile)); 207 } 208 } 209 210 /** 211 * Serializes specified object to an output stream. 212 */ 213 public static void putObjectToStream(Object obj, OutputStream os) 214 throws IOException { 215 ObjectOutputStream oos = new ObjectOutputStream(os); 216 oos.writeObject(obj); 217 oos.flush(); 218 oos.close(); 219 } 220 221 /** 222 * Deserializes single object from an input stream. 223 */ 224 public static Serializable getObjectFromStream(InputStream is) throws IOException, 225 ClassNotFoundException { 226 ObjectInputStream ois = new ObjectInputStream(is); 227 Object result = ois.readObject(); 228 ois.close(); 229 return (Serializable)result; 230 } 231 232 /** 233 * Interface to compare (de)serialized objects 234 * 235 * Should be implemented if a class under test does not provide specific 236 * equals() method and it's instances should to be compared manually. 237 */ 238 public interface SerializableAssert { 239 240 /** 241 * Compares deserialized and reference objects. 242 * 243 * @param initial - 244 * initial object used for creating serialized form 245 * @param deserialized - 246 * deserialized object 247 */ 248 void assertDeserialized(Serializable initial, Serializable deserialized); 249 } 250 251 // default comparator for a class that has equals(Object) method 252 private final static SerializableAssert DEFAULT_COMPARATOR = new SerializableAssert() { 253 public void assertDeserialized(Serializable initial, 254 Serializable deserialized) { 255 256 Assert.assertEquals(initial, deserialized); 257 } 258 }; 259 260 /** 261 * Comparator for verifying that deserialized object is the same as initial. 262 */ 263 public final static SerializableAssert SAME_COMPARATOR = new SerializableAssert() { 264 public void assertDeserialized(Serializable initial, 265 Serializable deserialized) { 266 267 Assert.assertSame(initial, deserialized); 268 } 269 }; 270 271 /** 272 * Comparator for java.lang.Throwable objects 273 */ 274 public final static SerializableAssert THROWABLE_COMPARATOR = new SerializableAssert() { 275 public void assertDeserialized(Serializable initial, Serializable deserialized) { 276 277 Throwable initThr = (Throwable) initial; 278 Throwable dserThr = (Throwable) deserialized; 279 280 // verify class 281 Assert.assertEquals(initThr.getClass(), dserThr.getClass()); 282 283 // verify message 284 Assert.assertEquals(initThr.getMessage(), dserThr.getMessage()); 285 286 // verify cause 287 if (initThr.getCause() == null) { 288 Assert.assertNull(dserThr.getCause()); 289 } else { 290 Assert.assertNotNull(dserThr.getCause()); 291 292 THROWABLE_COMPARATOR.assertDeserialized(initThr.getCause(), 293 dserThr.getCause()); 294 } 295 } 296 }; 297 298 /** 299 * Comparator for java.security.PermissionCollection objects 300 */ 301 public final static SerializableAssert PERMISSION_COLLECTION_COMPARATOR = new SerializableAssert() { 302 public void assertDeserialized(Serializable initial, Serializable deserialized) { 303 304 PermissionCollection initPC = (PermissionCollection) initial; 305 PermissionCollection dserPC = (PermissionCollection) deserialized; 306 307 // verify class 308 Assert.assertEquals(initPC.getClass(), dserPC.getClass()); 309 310 // verify 'readOnly' field 311 Assert.assertEquals(initPC.isReadOnly(), dserPC.isReadOnly()); 312 313 // verify collection of permissions 314 Collection<Permission> refCollection = new HashSet<Permission>( 315 Collections.list(initPC.elements())); 316 Collection<Permission> tstCollection = new HashSet<Permission>( 317 Collections.list(dserPC.elements())); 318 319 Assert.assertEquals(refCollection, tstCollection); 320 } 321 }; 322 323 /** 324 * Returns <code>comparator</code> for provided serializable 325 * <code>object</code>. 326 * 327 * The <code>comparator</code> is searched in the following order: <br>- 328 * if <code>test</code> implements SerializableAssert interface then it is 329 * selected as </code>comparator</code>.<br>- if passed <code>object</code> 330 * has class in its classes hierarchy that overrides <code>equals(Object)</code> 331 * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the 332 * method tries to select one of known comparators basing on <code>object's</code> 333 * class,for example, if passed <code>object</code> is instance of 334 * java.lang.Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>- 335 * otherwise RuntimeException is thrown 336 * 337 * @param test - 338 * test case 339 * @param object - 340 * object to be compared 341 * @return object's comparator 342 */ 343 public static SerializableAssert defineComparator(TestCase test, 344 Object object) throws Exception { 345 346 if (test instanceof SerializableAssert) { 347 return (SerializableAssert) test; 348 } 349 350 Method m = object.getClass().getMethod("equals", 351 new Class[] { Object.class }); 352 353 if (m.getDeclaringClass() != Object.class) { 354 // one of classes overrides Object.equals(Object) method 355 // use default comparator 356 return DEFAULT_COMPARATOR; 357 } 358 359 // TODO use generics to detect comparator 360 // instead of 'instanceof' for the first element 361 if (object instanceof java.lang.Throwable) { 362 return THROWABLE_COMPARATOR; 363 } else if (object instanceof java.security.PermissionCollection) { 364 return PERMISSION_COLLECTION_COMPARATOR; 365 } 366 367 throw new RuntimeException("Failed to detect comparator"); 368 } 369 370 /** 371 * Verifies that object deserialized from golden file correctly. 372 * 373 * The method invokes <br> 374 * verifyGolden(test, object, defineComparator(test, object)); 375 * 376 * @param test - 377 * test case 378 * @param object - 379 * to be compared 380 */ 381 public static void verifyGolden(TestCase test, Object object) 382 throws Exception { 383 384 verifyGolden(test, object, defineComparator(test, object)); 385 } 386 387 /** 388 * Verifies that object deserialized from golden file correctly. 389 * 390 * The method loads "<code>testName</code>.golden.ser" resource file 391 * from "<module root>/src/test/resources/serialization/<code>testPackage</code>" 392 * folder, reads an object from the loaded file and compares it with 393 * <code>object</code> using specified <code>comparator</code>. 394 * 395 * @param test- 396 * test case 397 * @param object- 398 * to be compared 399 * @param comparator - 400 * for comparing (de)serialized objects 401 */ 402 public static void verifyGolden(TestCase test, Object object, 403 SerializableAssert comparator) throws Exception { 404 405 Assert.assertNotNull("Null comparator", comparator); 406 407 Serializable deserialized = getObject(test, ".golden.ser"); 408 409 comparator.assertDeserialized((Serializable) object, deserialized); 410 } 411 412 /** 413 * Verifies that objects from array deserialized from golden files 414 * correctly. 415 * 416 * The method invokes <br> 417 * verifyGolden(test, objects, defineComparator(test, object[0])); 418 * 419 * @param test - 420 * test case 421 * @param objects - 422 * array of objects to be compared 423 */ 424 public static void verifyGolden(TestCase test, Object[] objects) 425 throws Exception { 426 427 Assert.assertFalse("Empty array", objects.length == 0); 428 verifyGolden(test, objects, defineComparator(test, objects[0])); 429 } 430 431 /** 432 * Verifies that objects from array deserialized from golden files 433 * correctly. 434 * 435 * The method loads "<code>testName</code>.golden.<code>N</code>.ser" 436 * resource files from "<module root>/src/test/resources/serialization/<code>testPackage</code>" 437 * folder, from each loaded file it reads an object from and compares it 438 * with corresponding object in provided array (i.e. <code>objects[N]</code>) 439 * using specified <code>comparator</code>. (<code>N</code> is index 440 * in object's array.) 441 * 442 * @param test- 443 * test case 444 * @param objects - 445 * array of objects to be compared 446 * @param comparator - 447 * for comparing (de)serialized objects 448 */ 449 public static void verifyGolden(TestCase test, Object[] objects, 450 SerializableAssert comparator) throws Exception { 451 452 Assert.assertFalse("Empty array", objects.length == 0); 453 for (int i = 0; i < objects.length; i++) { 454 Serializable deserialized = getObject(test, ".golden." + i + ".ser"); 455 comparator.assertDeserialized((Serializable) objects[i], 456 deserialized); 457 } 458 } 459 460 /** 461 * Verifies that object can be smoothly serialized/deserialized. 462 * 463 * The method invokes <br> 464 * verifySelf(object, defineComparator(null, object)); 465 * 466 * @param object - 467 * to be serialized/deserialized 468 */ 469 public static void verifySelf(Object object) 470 throws Exception { 471 472 verifySelf(object, defineComparator(null, object)); 473 } 474 475 /** 476 * Verifies that object can be smoothly serialized/deserialized. 477 * 478 * The method serialize/deserialize <code>object</code> and compare it 479 * with initial <code>object</code>. 480 * 481 * @param object - 482 * object to be serialized/deserialized 483 * @param comparator - 484 * for comparing serialized/deserialized object with initial 485 * object 486 */ 487 public static void verifySelf(Object object, SerializableAssert comparator) 488 throws Exception { 489 490 Serializable initial = (Serializable) object; 491 492 comparator.assertDeserialized(initial, copySerializable(initial)); 493 } 494 495 /** 496 * Verifies that that objects from array can be smoothly 497 * serialized/deserialized. 498 * 499 * The method invokes <br> 500 * verifySelf(objects, defineComparator(null, object[0])); 501 * 502 * @param objects - 503 * array of objects to be serialized/deserialized 504 */ 505 public static void verifySelf(Object[] objects) 506 throws Exception { 507 508 Assert.assertFalse("Empty array", objects.length == 0); 509 verifySelf(objects, defineComparator(null, objects[0])); 510 } 511 512 /** 513 * Verifies that that objects from array can be smoothly 514 * serialized/deserialized. 515 * 516 * The method serialize/deserialize each object in <code>objects</code> 517 * array and compare it with initial object. 518 * 519 * @param objects - 520 * array of objects to be serialized/deserialized 521 * @param comparator - 522 * for comparing serialized/deserialized object with initial 523 * object 524 */ 525 public static void verifySelf(Object[] objects, SerializableAssert comparator) 526 throws Exception { 527 528 Assert.assertFalse("Empty array", objects.length == 0); 529 for(Object entry: objects){ 530 verifySelf(entry, comparator); 531 } 532 } 533 534 private static Serializable getObject(TestCase test, String toAppend) 535 throws Exception { 536 537 StringBuilder path = new StringBuilder("/serialization"); 538 539 path.append(File.separatorChar); 540 path.append(test.getClass().getName().replace('.', File.separatorChar)); 541 path.append(toAppend); 542 543 InputStream in = SerializationTest.class 544 .getResourceAsStream(path.toString()); 545 546 Assert.assertNotNull("Failed to load serialization resource file: " 547 + path, in); 548 549 return getObjectFromStream(in); 550 } 551 552 /** 553 * Creates golden file. 554 * 555 * The folder for created file is: <code>root + test's package name</code>. 556 * The file name is: <code>test's name + "golden.ser"</code> 557 * 558 * @param root - 559 * root directory for serialization resource files 560 * @param test - 561 * test case 562 * @param object - 563 * object to be serialized 564 * @throws IOException - 565 * if I/O error 566 */ 567 public static void createGoldenFile(String root, TestCase test, 568 Object object) throws IOException { 569 570 String goldenPath = test.getClass().getName().replace('.', 571 File.separatorChar) 572 + ".golden.ser"; 573 574 if (root != null) { 575 goldenPath = root + File.separatorChar + goldenPath; 576 } 577 578 579 File goldenFile = new File(goldenPath); 580 goldenFile.getParentFile().mkdirs(); 581 goldenFile.createNewFile(); 582 583 putObjectToStream(object, new FileOutputStream(goldenFile)); 584 585 // don't forget to remove it from test case after using 586 Assert.fail("Generating golden file.\nGolden file name:" 587 + goldenFile.getAbsolutePath()); 588 } 589 590 /** 591 * Copies an object by serializing/deserializing it. 592 * 593 * @param initial - 594 * an object to be copied 595 * @return copy of provided object 596 */ 597 public static Serializable copySerializable(Serializable initial) 598 throws IOException, ClassNotFoundException { 599 600 ByteArrayOutputStream out = new ByteArrayOutputStream(); 601 putObjectToStream(initial, out); 602 ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); 603 604 return getObjectFromStream(in); 605 } 606} 607