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.Assert; 42 import junit.framework.TestCase; 43 import libcore.base.Objects; 44 45 /** 46 * Framework for serialization testing. Subclasses only need to override 47 * getData() method and, optionally, assertDeserialized() method. The first one 48 * returns array of objects to be de/serialized in tests, and the second 49 * compares reference and deserialized objects (needed only if tested objects do 50 * not provide specific method equals()). <br> 51 * There are two modes of test run: <b>reference generation mode </b> and 52 * <b>testing mode </b>. The actual mode is selected via 53 * <b>"test.mode" </b> system property. The <b>testing mode </b> is 54 * the default mode. <br> 55 * To turn on the <b>reference generation mode </b>, the test.mode property 56 * should be set to value "serial.reference". In this mode, no testing 57 * is performed but golden files are produced, which contain reference 58 * serialized objects. This mode should be run on a pure 59 * Implementation classes, which are targeted for compartibility. <br> 60 * The location of golden files (in both modes) is controlled via 61 * <b>"RESOURCE_DIR" </b> system property. 62 * 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 */ 93 public SerializationTest(String name) { 94 super(name); 95 } 96 97 /** 98 * Default c-tor inherited from superclass. 99 */ 100 public SerializationTest() { 101 super(); 102 } 103 104 /** 105 * Depending on testing mode, produces golden files or performs testing. 106 */ 107 @Override 108 public void runBare() throws Throwable { 109 110 if (Objects.equal(mode, 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 */ 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 */ 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 * compatibility with Reference Implementation. 143 */ 144 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 */ 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 */ 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 */ 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 */ 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 */ 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, tstCollection); 302 } 303 }; 304 305 /** 306 * Returns <code>comparator</code> for provided serializable 307 * <code>object</code>. 308 * 309 * The <code>comparator</code> is searched in the following order: <br>- 310 * if <code>test</code> implements SerializableAssert interface then it is 311 * selected as </code>comparator</code>.<br>- if passed <code>object</code> 312 * has class in its classes hierarchy that overrides <code>equals(Object)</code> 313 * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the 314 * method tries to select one of known comparators basing on <code>object's</code> 315 * class,for example, if passed <code>object</code> is instance of 316 * java.lang.Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br>- 317 * otherwise RuntimeException is thrown 318 * 319 * @param test - 320 * test case 321 * @param object - 322 * object to be compared 323 * @return object's comparator 324 */ 325 public static SerializableAssert defineComparator(TestCase test, 326 Object object) throws Exception { 327 328 if (test instanceof SerializableAssert) { 329 return (SerializableAssert) test; 330 } 331 332 Method m = object.getClass().getMethod("equals", 333 new Class[] { Object.class }); 334 335 if (m.getDeclaringClass() != Object.class) { 336 // one of classes overrides Object.equals(Object) method 337 // use default comparator 338 return DEFAULT_COMPARATOR; 339 } 340 341 // TODO use generics to detect comparator 342 // instead of 'instanceof' for the first element 343 if (object instanceof java.lang.Throwable) { 344 return THROWABLE_COMPARATOR; 345 } else if (object instanceof java.security.PermissionCollection) { 346 return PERMISSION_COLLECTION_COMPARATOR; 347 } 348 349 throw new RuntimeException("Failed to detect comparator"); 350 } 351 352 /** 353 * Verifies that object deserialized from golden file correctly. 354 * 355 * The method invokes <br> 356 * verifyGolden(test, object, defineComparator(test, object)); 357 * 358 * @param test - 359 * test case 360 * @param object - 361 * to be compared 362 */ 363 public static void verifyGolden(TestCase test, Object object) 364 throws Exception { 365 366 verifyGolden(test, object, defineComparator(test, object)); 367 } 368 369 /** 370 * Verifies that object deserialized from golden file correctly. 371 * 372 * The method loads "<code>testName</code>.golden.ser" resource file 373 * from "<module root>/src/test/resources/serialization/<code>testPackage</code>" 374 * folder, reads an object from the loaded file and compares it with 375 * <code>object</code> using specified <code>comparator</code>. 376 * 377 * @param test- 378 * test case 379 * @param object- 380 * to be compared 381 * @param comparator - 382 * for comparing (de)serialized objects 383 */ 384 public static void verifyGolden(TestCase test, Object object, 385 SerializableAssert comparator) throws Exception { 386 387 Assert.assertNotNull("Null comparator", comparator); 388 389 Serializable deserialized = getObject(test, ".golden.ser"); 390 391 comparator.assertDeserialized((Serializable) object, deserialized); 392 } 393 394 /** 395 * Verifies that objects from array deserialized from golden files 396 * correctly. 397 * 398 * The method invokes <br> 399 * verifyGolden(test, objects, defineComparator(test, object[0])); 400 * 401 * @param test - 402 * test case 403 * @param objects - 404 * array of objects to be compared 405 */ 406 public static void verifyGolden(TestCase test, Object[] objects) 407 throws Exception { 408 409 Assert.assertFalse("Empty array", objects.length == 0); 410 verifyGolden(test, objects, defineComparator(test, objects[0])); 411 } 412 413 /** 414 * Verifies that objects from array deserialized from golden files 415 * correctly. 416 * 417 * The method loads "<code>testName</code>.golden.<code>N</code>.ser" 418 * resource files from "<module root>/src/test/resources/serialization/<code>testPackage</code>" 419 * folder, from each loaded file it reads an object from and compares it 420 * with corresponding object in provided array (i.e. <code>objects[N]</code>) 421 * using specified <code>comparator</code>. (<code>N</code> is index 422 * in object's array.) 423 * 424 * @param test- 425 * test case 426 * @param objects - 427 * array of objects to be compared 428 * @param comparator - 429 * for comparing (de)serialized objects 430 */ 431 public static void verifyGolden(TestCase test, Object[] objects, 432 SerializableAssert comparator) throws Exception { 433 434 Assert.assertFalse("Empty array", objects.length == 0); 435 for (int i = 0; i < objects.length; i++) { 436 Serializable deserialized = getObject(test, ".golden." + i + ".ser"); 437 comparator.assertDeserialized((Serializable) objects[i], 438 deserialized); 439 } 440 } 441 442 /** 443 * Verifies that object can be smoothly serialized/deserialized. 444 * 445 * The method invokes <br> 446 * verifySelf(object, defineComparator(null, object)); 447 * 448 * @param object - 449 * to be serialized/deserialized 450 */ 451 public static void verifySelf(Object object) 452 throws Exception { 453 454 verifySelf(object, defineComparator(null, object)); 455 } 456 457 /** 458 * Verifies that object can be smoothly serialized/deserialized. 459 * 460 * The method serialize/deserialize <code>object</code> and compare it 461 * with initial <code>object</code>. 462 * 463 * @param object - 464 * object to be serialized/deserialized 465 * @param comparator - 466 * for comparing serialized/deserialized object with initial 467 * object 468 */ 469 public static void verifySelf(Object object, SerializableAssert comparator) 470 throws Exception { 471 472 Serializable initial = (Serializable) object; 473 474 comparator.assertDeserialized(initial, copySerializable(initial)); 475 } 476 477 /** 478 * Verifies that that objects from array can be smoothly 479 * serialized/deserialized. 480 * 481 * The method invokes <br> 482 * verifySelf(objects, defineComparator(null, object[0])); 483 * 484 * @param objects - 485 * array of objects to be serialized/deserialized 486 */ 487 public static void verifySelf(Object[] objects) 488 throws Exception { 489 490 Assert.assertFalse("Empty array", objects.length == 0); 491 verifySelf(objects, defineComparator(null, objects[0])); 492 } 493 494 /** 495 * Verifies that that objects from array can be smoothly 496 * serialized/deserialized. 497 * 498 * The method serialize/deserialize each object in <code>objects</code> 499 * array and compare it with initial object. 500 * 501 * @param objects - 502 * array of objects to be serialized/deserialized 503 * @param comparator - 504 * for comparing serialized/deserialized object with initial 505 * object 506 */ 507 public static void verifySelf(Object[] objects, SerializableAssert comparator) 508 throws Exception { 509 510 Assert.assertFalse("Empty array", objects.length == 0); 511 for(Object entry: objects){ 512 verifySelf(entry, comparator); 513 } 514 } 515 516 private static Serializable getObject(TestCase test, String toAppend) 517 throws Exception { 518 519 StringBuilder path = new StringBuilder("/serialization"); 520 521 path.append(File.separatorChar); 522 path.append(test.getClass().getName().replace('.', File.separatorChar)); 523 path.append(toAppend); 524 525 InputStream in = SerializationTest.class 526 .getResourceAsStream(path.toString()); 527 528 Assert.assertNotNull("Failed to load serialization resource file: " 529 + path, in); 530 531 return getObjectFromStream(in); 532 } 533 534 /** 535 * Creates golden file. 536 * 537 * The folder for created file is: <code>root + test's package name</code>. 538 * The file name is: <code>test's name + "golden.ser"</code> 539 * 540 * @param root - 541 * root directory for serialization resource files 542 * @param test - 543 * test case 544 * @param object - 545 * object to be serialized 546 * @throws IOException - 547 * if I/O error 548 */ 549 public static void createGoldenFile(String root, TestCase test, 550 Object object) throws IOException { 551 552 String goldenPath = test.getClass().getName().replace('.', 553 File.separatorChar) 554 + ".golden.ser"; 555 556 if (root != null) { 557 goldenPath = root + File.separatorChar + goldenPath; 558 } 559 560 561 File goldenFile = new File(goldenPath); 562 goldenFile.getParentFile().mkdirs(); 563 goldenFile.createNewFile(); 564 565 putObjectToStream(object, new FileOutputStream(goldenFile)); 566 567 // don't forget to remove it from test case after using 568 Assert.fail("Generating golden file.\nGolden file name:" 569 + goldenFile.getAbsolutePath()); 570 } 571 572 /** 573 * Copies an object by serializing/deserializing it. 574 * 575 * @param initial - 576 * an object to be copied 577 * @return copy of provided object 578 */ 579 public static Serializable copySerializable(Serializable initial) 580 throws IOException, ClassNotFoundException { 581 582 ByteArrayOutputStream out = new ByteArrayOutputStream(); 583 putObjectToStream(initial, out); 584 ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); 585 586 return getObjectFromStream(in); 587 } 588 } 589