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