1 /* 2 * Copyright (c) 2016, 2017, 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 /* 25 * @test 26 * @library /test/lib 27 * @modules java.base/jdk.internal.misc 28 * jdk.compiler 29 * jdk.jartool 30 * @build jdk.test.lib.util.FileUtils 31 * jdk.test.lib.Utils 32 * jdk.test.lib.Asserts 33 * jdk.test.lib.JDKToolFinder 34 * jdk.test.lib.JDKToolLauncher 35 * jdk.test.lib.Platform 36 * jdk.test.lib.process.* 37 * MRTestBase 38 * @run testng Basic 39 */ 40 41 import static org.testng.Assert.*; 42 43 import jdk.test.lib.util.FileUtils; 44 import org.testng.annotations.*; 45 46 import java.io.File; 47 import java.nio.file.*; 48 import java.util.*; 49 import java.util.jar.JarFile; 50 import java.util.zip.ZipFile; 51 52 public class Basic extends MRTestBase { 53 54 @Test 55 // create a regular, non-multi-release jar 56 public void test00() throws Throwable { 57 String jarfile = "test.jar"; 58 59 compile("test01"); //use same data as test01 60 61 Path classes = Paths.get("classes"); 62 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".") 63 .shouldHaveExitValue(SUCCESS); 64 65 checkMultiRelease(jarfile, false); 66 67 Map<String, String[]> names = Map.of( 68 "version/Main.class", 69 new String[]{"base", "version", "Main.class"}, 70 71 "version/Version.class", 72 new String[]{"base", "version", "Version.class"} 73 ); 74 75 compare(jarfile, names); 76 77 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 78 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 79 } 80 81 @Test 82 // create a multi-release jar 83 public void test01() throws Throwable { 84 String jarfile = "test.jar"; 85 86 compile("test01"); 87 88 Path classes = Paths.get("classes"); 89 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 90 "--release", "9", "-C", classes.resolve("v9").toString(), ".", 91 "--release", "10", "-C", classes.resolve("v10").toString(), ".") 92 .shouldHaveExitValue(SUCCESS); 93 94 checkMultiRelease(jarfile, true); 95 96 Map<String, String[]> names = Map.of( 97 "version/Main.class", 98 new String[]{"base", "version", "Main.class"}, 99 100 "version/Version.class", 101 new String[]{"base", "version", "Version.class"}, 102 103 "META-INF/versions/9/version/Version.class", 104 new String[]{"v9", "version", "Version.class"}, 105 106 "META-INF/versions/10/version/Version.class", 107 new String[]{"v10", "version", "Version.class"} 108 ); 109 110 compare(jarfile, names); 111 112 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 113 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 114 } 115 116 @Test 117 public void versionFormat() throws Throwable { 118 String jarfile = "test.jar"; 119 120 compile("test01"); 121 122 Path classes = Paths.get("classes"); 123 124 // valid 125 for (String release : List.of("10000", "09", "00010", "10")) { 126 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 127 "--release", release, "-C", classes.resolve("v10").toString(), ".") 128 .shouldHaveExitValue(SUCCESS) 129 .shouldBeEmpty(); 130 } 131 // invalid 132 for (String release : List.of("9.0", "8", "v9", 133 "9v", "0", "-10")) { 134 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 135 "--release", release, "-C", classes.resolve("v10").toString(), ".") 136 .shouldNotHaveExitValue(SUCCESS) 137 .shouldContain("release " + release + " not valid"); 138 } 139 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 140 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 141 } 142 143 @Test 144 // update a regular jar to a multi-release jar 145 public void test02() throws Throwable { 146 String jarfile = "test.jar"; 147 148 compile("test01"); //use same data as test01 149 150 Path classes = Paths.get("classes"); 151 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".") 152 .shouldHaveExitValue(SUCCESS); 153 154 checkMultiRelease(jarfile, false); 155 156 jar("uf", jarfile, 157 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 158 .shouldHaveExitValue(SUCCESS); 159 160 checkMultiRelease(jarfile, true); 161 162 Map<String, String[]> names = Map.of( 163 "version/Main.class", 164 new String[]{"base", "version", "Main.class"}, 165 166 "version/Version.class", 167 new String[]{"base", "version", "Version.class"}, 168 169 "META-INF/versions/9/version/Version.class", 170 new String[]{"v9", "version", "Version.class"} 171 ); 172 173 compare(jarfile, names); 174 175 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 176 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 177 } 178 179 @Test 180 // replace a base entry and a versioned entry 181 public void test03() throws Throwable { 182 String jarfile = "test.jar"; 183 184 compile("test01"); //use same data as test01 185 186 Path classes = Paths.get("classes"); 187 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 188 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 189 .shouldHaveExitValue(SUCCESS); 190 191 checkMultiRelease(jarfile, true); 192 193 Map<String, String[]> names = Map.of( 194 "version/Main.class", 195 new String[]{"base", "version", "Main.class"}, 196 197 "version/Version.class", 198 new String[]{"base", "version", "Version.class"}, 199 200 "META-INF/versions/9/version/Version.class", 201 new String[]{"v9", "version", "Version.class"} 202 ); 203 204 compare(jarfile, names); 205 206 // write the v9 version/Version.class entry in base and the v10 207 // version/Version.class entry in versions/9 section 208 jar("uf", jarfile, "-C", classes.resolve("v9").toString(), "version", 209 "--release", "9", "-C", classes.resolve("v10").toString(), ".") 210 .shouldHaveExitValue(SUCCESS); 211 212 checkMultiRelease(jarfile, true); 213 214 names = Map.of( 215 "version/Main.class", 216 new String[]{"base", "version", "Main.class"}, 217 218 "version/Version.class", 219 new String[]{"v9", "version", "Version.class"}, 220 221 "META-INF/versions/9/version/Version.class", 222 new String[]{"v10", "version", "Version.class"} 223 ); 224 225 compare(jarfile, names); 226 227 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 228 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 229 } 230 231 /* 232 * The following tests exercise the jar validator 233 */ 234 235 @Test 236 // META-INF/versions/9 class has different api than base class 237 public void test04() throws Throwable { 238 String jarfile = "test.jar"; 239 240 compile("test01"); //use same data as test01 241 242 Path classes = Paths.get("classes"); 243 244 // replace the v9 class 245 Path source = Paths.get(src, "data", "test04", "v9", "version"); 246 javac(classes.resolve("v9"), source.resolve("Version.java")); 247 248 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 249 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 250 .shouldNotHaveExitValue(SUCCESS) 251 .shouldContain("different api from earlier"); 252 253 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 254 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 255 } 256 257 @Test 258 // META-INF/versions/9 contains an extra public class 259 public void test05() throws Throwable { 260 String jarfile = "test.jar"; 261 262 compile("test01"); //use same data as test01 263 264 Path classes = Paths.get("classes"); 265 266 // add the new v9 class 267 Path source = Paths.get(src, "data", "test05", "v9", "version"); 268 javac(classes.resolve("v9"), source.resolve("Extra.java")); 269 270 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 271 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 272 .shouldNotHaveExitValue(SUCCESS) 273 .shouldContain("contains a new public class"); 274 275 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 276 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 277 } 278 279 @Test 280 // META-INF/versions/9 contains an extra package private class -- this is okay 281 public void test06() throws Throwable { 282 String jarfile = "test.jar"; 283 284 compile("test01"); //use same data as test01 285 286 Path classes = Paths.get("classes"); 287 288 // add the new v9 class 289 Path source = Paths.get(src, "data", "test06", "v9", "version"); 290 javac(classes.resolve("v9"), source.resolve("Extra.java")); 291 292 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 293 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 294 .shouldHaveExitValue(SUCCESS); 295 296 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 297 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 298 } 299 300 @Test 301 // META-INF/versions/9 contains an identical class to base entry class 302 // this is okay but produces warning 303 public void test07() throws Throwable { 304 String jarfile = "test.jar"; 305 306 compile("test01"); //use same data as test01 307 308 Path classes = Paths.get("classes"); 309 310 // add the new v9 class 311 Path source = Paths.get(src, "data", "test01", "base", "version"); 312 javac(classes.resolve("v9"), source.resolve("Version.java")); 313 314 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 315 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 316 .shouldHaveExitValue(SUCCESS) 317 .shouldContain("contains a class that") 318 .shouldContain("is identical"); 319 320 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 321 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 322 } 323 324 @Test 325 // META-INF/versions/9 contains an identical class to previous version entry class 326 // this is okay but produces warning 327 public void identicalClassToPreviousVersion() throws Throwable { 328 String jarfile = "test.jar"; 329 330 compile("test01"); //use same data as test01 331 332 Path classes = Paths.get("classes"); 333 334 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 335 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 336 .shouldHaveExitValue(SUCCESS) 337 .shouldBeEmpty(); 338 jar("uf", jarfile, 339 "--release", "10", "-C", classes.resolve("v9").toString(), ".") 340 .shouldHaveExitValue(SUCCESS) 341 .shouldContain("contains a class that") 342 .shouldContain("is identical"); 343 344 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 345 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 346 } 347 348 @Test 349 // resources with same name in different versions 350 // this is okay but produces warning 351 public void test08() throws Throwable { 352 String jarfile = "test.jar"; 353 354 compile("test01"); //use same data as test01 355 356 Path classes = Paths.get("classes"); 357 358 // add a resource to the base 359 Path source = Paths.get(src, "data", "test01", "base", "version"); 360 Files.copy(source.resolve("Version.java"), classes.resolve("base") 361 .resolve("version").resolve("Version.java")); 362 363 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 364 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 365 .shouldHaveExitValue(SUCCESS) 366 .shouldBeEmpty(); 367 368 // now add a different resource with same name to META-INF/version/9 369 Files.copy(source.resolve("Main.java"), classes.resolve("v9") 370 .resolve("version").resolve("Version.java")); 371 372 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 373 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 374 .shouldHaveExitValue(SUCCESS) 375 .shouldContain("multiple resources with same name"); 376 377 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 378 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 379 } 380 381 @Test 382 // a class with an internal name different from the external name 383 public void test09() throws Throwable { 384 String jarfile = "test.jar"; 385 386 compile("test01"); //use same data as test01 387 388 Path classes = Paths.get("classes"); 389 390 Path base = classes.resolve("base").resolve("version"); 391 392 Files.copy(base.resolve("Main.class"), base.resolve("Foo.class")); 393 394 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 395 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 396 .shouldNotHaveExitValue(SUCCESS) 397 .shouldContain("names do not match"); 398 399 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 400 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 401 } 402 403 @Test 404 // assure that basic nested classes are acceptable 405 public void test10() throws Throwable { 406 String jarfile = "test.jar"; 407 408 compile("test01"); //use same data as test01 409 410 Path classes = Paths.get("classes"); 411 412 // add a base class with a nested class 413 Path source = Paths.get(src, "data", "test10", "base", "version"); 414 javac(classes.resolve("base"), source.resolve("Nested.java")); 415 416 // add a versioned class with a nested class 417 source = Paths.get(src, "data", "test10", "v9", "version"); 418 javac(classes.resolve("v9"), source.resolve("Nested.java")); 419 420 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 421 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 422 .shouldHaveExitValue(SUCCESS); 423 424 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 425 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 426 } 427 428 @Test 429 // a base entry contains a nested class that doesn't have a matching top level class 430 public void test11() throws Throwable { 431 String jarfile = "test.jar"; 432 433 compile("test01"); //use same data as test01 434 435 Path classes = Paths.get("classes"); 436 437 // add a base class with a nested class 438 Path source = Paths.get(src, "data", "test10", "base", "version"); 439 javac(classes.resolve("base"), source.resolve("Nested.java")); 440 441 // remove the top level class, thus isolating the nested class 442 Files.delete(classes.resolve("base").resolve("version").resolve("Nested.class")); 443 444 // add a versioned class with a nested class 445 source = Paths.get(src, "data", "test10", "v9", "version"); 446 javac(classes.resolve("v9"), source.resolve("Nested.java")); 447 448 List<String> output = jar("cf", jarfile, 449 "-C", classes.resolve("base").toString(), ".", 450 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 451 .shouldNotHaveExitValue(SUCCESS) 452 .asLines(); 453 454 assertTrue(output.size() == 4); 455 assertTrue(output.get(0).contains("an isolated nested class"), 456 output.get(0)); 457 assertTrue(output.get(1).contains("contains a new public class"), 458 output.get(1)); 459 assertTrue(output.get(2).contains("an isolated nested class"), 460 output.get(2)); 461 assertTrue(output.get(3).contains("invalid multi-release jar file"), 462 output.get(3)); 463 464 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 465 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 466 } 467 468 @Test 469 // a versioned entry contains a nested class that doesn't have a matching top level class 470 public void test12() throws Throwable { 471 String jarfile = "test.jar"; 472 473 compile("test01"); //use same data as test01 474 475 Path classes = Paths.get("classes"); 476 477 // add a base class with a nested class 478 Path source = Paths.get(src, "data", "test10", "base", "version"); 479 javac(classes.resolve("base"), source.resolve("Nested.java")); 480 481 // add a versioned class with a nested class 482 source = Paths.get(src, "data", "test10", "v9", "version"); 483 javac(classes.resolve("v9"), source.resolve("Nested.java")); 484 485 // remove the top level class, thus isolating the nested class 486 Files.delete(classes.resolve("v9").resolve("version").resolve("Nested.class")); 487 488 jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".", 489 "--release", "9", "-C", classes.resolve("v9").toString(), ".") 490 .shouldNotHaveExitValue(SUCCESS) 491 .shouldContain("an isolated nested class"); 492 493 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 494 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 495 } 496 497 @Test 498 public void testCustomManifest() throws Throwable { 499 String jarfile = "test.jar"; 500 501 compile("test01"); 502 503 Path classes = Paths.get("classes"); 504 Path manifest = Paths.get("Manifest.txt"); 505 506 // create 507 Files.write(manifest, "Class-Path: MyUtils.jar\n".getBytes()); 508 509 jar("cfm", jarfile, manifest.toString(), 510 "-C", classes.resolve("base").toString(), ".", 511 "--release", "10", "-C", classes.resolve("v10").toString(), ".") 512 .shouldHaveExitValue(SUCCESS) 513 .shouldBeEmpty(); 514 515 try (JarFile jf = new JarFile(new File(jarfile), true, 516 ZipFile.OPEN_READ, JarFile.runtimeVersion())) { 517 assertTrue(jf.isMultiRelease(), "Not multi-release jar"); 518 assertEquals(jf.getManifest() 519 .getMainAttributes() 520 .getValue("Class-Path"), 521 "MyUtils.jar"); 522 } 523 524 // update 525 Files.write(manifest, "Multi-release: false\n".getBytes()); 526 527 jar("ufm", jarfile, manifest.toString(), 528 "-C", classes.resolve("base").toString(), ".", 529 "--release", "9", "-C", classes.resolve("v10").toString(), ".") 530 .shouldHaveExitValue(SUCCESS) 531 .shouldContain("WARNING: Duplicate name in Manifest: Multi-release."); 532 533 try (JarFile jf = new JarFile(new File(jarfile), true, 534 ZipFile.OPEN_READ, JarFile.runtimeVersion())) { 535 assertTrue(jf.isMultiRelease(), "Not multi-release jar"); 536 assertEquals(jf.getManifest() 537 .getMainAttributes() 538 .getValue("Class-Path"), 539 "MyUtils.jar"); 540 } 541 542 FileUtils.deleteFileIfExistsWithRetry(Paths.get(jarfile)); 543 FileUtils.deleteFileTreeWithRetry(Paths.get(usr, "classes")); 544 } 545 }