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>"test.mode" </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 "serial.reference". 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>"RESOURCE_DIR" </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 */ SerializationTest(String name)93 public SerializationTest(String name) { 94 super(name); 95 } 96 97 /** 98 * Default c-tor inherited from superclass. 99 */ SerializationTest()100 public SerializationTest() { 101 super(); 102 } 103 104 /** 105 * Depending on testing mode, produces golden files or performs testing. 106 */ 107 @Override runBare()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 */ getData()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 */ testSelf()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 */ testGolden()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 */ getDataFile(int index)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 */ produceGoldenFiles()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 */ putObjectToStream(Object obj, OutputStream os)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 */ getObjectFromStream(InputStream is)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 */ assertDeserialized(Serializable initial, Serializable deserialized)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 */ defineComparator(TestCase test, Object object)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 */ verifyGolden(TestCase test, Object object)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 */ verifyGolden(TestCase test, Object object, SerializableAssert comparator)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 */ verifyGolden(TestCase test, Object[] objects)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 */ verifyGolden(TestCase test, Object[] objects, SerializableAssert comparator)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 */ verifySelf(Object object)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 */ verifySelf(Object object, SerializableAssert comparator)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 */ verifySelf(Object[] objects)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 */ verifySelf(Object[] objects, SerializableAssert comparator)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 getObject(TestCase test, String toAppend)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 */ createGoldenFile(String root, TestCase test, Object object)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 */ copySerializable(Serializable initial)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