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 }