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 }