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 junit.framework.TestCase; 26 import java.io.ByteArrayInputStream; 27 import java.io.ByteArrayOutputStream; 28 import java.io.File; 29 import java.io.FileOutputStream; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.ObjectInputStream; 33 import java.io.ObjectOutputStream; 34 import java.io.OutputStream; 35 import java.io.Serializable; 36 import java.lang.reflect.Method; 37 import java.security.Permission; 38 import java.security.PermissionCollection; 39 import java.util.Collection; 40 import java.util.Collections; 41 import java.util.HashSet; 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 verifyGolden(this, getData()); 102 } 103 104 /** 105 * Returns golden file for an object being tested. 106 * 107 * @param index array index of tested data (as returned by 108 * {@link #getData() getData()}) 109 * @return corresponding golden file 110 */ 111 protected File getDataFile(int index) { 112 String name = this.getClass().getName(); 113 int dot = name.lastIndexOf("."); 114 String path = name.substring(0, dot).replace('.', File.separatorChar); 115 if (outputPath != null && outputPath.length() != 0) { 116 path = outputPath + File.separator + path; 117 } 118 119 return new File(path, name.substring(dot + 1) + "." + index + ".dat"); 120 } 121 122 /** 123 * Working method for files generation mode. Serializes test objects 124 * returned by {@link #getData() getData()}to golden files, each object to 125 * a separate file. 126 * 127 * @throws IOException 128 */ 129 protected void produceGoldenFiles() throws IOException { 130 131 String goldenPath = outputPath + File.separatorChar 132 + getClass().getName().replace('.', File.separatorChar) 133 + ".golden."; 134 135 Object[] data = getData(); 136 for (int i = 0; i < data.length; i++) { 137 138 File goldenFile = new File(goldenPath + i + ".ser"); 139 goldenFile.getParentFile().mkdirs(); 140 goldenFile.createNewFile(); 141 142 putObjectToStream(data[i], new FileOutputStream(goldenFile)); 143 } 144 } 145 146 /** 147 * Serializes specified object to an output stream. 148 */ 149 public static void putObjectToStream(Object obj, OutputStream os) 150 throws IOException { 151 ObjectOutputStream oos = new ObjectOutputStream(os); 152 oos.writeObject(obj); 153 oos.flush(); 154 oos.close(); 155 } 156 157 /** 158 * Deserializes single object from an input stream. 159 */ 160 public static Serializable getObjectFromStream(InputStream is) 161 throws IOException, ClassNotFoundException { 162 ObjectInputStream ois = new ObjectInputStream(is); 163 Object result = ois.readObject(); 164 ois.close(); 165 return (Serializable)result; 166 } 167 168 /** 169 * Interface to compare (de)serialized objects 170 * 171 * Should be implemented if a class under test does not provide specific 172 * equals() method and it's instances should to be compared manually. 173 */ 174 public interface SerializableAssert { 175 176 /** 177 * Compares deserialized and reference objects. 178 * 179 * @param initial - initial object used for creating serialized form 180 * @param deserialized - deserialized object 181 */ 182 void assertDeserialized(Serializable initial, Serializable deserialized); 183 } 184 185 // default comparator for a class that has equals(Object) method 186 private final static SerializableAssert DEFAULT_COMPARATOR = new SerializableAssert() { 187 public void assertDeserialized(Serializable initial, Serializable deserialized) { 188 assertEquals(initial, deserialized); 189 } 190 }; 191 192 /** 193 * Comparator for verifying that deserialized object is the same as initial. 194 */ 195 public final static SerializableAssert SAME_COMPARATOR = new SerializableAssert() { 196 public void assertDeserialized(Serializable initial, Serializable deserialized) { 197 assertSame(initial, deserialized); 198 } 199 }; 200 201 /** 202 * Comparator for Throwable objects 203 */ 204 public final static SerializableAssert THROWABLE_COMPARATOR = new SerializableAssert() { 205 public void assertDeserialized(Serializable initial, Serializable deserialized) { 206 Throwable initThr = (Throwable) initial; 207 Throwable dserThr = (Throwable) deserialized; 208 209 // verify class 210 assertEquals(initThr.getClass(), dserThr.getClass()); 211 212 // verify message 213 assertEquals(initThr.getMessage(), dserThr.getMessage()); 214 215 // verify cause 216 if (initThr.getCause() == null) { 217 assertNull(dserThr.getCause()); 218 } else { 219 assertNotNull(dserThr.getCause()); 220 THROWABLE_COMPARATOR.assertDeserialized(initThr.getCause(), 221 dserThr.getCause()); 222 } 223 } 224 }; 225 226 /** 227 * Comparator for PermissionCollection objects 228 */ 229 public final static SerializableAssert PERMISSION_COLLECTION_COMPARATOR = new SerializableAssert() { 230 public void assertDeserialized(Serializable initial, Serializable deserialized) { 231 232 PermissionCollection initPC = (PermissionCollection) initial; 233 PermissionCollection dserPC = (PermissionCollection) deserialized; 234 235 // verify class 236 assertEquals(initPC.getClass(), dserPC.getClass()); 237 238 // verify 'readOnly' field 239 assertEquals(initPC.isReadOnly(), dserPC.isReadOnly()); 240 241 // verify collection of permissions 242 Collection<Permission> refCollection = new HashSet<Permission>( 243 Collections.list(initPC.elements())); 244 Collection<Permission> tstCollection = new HashSet<Permission>( 245 Collections.list(dserPC.elements())); 246 247 assertEquals(refCollection, tstCollection); 248 } 249 }; 250 251 /** 252 * Returns <code>comparator</code> for provided serializable 253 * <code>object</code>. 254 * 255 * The <code>comparator</code> is searched in the following order: <br> 256 * - if <code>test</code> implements SerializableAssert interface then it is 257 * selected as </code>comparator</code>.<br>- if passed <code>object</code> 258 * has class in its classes hierarchy that overrides <code>equals(Object)</code> 259 * method then <code>DEFAULT_COMPARATOR</code> is selected.<br> - the 260 * method tries to select one of known comparators basing on <code>object's</code> 261 * class,for example, if passed <code>object</code> is instance of 262 * Throwable then <code>THROWABLE_COMPARATOR</code> is used.<br> 263 * - otherwise RuntimeException is thrown 264 * 265 * @param test - test case 266 * @param object - object to be compared 267 * @return object's comparator 268 */ 269 public static SerializableAssert defineComparator(Object test, Object object) 270 throws Exception { 271 272 if (test instanceof SerializableAssert) { 273 return (SerializableAssert) test; 274 } 275 276 Method m = object.getClass().getMethod("equals", new Class[] { Object.class }); 277 if (m.getDeclaringClass() != Object.class) { 278 // one of classes overrides Object.equals(Object) method 279 // use default comparator 280 return DEFAULT_COMPARATOR; 281 } 282 283 // TODO use generics to detect comparator 284 // instead of 'instanceof' for the first element 285 if (object instanceof Throwable) { 286 return THROWABLE_COMPARATOR; 287 } 288 if (object instanceof PermissionCollection) { 289 return PERMISSION_COLLECTION_COMPARATOR; 290 } 291 throw new RuntimeException("Failed to detect comparator"); 292 } 293 294 /** 295 * Verifies that object deserialized from golden file correctly. 296 * 297 * The method invokes <br> 298 * verifyGolden(test, object, defineComparator(test, object)); 299 * 300 * @param test - test case 301 * @param object - to be compared 302 */ 303 public static void verifyGolden(Object test, Object object) throws Exception { 304 verifyGolden(test, object, defineComparator(test, object)); 305 } 306 307 /** 308 * Verifies that object deserialized from golden file correctly. 309 * 310 * The method loads "<code>testName</code>.golden.ser" resource file 311 * from "<module root>/src/test/resources/serialization/<code>testPackage</code>" 312 * folder, reads an object from the loaded file and compares it with 313 * <code>object</code> using specified <code>comparator</code>. 314 * 315 * @param test - test case 316 * @param object - to be compared 317 * @param comparator - for comparing (de)serialized objects 318 */ 319 public static void verifyGolden(Object test, Object object, SerializableAssert comparator) 320 throws Exception { 321 assertNotNull("Null comparator", comparator); 322 Serializable deserialized = getObject(test, ".golden.ser"); 323 comparator.assertDeserialized((Serializable) object, deserialized); 324 } 325 326 /** 327 * Verifies that objects from array deserialized from golden files 328 * correctly. 329 * 330 * The method invokes <br> 331 * verifyGolden(test, objects, defineComparator(test, object[0])); 332 * 333 * @param test - test case 334 * @param objects - array of objects to be compared 335 */ 336 public static void verifyGolden(Object test, Object[] objects) throws Exception { 337 assertFalse("Empty array", objects.length == 0); 338 verifyGolden(test, objects, defineComparator(test, objects[0])); 339 } 340 341 /** 342 * Verifies that objects from array deserialized from golden files 343 * correctly. 344 * 345 * The method loads "<code>testName</code>.golden.<code>N</code>.ser" 346 * resource files from "<module root>/src/test/resources/serialization/<code>testPackage</code>" 347 * folder, from each loaded file it reads an object from and compares it 348 * with corresponding object in provided array (i.e. <code>objects[N]</code>) 349 * using specified <code>comparator</code>. (<code>N</code> is index 350 * in object's array.) 351 * 352 * @param test - test case 353 * @param objects - array of objects to be compared 354 * @param comparator - for comparing (de)serialized objects 355 */ 356 public static void verifyGolden(Object test, Object[] objects, SerializableAssert comparator) 357 throws Exception { 358 assertFalse("Empty array", objects.length == 0); 359 for (int i = 0; i < objects.length; i++) { 360 Serializable deserialized = getObject(test, ".golden." + i + ".ser"); 361 comparator.assertDeserialized((Serializable) objects[i], deserialized); 362 } 363 } 364 365 /** 366 * Verifies that object can be smoothly serialized/deserialized. 367 * 368 * The method invokes <br> 369 * verifySelf(object, defineComparator(null, object)); 370 * 371 * @param object - to be serialized/deserialized 372 */ 373 public static void verifySelf(Object object) throws Exception { 374 verifySelf(object, defineComparator(null, object)); 375 } 376 377 /** 378 * Verifies that object can be smoothly serialized/deserialized. 379 * 380 * The method serialize/deserialize <code>object</code> and compare it 381 * with initial <code>object</code>. 382 * 383 * @param object - object to be serialized/deserialized 384 * @param comparator - for comparing serialized/deserialized object with initial object 385 */ 386 public static void verifySelf(Object object, SerializableAssert comparator) throws Exception { 387 Serializable initial = (Serializable) object; 388 comparator.assertDeserialized(initial, copySerializable(initial)); 389 } 390 391 /** 392 * Verifies that that objects from array can be smoothly 393 * serialized/deserialized. 394 * 395 * The method invokes <br> 396 * verifySelf(objects, defineComparator(null, object[0])); 397 * 398 * @param objects - array of objects to be serialized/deserialized 399 */ 400 public static void verifySelf(Object[] objects) throws Exception { 401 assertFalse("Empty array", objects.length == 0); 402 verifySelf(objects, defineComparator(null, objects[0])); 403 } 404 405 /** 406 * Verifies that that objects from array can be smoothly 407 * serialized/deserialized. 408 * 409 * The method serialize/deserialize each object in <code>objects</code> 410 * array and compare it with initial object. 411 * 412 * @param objects - array of objects to be serialized/deserialized 413 * @param comparator - for comparing serialized/deserialized object with initial object 414 */ 415 public static void verifySelf(Object[] objects, SerializableAssert comparator) 416 throws Exception { 417 assertFalse("Empty array", objects.length == 0); 418 for (Object entry: objects){ 419 verifySelf(entry, comparator); 420 } 421 } 422 423 private static Serializable getObject(Object test, String toAppend) throws Exception { 424 StringBuilder path = new StringBuilder("/serialization"); 425 path.append(File.separatorChar); 426 path.append(test.getClass().getName().replace('.', File.separatorChar)); 427 path.append(toAppend); 428 429 String pathString = path.toString(); 430 431 InputStream in = SerializationTest.class.getResourceAsStream(pathString); 432 assertNotNull("Failed to load serialization resource file: " + path, in); 433 return getObjectFromStream(in); 434 } 435 436 /** 437 * Creates golden file. 438 * 439 * The folder for created file is: <code>root + test's package name</code>. 440 * The file name is: <code>test's name + "golden.ser"</code> 441 * 442 * @param root - root directory for serialization resource files 443 * @param test - test case 444 * @param object - to be serialized 445 * @throws IOException - if I/O error 446 */ 447 public static void createGoldenFile(String root, TestCase test, Object object) 448 throws IOException { 449 String goldenPath = (test.getClass().getName().replace('.', File.separatorChar) 450 + ".golden.ser"); 451 if (root != null) { 452 goldenPath = root + File.separatorChar + goldenPath; 453 } 454 455 File goldenFile = new File(goldenPath); 456 goldenFile.getParentFile().mkdirs(); 457 assertTrue("Could not create " + goldenFile.getParentFile(), 458 goldenFile.getParentFile().isDirectory()); 459 goldenFile.createNewFile(); 460 putObjectToStream(object, new FileOutputStream(goldenFile)); 461 462 // don't forget to remove it from test case after using 463 fail("Generating golden file. Golden file name: " + goldenFile.getAbsolutePath()); 464 } 465 466 /** 467 * Copies an object by serializing/deserializing it. 468 * 469 * @param initial - an object to be copied 470 * @return copy of provided object 471 */ 472 public static Serializable copySerializable(Serializable initial) 473 throws IOException, ClassNotFoundException { 474 ByteArrayOutputStream out = new ByteArrayOutputStream(); 475 putObjectToStream(initial, out); 476 ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); 477 return getObjectFromStream(in); 478 } 479 } 480