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