1 package annotations.tests.classfile; 2 3 /*>>> 4 import org.checkerframework.checker.nullness.qual.*; 5 */ 6 7 import java.io.File; 8 import java.io.FileInputStream; 9 import java.io.FileOutputStream; 10 import java.io.IOException; 11 import java.io.InputStream; 12 13 import junit.framework.TestCase; 14 import junit.framework.TestResult; 15 import junit.framework.TestSuite; 16 17 import org.objectweb.asm.ClassReader; 18 19 import annotations.Annotation; 20 import annotations.AnnotationFactory; 21 import annotations.el.AScene; 22 import annotations.io.IndexFileParser; 23 import annotations.io.IndexFileWriter; 24 import annotations.io.classfile.ClassFileReader; 25 import annotations.io.classfile.ClassFileWriter; 26 import annotations.tests.classfile.foo.A; 27 28 /** 29 * This class is the testing framework for the class file/index file 30 * annotations converter. To add a new test, 31 * <ul> 32 * <li>add the class name to array {@link #allTests} 33 * <li>place two files in directory {@link #CLASS_FILE_BASE}: 34 * a .class file (for the unannotated version of the class), 35 * an _Expected.class file (for the annotated version of the class). 36 * <li>place two files in directory {@link #INDEX_FILE_BASE}: 37 * a .java source file (this is not used by the tests -- it is only for 38 * documentation, and is helpful when creating the test files), 39 * a .jaif index file. 40 * <li>Add a <code>testc*()</code> method to test against class file and a 41 * <code>testi*()</code> method to test against index file; this is just so 42 * that JUnit has an accurate count of all tests. 43 * </ul> 44 * 45 * Two types of tests are performed: 46 * <ul> 47 * <li>"c" tests that call testAgainstClass: 48 * Read the annotations from <code>name.jaif</code>, insert them into 49 * <code>name.class</code>, write the results to a temporary file 50 * (name_Generated.class), and compare this generated class file with 51 * <code>name_Expected.class</code>, asserting that they have the same 52 * annotations. 53 * <li>"i" tests that call testAgainstIndexFile: 54 * Read the annotations from the generated class file, and check them 55 * against the annotations from the index file. 56 * </ul> 57 */ 58 public class AnnotationsTest extends TestCase { 59 60 /** 61 * The directory in which to find the index files to test. 62 */ 63 private static final String INDEX_FILE_BASE = 64 "test/annotations/tests/classfile/cases/"; 65 66 /** 67 * The directory in which to find the class files (both .class and _Generated.class) 68 * to test. 69 */ 70 private static final String CLASS_FILE_BASE = 71 "test/annotations-expected/tests/classfile/cases/"; 72 73 /** 74 * An array of all the classes to test. For each name in this array, there 75 * must be a corresponding .jaif file in {@link #INDEX_FILE_BASE} and 76 * .class and _Expected.class files in {@link #CLASS_FILE_BASE} 77 */ 78 public static final String[] allTests = { 79 "TestClassEmpty", 80 "TestClassNonEmpty", 81 "TestFieldSimple", 82 "TestFieldGeneric", 83 "TestLocalVariable", 84 "TestLocalVariableA", 85 "TestLocalVariableGenericArray", 86 "TestTypecast", 87 "TestTypecastGenericArray", 88 "TestTypeTest", 89 "TestObjectCreation", 90 "TestObjectCreationGenericArray", 91 "TestMethodReceiver", 92 "TestMethodReturnTypeGenericArray" 93 }; 94 95 /** 96 * Constructs a new <code>AnnotationsTest</code> with the given name. 97 * 98 * @param s the name of this test case 99 */ AnnotationsTest(String s)100 public AnnotationsTest(String s) { 101 super(s); 102 } 103 104 /** 105 * Runs all the tests in {@link #allTests} and displays the failure and error 106 * counts. 107 */ main(String[] args)108 public static void main(String[] args) { 109 TestSuite suite = new TestSuite(AnnotationsTest.class); 110 TestResult result = new TestResult(); 111 suite.run(result); 112 System.out.println( 113 "AnnotationsTests ran with " + result.failureCount() + " failures and " 114 + result.errorCount() + " errors. (" + result.runCount() 115 + " successes.)"); 116 } 117 118 /** 119 * Prepends {@link #CLASS_FILE_BASE} to s. 120 */ nameClass(String s)121 private String nameClass(String s) { 122 return CLASS_FILE_BASE + s; 123 } 124 125 /** 126 * Prepends {@link #INDEX_FILE_BASE} to s. 127 */ nameIndex(String s)128 private String nameIndex(String s) { 129 return INDEX_FILE_BASE + s; 130 } 131 132 /** 133 * Writes out scene to filename as an index file. 134 * 135 * @param filename the file to write to 136 * @param scene the scene to write out 137 */ writeScene(String filename, AScene scene)138 private void writeScene(String filename, AScene scene) { 139 try { 140 IndexFileWriter.write(scene, filename); 141 } catch (Exception e) { 142 System.err.println("caught exception: "); 143 e.printStackTrace(); 144 fail(); 145 } 146 } 147 148 /** 149 * Reads in the annotations from filename, an index file, into scene. 150 * 151 * @param filename the index file to read from 152 * @param scene the scene to write out to 153 */ readScene(String filename, AScene scene)154 private void readScene(String filename, AScene scene) { 155 try { 156 IndexFileParser.parseFile(filename, scene); 157 } catch (Exception e) { 158 System.err.println("caught exception: "); 159 e.printStackTrace(); 160 fail("caught exception: " + e.toString()); 161 } 162 } 163 164 /** 165 * Reads in the class file from the given filename, inserts the annotations 166 * from scene, and writes out the result into the same file. 167 * 168 * @param filename the class file to insert annotations into 169 * @param scene the scene that contains annotations to be inserted 170 * @param overwrite whether to overwrite existing annotations 171 */ writeClass(String filename, AScene scene, boolean overwrite)172 private void writeClass(String filename, 173 AScene scene, boolean overwrite) { 174 writeClass(filename, filename, scene, overwrite); 175 } 176 177 /** 178 * Like {@link #writeClass(String, AScene, boolean)}, except the class will be read from and written to 179 * different files. 180 * 181 * @param oldFileName the class file to read from 182 * @param newFileName the class file to write to 183 * @param scene the scene that contains annotations to be inserted 184 * @param overwrite whether to overwrite existing annotations 185 */ writeClass( String oldFileName, String newFileName, AScene scene, boolean overwrite)186 private void writeClass( 187 String oldFileName, 188 String newFileName, 189 AScene scene, 190 boolean overwrite) { 191 try { 192 ClassFileWriter.insert( 193 scene, 194 new FileInputStream(oldFileName), 195 new FileOutputStream(newFileName), 196 overwrite); 197 } catch (Throwable e) { 198 System.err.printf("caught exception in writeClass(oldFileName=%s, newFileName=%s, ...):%n", 199 oldFileName, newFileName); 200 e.printStackTrace(); 201 fail(); 202 } 203 } 204 205 /** 206 * Reads in the annotations from the class file at filename and inserts them 207 * into scene. 208 * 209 * @param filename the class file to read from 210 * @param scene the scene to write to 211 */ readClass(String filename, AScene scene)212 private void readClass(String filename, 213 AScene scene) { 214 try { 215 ClassFileReader.read(scene, filename); 216 } catch (Exception e) { 217 System.err.printf("caught exception while reading %s:%n", new File(filename).getAbsolutePath()); 218 e.printStackTrace(); 219 fail(); 220 } 221 } 222 223 /** 224 * Creates scene from the annotations in the given index file. 225 * 226 * @param indexFile the index file to create a scene from 227 * @return the scene created from the given index file 228 */ createScene(String indexFile)229 private AScene createScene(String indexFile) { 230 AScene scene = 231 new AScene(); 232 readScene(indexFile, scene); 233 return scene; 234 } 235 236 /** 237 * Asserts that the annotations in two class files match. 238 * This method will cause this test to fail if there 239 * is a mismatch in annotations, or if there is a mismatch in either field 240 * or method information that means these classes cannot reasonably be 241 * compared. 242 * 243 * @param correctClass the file name of the correct version of the class 244 * @param generatedClass the file name of the version of the class being tested 245 */ assertClassAnnotations(String correctClass, String generatedClass)246 private void assertClassAnnotations(String correctClass, String generatedClass) { 247 248 try { 249 InputStream correctIs = new FileInputStream(correctClass); 250 251 InputStream generatedIs = new FileInputStream(generatedClass); 252 253 ClassReader crCorrect = new ClassReader(correctIs); 254 ClassReader crGenerated = new ClassReader(generatedIs); 255 256 AnnotationVerifier av = new AnnotationVerifier(); 257 258 crCorrect.accept(av.originalVisitor(), false); 259 crGenerated.accept(av.newVisitor(), false); 260 261 try { 262 av.verify(); 263 } catch (AnnotationVerifier.AnnotationMismatchException e) { 264 String message = String.format("assertClassAnnotations (consider running javap on the two .class files):%n correctClass %s%n generatedClass %s%n%s", correctClass, generatedClass, e.toString()); 265 System.out.println(); 266 System.out.println(message); 267 av.verifyPrettyPrint(); 268 System.out.println(message); 269 System.out.println(); 270 fail(message); 271 } 272 273 } catch (IOException e) { 274 fail("IOException caught: " + e); 275 } 276 } 277 278 /** 279 * Runs a test that: 280 * <li> reads annotations from indexFileName, 281 * <li> inserts them into baseClassName.class, 282 * <li> writes the result out to baseClassName_Generated.class, and 283 * <li> asserts that the results written out match baseClassName_Expected.class 284 */ testAgainstClass(String indexFileName, String baseClassName)285 private void testAgainstClass(String indexFileName, String baseClassName) { 286 String base = baseClassName + ".class"; 287 String expected = baseClassName + "_Expected.class"; 288 String generated = baseClassName + "_Generated.class"; 289 290 AScene scene = new AScene(); 291 292 // read in annotations from index file to scene 293 readScene(indexFileName, scene); 294 295 // read in class from base, merge annotations from scene and 296 // write out to generated 297 writeClass(base, generated, scene, true); 298 299 // assert that generated class has same annotations as expected class 300 assertClassAnnotations(expected, generated); 301 } 302 303 /** 304 * Runs a test that: 305 * <li> reads annotations from indexFileName, 306 * <li> inserts them into className 307 * <li> writes results out to a temporary class file 308 * <li> reads annotations from that class file, and 309 * <li> asserts that results written out match the annotations in the index file. 310 */ testAgainstIndexFile(String indexFileName, String className)311 private void testAgainstIndexFile(String indexFileName, String className) { 312 AScene correctScene = createScene(indexFileName); 313 314 String basename = className; 315 if (basename.endsWith(".class")) { 316 basename = basename.substring(0, basename.length() - 6); 317 } 318 319 File tempFile = new File(basename+"_temp.class"); 320 321 writeClass(className, tempFile.toString(), correctScene, true); 322 323 AScene generatedScene = new AScene(); 324 325 readClass(tempFile.toString(), generatedScene); 326 327 correctScene.prune(); 328 generatedScene.prune(); 329 330 if (!correctScene.equals(generatedScene)) { 331 String fname1 = className+"-from-indexfile.txt"; 332 String fname2 = className+"-via-classfile-scene.txt"; 333 writeScene(fname1, correctScene); 334 writeScene(fname2, generatedScene); 335 fail(String.format("For annotations read from %s :%n After writing to class file and re-reading, result differed.%n Scene read from index file is in %s .%n Scene generated from class file is in %s .%n Also consider running javap -v on %s .%n", indexFileName, fname1, fname2, tempFile)); 336 } 337 338 tempFile.delete(); 339 340 } 341 342 /** 343 * Runs both types of tests (against class file and index file), on all 344 * classes specified by {@link #allTests} 345 */ testAll()346 public void testAll() throws Exception { 347 // for (String s : allTests) { 348 // testAgainstIndexFile(nameIndex(s + ".jaif"), nameClass(s+".class")); 349 // testAgainstClass(nameIndex(s + ".jaif"), nameClass(s)); 350 // } 351 } 352 353 /** 354 * Runs a test on class files for package-info. 355 */ testcPackage()356 public void testcPackage() { 357 testAgainstClass(nameIndex("package-info.jaif"), 358 nameClass("package-info")); 359 } 360 361 /** 362 * Runs a test on index files for package-info. 363 */ testiPackage()364 public void testiPackage() { 365 testAgainstIndexFile(nameIndex("package-info.jaif"), 366 nameClass("package-info.class")); 367 } 368 369 /** 370 * Runs a test on class files for TestClassEmpty. 371 */ testcClassEmpty()372 public void testcClassEmpty() { 373 testAgainstClass(nameIndex("TestClassEmpty.jaif"), 374 nameClass("TestClassEmpty")); 375 } 376 377 /** 378 * Runs a test on index files for TestClassEmpty. 379 */ testiClassEmpty()380 public void testiClassEmpty() { 381 testAgainstIndexFile(nameIndex("TestClassEmpty.jaif"), 382 nameClass("TestClassEmpty.class")); 383 } 384 385 /** 386 * Runs a test on class files for TestClassNonEmpty. 387 */ testcClassNonEmpty()388 public void testcClassNonEmpty() { 389 testAgainstClass(nameIndex("TestClassNonEmpty.jaif"), 390 nameClass("TestClassNonEmpty")); 391 } 392 393 /** 394 * Runs a test on index files for TestClassNonEmpty. 395 */ testiClassNonEmpty()396 public void testiClassNonEmpty() { 397 testAgainstIndexFile(nameIndex("TestClassNonEmpty.jaif"), 398 nameClass("TestClassNonEmpty.class")); 399 } 400 401 /** 402 * Runs a test on class files for TestFieldSimple. 403 */ testcFieldSimple()404 public void testcFieldSimple() { 405 testAgainstClass(nameIndex("TestFieldSimple.jaif"), 406 nameClass("TestFieldSimple")); 407 } 408 409 /** 410 * Runs a test on index files for TestFieldSimple. 411 */ testiFieldSimple()412 public void testiFieldSimple() { 413 testAgainstIndexFile(nameIndex("TestFieldSimple.jaif"), 414 nameClass("TestFieldSimple.class")); 415 } 416 417 /** 418 * Runs a test on class files for TestFieldGeneric. 419 */ testcFieldGeneric()420 public void testcFieldGeneric() { 421 testAgainstClass(nameIndex("TestFieldGeneric.jaif"), 422 nameClass("TestFieldGeneric")); 423 } 424 425 /** 426 * Runs a test on index files for TestFieldGeneric. 427 */ testiFieldGeneric()428 public void testiFieldGeneric() { 429 testAgainstIndexFile(nameIndex("TestFieldGeneric.jaif"), 430 nameClass("TestFieldGeneric.class")); 431 } 432 433 /** 434 * Runs a test on class files for TestLocalVariable. 435 */ testcLocalVariable()436 public void testcLocalVariable() { 437 testAgainstClass(nameIndex("TestLocalVariable.jaif"), 438 nameClass("TestLocalVariable")); 439 } 440 441 /** 442 * Runs a test on index files for TestLocalVariable. 443 */ testiLocalVariable()444 public void testiLocalVariable() { 445 testAgainstIndexFile(nameIndex("TestLocalVariable.jaif"), 446 nameClass("TestLocalVariable.class")); 447 } 448 449 /** 450 * Runs a test on class files for TestLocalVariableA. 451 */ testcLocalVariableA()452 public void testcLocalVariableA() { 453 testAgainstClass(nameIndex("TestLocalVariableA.jaif"), 454 nameClass("TestLocalVariableA")); 455 } 456 457 /** 458 * Runs a test on index files for TestLocalVariableA. 459 */ testiLocalVariableA()460 public void testiLocalVariableA() { 461 testAgainstIndexFile(nameIndex("TestLocalVariableA.jaif"), 462 nameClass("TestLocalVariableA.class")); 463 } 464 465 /** 466 * Runs a test on class files for TestLocalVariableGenericArray. 467 */ testcLocalVariableGenericArray()468 public void testcLocalVariableGenericArray() { 469 testAgainstClass(nameIndex("TestLocalVariableGenericArray.jaif"), 470 nameClass("TestLocalVariableGenericArray")); 471 } 472 473 /** 474 * Runs a test on index files for TestLocalVariableGenericArray. 475 */ testiLocalVariableGenericArray()476 public void testiLocalVariableGenericArray() { 477 testAgainstIndexFile(nameIndex("TestLocalVariableGenericArray.jaif"), 478 nameClass("TestLocalVariableGenericArray.class")); 479 } 480 481 /** 482 * Runs a test on class files for TestTypecast. 483 */ testcTypecast()484 public void testcTypecast() { 485 testAgainstClass(nameIndex("TestTypecast.jaif"), 486 nameClass("TestTypecast")); 487 } 488 489 /** 490 * Runs a test on index files for TestTypecast. 491 */ testiTypecast()492 public void testiTypecast() { 493 testAgainstIndexFile(nameIndex("TestTypecast.jaif"), 494 nameClass("TestTypecast.class")); 495 } 496 497 /** 498 * Runs a test on class files for TestTypecastGenericArray. 499 */ testcTypecastGenericArray()500 public void testcTypecastGenericArray() { 501 testAgainstClass(nameIndex("TestTypecastGenericArray.jaif"), 502 nameClass("TestTypecastGenericArray")); 503 } 504 505 /** 506 * Runs a test on index files for TestTypecastGenericArray. 507 */ testiTypecastGenericArray()508 public void testiTypecastGenericArray() { 509 testAgainstIndexFile(nameIndex("TestTypecastGenericArray.jaif"), 510 nameClass("TestTypecastGenericArray.class")); 511 } 512 513 /** 514 * Runs a test on class files for TestTypeTest. 515 */ testcTypeTest()516 public void testcTypeTest() { 517 testAgainstClass(nameIndex("TestTypeTest.jaif"), 518 nameClass("TestTypeTest")); 519 } 520 521 /** 522 * Runs a test on index files for TestTypeTest. 523 */ testiTypeTest()524 public void testiTypeTest() { 525 testAgainstIndexFile(nameIndex("TestTypeTest.jaif"), 526 nameClass("TestTypeTest.class")); 527 } 528 529 /** 530 * Runs a test on class files for TestObjectCreation. 531 */ testcObjectCreation()532 public void testcObjectCreation() { 533 testAgainstClass(nameIndex("TestObjectCreation.jaif"), 534 nameClass("TestObjectCreation")); 535 } 536 537 /** 538 * Runs a test on index files for TestObjectCreation. 539 */ testiObjectCreation()540 public void testiObjectCreation() { 541 testAgainstIndexFile(nameIndex("TestObjectCreation.jaif"), 542 nameClass("TestObjectCreation.class")); 543 } 544 545 /** 546 * Runs a test on class files for TestObjectCreationGenericArray. 547 */ testcObjectCreationGenericArray()548 public void testcObjectCreationGenericArray() { 549 testAgainstClass(nameIndex("TestObjectCreationGenericArray.jaif"), 550 nameClass("TestObjectCreationGenericArray")); 551 } 552 553 /** 554 * Runs a test on index files for TestObjectCreationGenericArray. 555 */ testiObjectCreationGenericArray()556 public void testiObjectCreationGenericArray() { 557 testAgainstIndexFile(nameIndex("TestObjectCreationGenericArray.jaif"), 558 nameClass("TestObjectCreationGenericArray.class")); 559 } 560 561 /** 562 * Runs a test on class files for TestMethodReceiver. 563 */ testcMethodReceiver()564 public void testcMethodReceiver() { 565 testAgainstClass(nameIndex("TestMethodReceiver.jaif"), 566 nameClass("TestMethodReceiver")); 567 } 568 569 /** 570 * Runs a test on index files for TestMethodReceiver. 571 */ testiMethodReceiver()572 public void testiMethodReceiver() { 573 testAgainstIndexFile(nameIndex("TestMethodReceiver.jaif"), 574 nameClass("TestMethodReceiver.class")); 575 } 576 577 /** 578 * Runs a test on class files for TestMethodReturnTypeGenericArray. 579 */ testcMethodReturnTypeGenericArray()580 public void testcMethodReturnTypeGenericArray() { 581 testAgainstClass(nameIndex("TestMethodReturnTypeGenericArray.jaif"), 582 nameClass("TestMethodReturnTypeGenericArray")); 583 } 584 585 /** 586 * Runs a test on index files for TestMethodReturnTypeGenericArray. 587 */ testiMethodReturnTypeGenericArray()588 public void testiMethodReturnTypeGenericArray() { 589 testAgainstIndexFile(nameIndex("TestMethodReturnTypeGenericArray.jaif"), 590 nameClass("TestMethodReturnTypeGenericArray.class")); 591 } 592 593 // // Call javap programmatically. 594 // public static void javap(InputStream is, PrintStream ps) { 595 // JavapEnvironment env = new JavapEnvironment(); 596 // PrintWriter pw = new PrintWriter(ps); 597 // JavapPrinter javapPrinter = new JavapPrinter(is, pw, env); 598 // javapPrinter.print(); 599 // pw.flush(); 600 // } 601 602 } 603