1 /* 2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.util.*; 25 import java.io.*; 26 import java.net.*; 27 import java.nio.file.*; 28 import java.nio.file.attribute.*; 29 import java.nio.charset.*; 30 31 import com.sun.tools.sjavac.Main; 32 33 public 34 class SJavac { 35 36 public static void main(String... args) throws Exception { 37 SJavac s = new SJavac(); 38 s.test(); 39 } 40 41 FileSystem defaultfs = FileSystems.getDefault(); 42 43 // Where to put generated sources that will 44 // test aspects of sjavac, ie JTWork/scratch/gensrc 45 Path gensrc; 46 // More gensrc dirs are used to test merging of serveral source roots. 47 Path gensrc2; 48 Path gensrc3; 49 50 // Where to put compiled classes. 51 Path bin; 52 // Where to put c-header files. 53 Path headers; 54 55 // The sjavac compiler. 56 Main main = new Main(); 57 58 // Remember the previous bin and headers state here. 59 Map<String,Long> previous_bin_state; 60 Map<String,Long> previous_headers_state; 61 62 public void test() throws Exception { 63 gensrc = defaultfs.getPath("gensrc"); 64 gensrc2 = defaultfs.getPath("gensrc2"); 65 gensrc3 = defaultfs.getPath("gensrc3"); 66 bin = defaultfs.getPath("bin"); 67 headers = defaultfs.getPath("headers"); 68 69 Files.createDirectory(gensrc); 70 Files.createDirectory(gensrc2); 71 Files.createDirectory(gensrc3); 72 Files.createDirectory(bin); 73 Files.createDirectory(headers); 74 75 initialCompile(); 76 incrementalCompileNoChanges(); 77 incrementalCompileDroppingClasses(); 78 incrementalCompileWithChange(); 79 incrementalCompileDropAllNatives(); 80 incrementalCompileAddNative(); 81 incrementalCompileChangeNative(); 82 compileWithOverrideSource(); 83 compileWithInvisibleSources(); 84 compileCircularSources(); 85 compileExcludingDependency(); 86 87 delete(gensrc); 88 delete(gensrc2); 89 delete(gensrc3); 90 delete(bin); 91 delete(headers); 92 } 93 94 void initialCompile() throws Exception { 95 System.out.println("\nInitial compile of gensrc."); 96 System.out.println("----------------------------"); 97 populate(gensrc, 98 "alfa/AINT.java", 99 "package alfa; public interface AINT { void aint(); }", 100 101 "alfa/A.java", 102 "package alfa; public class A implements AINT { "+ 103 "public final static int DEFINITION = 17; public void aint() { } }", 104 105 "alfa/AA.java", 106 "package alfa;"+ 107 "// A package private class, not contributing to the public api.\n"+ 108 "class AA {"+ 109 " // A properly nested static inner class.\n"+ 110 " static class AAA { }\n"+ 111 " // A properly nested inner class.\n"+ 112 " class AAAA { }\n"+ 113 " Runnable foo() {\n"+ 114 " // A proper anonymous class.\n"+ 115 " return new Runnable() { public void run() { } };\n"+ 116 " }\n"+ 117 " AAA aaa;\n"+ 118 " AAAA aaaa;\n"+ 119 " AAAAA aaaaa;\n"+ 120 "}\n"+ 121 "class AAAAA {\n"+ 122 " // A bad auxiliary class, but no one is referencing it\n"+ 123 " // from outside of this source file, therefore it is ok.\n"+ 124 "}\n", 125 126 "beta/BINT.java", 127 "package beta;public interface BINT { void foo(); }", 128 129 "beta/B.java", 130 "package beta; import alfa.A; public class B {"+ 131 "private int b() { return A.DEFINITION; } native void foo(); }"); 132 133 compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1", 134 "--server:portfile=testserver,background=false", "--log=debug"); 135 previous_bin_state = collectState(bin); 136 previous_headers_state = collectState(headers); 137 } 138 139 void incrementalCompileNoChanges() throws Exception { 140 System.out.println("\nTesting that no change in sources implies no change in binaries."); 141 System.out.println("------------------------------------------------------------------"); 142 compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1", 143 "--server:portfile=testserver,background=false", "--log=debug"); 144 Map<String,Long> new_bin_state = collectState(bin); 145 verifyEqual(new_bin_state, previous_bin_state); 146 Map<String,Long> new_headers_state = collectState(headers); 147 verifyEqual(previous_headers_state, new_headers_state); 148 } 149 150 void incrementalCompileDroppingClasses() throws Exception { 151 System.out.println("\nTesting that deleting AA.java deletes all"); 152 System.out.println("generated inner class as well as AA.class"); 153 System.out.println("-----------------------------------------"); 154 removeFrom(gensrc, "alfa/AA.java"); 155 compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1", 156 "--server:portfile=testserver,background=false", "--log=debug"); 157 Map<String,Long> new_bin_state = collectState(bin); 158 verifyThatFilesHaveBeenRemoved(previous_bin_state, new_bin_state, 159 "bin/alfa/AA$1.class", 160 "bin/alfa/AA$AAAA.class", 161 "bin/alfa/AA$AAA.class", 162 "bin/alfa/AAAAA.class", 163 "bin/alfa/AA.class"); 164 165 previous_bin_state = new_bin_state; 166 Map<String,Long> new_headers_state = collectState(headers); 167 verifyEqual(previous_headers_state, new_headers_state); 168 } 169 170 void incrementalCompileWithChange() throws Exception { 171 System.out.println("\nNow update the A.java file with a new timestamps and"); 172 System.out.println("new final static definition. This should trigger a recompile,"); 173 System.out.println("not only of alfa, but also beta."); 174 System.out.println("But check that the generated native header was not updated!"); 175 System.out.println("Since we did not modify the native api of B."); 176 System.out.println("-------------------------------------------------------------"); 177 178 populate(gensrc,"alfa/A.java", 179 "package alfa; public class A implements AINT { "+ 180 "public final static int DEFINITION = 18; public void aint() { } private void foo() { } }"); 181 182 compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1", 183 "--server:portfile=testserver,background=false", "--log=debug"); 184 Map<String,Long> new_bin_state = collectState(bin); 185 186 verifyNewerFiles(previous_bin_state, new_bin_state, 187 "bin/alfa/A.class", 188 "bin/alfa/AINT.class", 189 "bin/beta/B.class", 190 "bin/beta/BINT.class", 191 "bin/javac_state"); 192 previous_bin_state = new_bin_state; 193 194 Map<String,Long> new_headers_state = collectState(headers); 195 verifyEqual(new_headers_state, previous_headers_state); 196 } 197 198 void incrementalCompileDropAllNatives() throws Exception { 199 System.out.println("\nNow update the B.java file with one less native method,"); 200 System.out.println("ie it has no longer any methods!"); 201 System.out.println("Verify that beta_B.h is removed!"); 202 System.out.println("---------------------------------------------------------"); 203 204 populate(gensrc,"beta/B.java", 205 "package beta; import alfa.A; public class B {"+ 206 "private int b() { return A.DEFINITION; } }"); 207 208 compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1", 209 "--server:portfile=testserver,background=false", "--log=debug"); 210 Map<String,Long> new_bin_state = collectState(bin); 211 verifyNewerFiles(previous_bin_state, new_bin_state, 212 "bin/beta/B.class", 213 "bin/beta/BINT.class", 214 "bin/javac_state"); 215 previous_bin_state = new_bin_state; 216 217 Map<String,Long> new_headers_state = collectState(headers); 218 verifyThatFilesHaveBeenRemoved(previous_headers_state, new_headers_state, 219 "headers/beta_B.h"); 220 previous_headers_state = new_headers_state; 221 } 222 223 void incrementalCompileAddNative() throws Exception { 224 System.out.println("\nNow update the B.java file with a final static annotated with @Native."); 225 System.out.println("Verify that beta_B.h is added again!"); 226 System.out.println("------------------------------------------------------------------------"); 227 228 populate(gensrc,"beta/B.java", 229 "package beta; import alfa.A; public class B {"+ 230 "private int b() { return A.DEFINITION; } "+ 231 "@java.lang.annotation.Native final static int alfa = 42; }"); 232 233 compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1", 234 "--server:portfile=testserver,background=false", "--log=debug"); 235 Map<String,Long> new_bin_state = collectState(bin); 236 verifyNewerFiles(previous_bin_state, new_bin_state, 237 "bin/beta/B.class", 238 "bin/beta/BINT.class", 239 "bin/javac_state"); 240 previous_bin_state = new_bin_state; 241 242 Map<String,Long> new_headers_state = collectState(headers); 243 verifyThatFilesHaveBeenAdded(previous_headers_state, new_headers_state, 244 "headers/beta_B.h"); 245 previous_headers_state = new_headers_state; 246 } 247 248 void incrementalCompileChangeNative() throws Exception { 249 System.out.println("\nNow update the B.java file with a new value for the final static"+ 250 " annotated with @Native."); 251 System.out.println("Verify that beta_B.h is rewritten again!"); 252 System.out.println("-------------------------------------------------------------------"); 253 254 populate(gensrc,"beta/B.java", 255 "package beta; import alfa.A; public class B {"+ 256 "private int b() { return A.DEFINITION; } "+ 257 "@java.lang.annotation.Native final static int alfa = 43; }"); 258 259 compile("gensrc", "-d", "bin", "-h", "headers", "-j", "1", 260 "--server:portfile=testserver,background=false", "--log=debug"); 261 Map<String,Long> new_bin_state = collectState(bin); 262 verifyNewerFiles(previous_bin_state, new_bin_state, 263 "bin/beta/B.class", 264 "bin/beta/BINT.class", 265 "bin/javac_state"); 266 previous_bin_state = new_bin_state; 267 268 Map<String,Long> new_headers_state = collectState(headers); 269 verifyNewerFiles(previous_headers_state, new_headers_state, 270 "headers/beta_B.h"); 271 previous_headers_state = new_headers_state; 272 } 273 274 void compileWithOverrideSource() throws Exception { 275 System.out.println("\nNow verify that we can override sources to be compiled."); 276 System.out.println("Compile gensrc and gensrc2. However do not compile broken beta.B in gensrc,"); 277 System.out.println("only compile ok beta.B in gensrc2."); 278 System.out.println("---------------------------------------------------------------------------"); 279 280 delete(gensrc); 281 delete(gensrc2); 282 delete(bin); 283 previous_bin_state = collectState(bin); 284 285 populate(gensrc,"alfa/A.java", 286 "package alfa; import beta.B; import gamma.C; public class A { B b; C c; }", 287 "beta/B.java", 288 "package beta; public class B { broken", 289 "gamma/C.java", 290 "package gamma; public class C { }"); 291 292 populate(gensrc2, 293 "beta/B.java", 294 "package beta; public class B { }"); 295 296 compile("-x", "beta", "gensrc", "gensrc2", "-d", "bin", "-h", "headers", "-j", "1", 297 "--server:portfile=testserver,background=false"); 298 Map<String,Long> new_bin_state = collectState(bin); 299 verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state, 300 "bin/alfa/A.class", 301 "bin/beta/B.class", 302 "bin/gamma/C.class", 303 "bin/javac_state"); 304 305 System.out.println("----- Compile with exluded beta went well!"); 306 delete(bin); 307 compileExpectFailure("gensrc", "gensrc2", "-d", "bin", "-h", "headers", "-j", "1", 308 "--server:portfile=testserver,background=false"); 309 310 System.out.println("----- Compile without exluded beta failed, as expected! Good!"); 311 delete(bin); 312 } 313 314 void compileWithInvisibleSources() throws Exception { 315 System.out.println("\nNow verify that we can make sources invisible to linking (sourcepath)."); 316 System.out.println("Compile gensrc and link against gensrc2 and gensrc3, however"); 317 System.out.println("gensrc2 contains broken code in beta.B, thus we must exclude that package"); 318 System.out.println("fortunately gensrc3 contains a proper beta.B."); 319 System.out.println("------------------------------------------------------------------------"); 320 321 // Start with a fresh gensrcs and bin. 322 delete(gensrc); 323 delete(gensrc2); 324 delete(gensrc3); 325 delete(bin); 326 previous_bin_state = collectState(bin); 327 328 populate(gensrc,"alfa/A.java", 329 "package alfa; import beta.B; import gamma.C; public class A { B b; C c; }"); 330 populate(gensrc2,"beta/B.java", 331 "package beta; public class B { broken", 332 "gamma/C.java", 333 "package gamma; public class C { }"); 334 populate(gensrc3, "beta/B.java", 335 "package beta; public class B { }"); 336 337 compile("gensrc", "-x", "beta", "-sourcepath", "gensrc2", 338 "-sourcepath", "gensrc3", "-d", "bin", "-h", "headers", "-j", "1", 339 "--server:portfile=testserver,background=false"); 340 341 System.out.println("The first compile went well!"); 342 Map<String,Long> new_bin_state = collectState(bin); 343 verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state, 344 "bin/alfa/A.class", 345 "bin/javac_state"); 346 347 System.out.println("----- Compile with exluded beta went well!"); 348 delete(bin); 349 compileExpectFailure("gensrc", "-sourcepath", "gensrc2", "-sourcepath", "gensrc3", 350 "-d", "bin", "-h", "headers", "-j", "1", 351 "--server:portfile=testserver,background=false"); 352 353 System.out.println("----- Compile without exluded beta failed, as expected! Good!"); 354 delete(bin); 355 } 356 357 void compileCircularSources() throws Exception { 358 System.out.println("\nNow verify that circular sources split on multiple cores can be compiled."); 359 System.out.println("---------------------------------------------------------------------------"); 360 361 // Start with a fresh gensrcs and bin. 362 delete(gensrc); 363 delete(gensrc2); 364 delete(gensrc3); 365 delete(bin); 366 previous_bin_state = collectState(bin); 367 368 populate(gensrc,"alfa/A.java", 369 "package alfa; public class A { beta.B b; }", 370 "beta/B.java", 371 "package beta; public class B { gamma.C c; }", 372 "gamma/C.java", 373 "package gamma; public class C { alfa.A a; }"); 374 375 compile("gensrc", "-d", "bin", "-h", "headers", "-j", "3", 376 "--server:portfile=testserver,background=false","--log=debug"); 377 Map<String,Long> new_bin_state = collectState(bin); 378 verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state, 379 "bin/alfa/A.class", 380 "bin/beta/B.class", 381 "bin/gamma/C.class", 382 "bin/javac_state"); 383 delete(bin); 384 } 385 386 /** 387 * Tests compiling class A that depends on class B without compiling class B 388 * @throws Exception If test fails 389 */ 390 void compileExcludingDependency() throws Exception { 391 System.out.println("\nVerify that excluding classes from compilation but not from linking works."); 392 System.out.println("---------------------------------------------------------------------------"); 393 394 delete(gensrc); 395 delete(bin); 396 previous_bin_state = collectState(bin); 397 398 populate(gensrc, 399 "alfa/A.java", 400 "package alfa; public class A { beta.B b; }", 401 "beta/B.java", 402 "package beta; public class B { }"); 403 404 compile("-x", "beta", "-src", "gensrc", "-x", "alfa", "-sourcepath", "gensrc", 405 "-d", "bin", "--server:portfile=testserver,background=false"); 406 407 Map<String,Long> new_bin_state = collectState(bin); 408 verifyThatFilesHaveBeenAdded(previous_bin_state, new_bin_state, 409 "bin/alfa/A.class", 410 "bin/javac_state"); 411 } 412 413 void removeFrom(Path dir, String... args) throws IOException { 414 for (String filename : args) { 415 Path p = dir.resolve(filename); 416 Files.delete(p); 417 } 418 } 419 420 void populate(Path src, String... args) throws IOException { 421 if (!Files.exists(src)) { 422 Files.createDirectory(src); 423 } 424 String[] a = args; 425 for (int i = 0; i<a.length; i+=2) { 426 String filename = a[i]; 427 String content = a[i+1]; 428 Path p = src.resolve(filename); 429 Files.createDirectories(p.getParent()); 430 PrintWriter out = new PrintWriter(Files.newBufferedWriter(p, 431 Charset.defaultCharset())); 432 out.println(content); 433 out.close(); 434 } 435 } 436 437 void delete(final Path root) throws IOException { 438 if (!Files.exists(root)) return; 439 Files.walkFileTree(root, new SimpleFileVisitor<Path>() { 440 @Override 441 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException 442 { 443 Files.delete(file); 444 return FileVisitResult.CONTINUE; 445 } 446 447 @Override 448 public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException 449 { 450 if (e == null) { 451 if (!dir.equals(root)) Files.delete(dir); 452 return FileVisitResult.CONTINUE; 453 } else { 454 // directory iteration failed 455 throw e; 456 } 457 } 458 }); 459 } 460 461 void compile(String... args) throws Exception { 462 int rc = main.go(args, System.out, System.err); 463 if (rc != 0) throw new Exception("Error during compile!"); 464 465 // Wait a second, to get around the (temporary) problem with 466 // second resolution in the Java file api. But do not do this 467 // on windows where the timestamps work. 468 long in_a_sec = System.currentTimeMillis()+1000; 469 while (in_a_sec > System.currentTimeMillis()) { 470 try { 471 Thread.sleep(1000); 472 } catch (InterruptedException e) { 473 } 474 } 475 } 476 477 void compileExpectFailure(String... args) throws Exception { 478 int rc = main.go(args, System.out, System.err); 479 if (rc == 0) throw new Exception("Expected error during compile! Did not fail!"); 480 } 481 482 Map<String,Long> collectState(Path dir) throws IOException 483 { 484 final Map<String,Long> files = new HashMap<>(); 485 Files.walkFileTree(dir, new SimpleFileVisitor<Path>() { 486 @Override 487 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 488 throws IOException 489 { 490 files.put(file.toString(),new Long(Files.getLastModifiedTime(file).toMillis())); 491 return FileVisitResult.CONTINUE; 492 } 493 }); 494 return files; 495 } 496 497 void verifyThatFilesHaveBeenRemoved(Map<String,Long> from, 498 Map<String,Long> to, 499 String... args) throws Exception { 500 501 Set<String> froms = from.keySet(); 502 Set<String> tos = to.keySet(); 503 504 if (froms.equals(tos)) { 505 throw new Exception("Expected new state to have fewer files than previous state!"); 506 } 507 508 for (String t : tos) { 509 if (!froms.contains(t)) { 510 throw new Exception("Expected "+t+" to exist in previous state!"); 511 } 512 } 513 514 for (String f : args) { 515 f = f.replace("/", File.separator); 516 if (!froms.contains(f)) { 517 throw new Exception("Expected "+f+" to exist in previous state!"); 518 } 519 if (tos.contains(f)) { 520 throw new Exception("Expected "+f+" to have been removed from the new state!"); 521 } 522 } 523 524 if (froms.size() - args.length != tos.size()) { 525 throw new Exception("There are more removed files than the expected list!"); 526 } 527 } 528 529 void verifyThatFilesHaveBeenAdded(Map<String,Long> from, 530 Map<String,Long> to, 531 String... args) throws Exception { 532 533 Set<String> froms = from.keySet(); 534 Set<String> tos = to.keySet(); 535 536 if (froms.equals(tos)) { 537 throw new Exception("Expected new state to have more files than previous state!"); 538 } 539 540 for (String t : froms) { 541 if (!tos.contains(t)) { 542 throw new Exception("Expected "+t+" to exist in new state!"); 543 } 544 } 545 546 for (String f : args) { 547 f = f.replace("/", File.separator); 548 if (!tos.contains(f)) { 549 throw new Exception("Expected "+f+" to have been added to new state!"); 550 } 551 if (froms.contains(f)) { 552 throw new Exception("Expected "+f+" to not exist in previous state!"); 553 } 554 } 555 556 if (froms.size() + args.length != tos.size()) { 557 throw new Exception("There are more added files than the expected list!"); 558 } 559 } 560 561 void verifyNewerFiles(Map<String,Long> from, 562 Map<String,Long> to, 563 String... args) throws Exception { 564 if (!from.keySet().equals(to.keySet())) { 565 throw new Exception("Expected the set of files to be identical!"); 566 } 567 Set<String> files = new HashSet<String>(); 568 for (String s : args) { 569 files.add(s.replace("/", File.separator)); 570 } 571 for (String fn : from.keySet()) { 572 long f = from.get(fn); 573 long t = to.get(fn); 574 if (files.contains(fn)) { 575 if (t <= f) { 576 throw new Exception("Expected "+fn+" to have a more recent timestamp!"); 577 } 578 } else { 579 if (t != f) { 580 throw new Exception("Expected "+fn+" to have the same timestamp!"); 581 } 582 } 583 } 584 } 585 586 String print(Map<String,Long> m) { 587 StringBuilder b = new StringBuilder(); 588 Set<String> keys = m.keySet(); 589 for (String k : keys) { 590 b.append(k+" "+m.get(k)+"\n"); 591 } 592 return b.toString(); 593 } 594 595 void verifyEqual(Map<String,Long> from, Map<String,Long> to) throws Exception { 596 if (!from.equals(to)) { 597 System.out.println("FROM---"+print(from)); 598 System.out.println("TO-----"+print(to)); 599 throw new Exception("The dir should not differ! But it does!"); 600 } 601 } 602 }