1 /*
   2  * Copyright (c) 2016, 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  * @build jdk.test.lib.JDKToolFinder jdk.test.lib.Platform
  29  * @run testng Basic
  30  */
  31 
  32 import static org.testng.Assert.*;
  33 
  34 import org.testng.annotations.*;
  35 
  36 import java.io.*;
  37 import java.nio.file.*;
  38 import java.nio.file.attribute.*;
  39 import java.util.*;
  40 import java.util.function.Consumer;
  41 import java.util.jar.*;
  42 import java.util.stream.Stream;
  43 import java.util.zip.*;
  44 
  45 import jdk.test.lib.JDKToolFinder;
  46 
  47 import static java.lang.String.format;
  48 import static java.lang.System.out;
  49 
  50 public class Basic {
  51     private final String src = System.getProperty("test.src", ".");
  52     private final String usr = System.getProperty("user.dir", ".");
  53 
  54     @Test
  55     // create a regular, non-multi-release jar
  56     public void test00() throws IOException {
  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                 .assertSuccess();
  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         delete(jarfile);
  78         deleteDir(Paths.get(usr, "classes"));
  79     }
  80 
  81     @Test
  82     // create a multi-release jar
  83     public void test01() throws IOException {
  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                 .assertSuccess();
  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         delete(jarfile);
 113         deleteDir(Paths.get(usr, "classes"));
 114     }
 115 
 116     @Test
 117     // update a regular jar to a multi-release jar
 118     public void test02() throws IOException {
 119         String jarfile = "test.jar";
 120 
 121         compile("test01");  //use same data as test01
 122 
 123         Path classes = Paths.get("classes");
 124         jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".")
 125                 .assertSuccess();
 126 
 127         checkMultiRelease(jarfile, false);
 128 
 129         jar("uf", jarfile, "--release", "9", "-C", classes.resolve("v9").toString(), ".")
 130                 .assertSuccess();
 131 
 132         checkMultiRelease(jarfile, true);
 133 
 134         Map<String,String[]> names = Map.of(
 135                 "version/Main.class",
 136                 new String[] {"base", "version", "Main.class"},
 137 
 138                 "version/Version.class",
 139                 new String[] {"base", "version", "Version.class"},
 140 
 141                 "META-INF/versions/9/version/Version.class",
 142                 new String[] {"v9", "version", "Version.class"}
 143         );
 144 
 145         compare(jarfile, names);
 146 
 147         delete(jarfile);
 148         deleteDir(Paths.get(usr, "classes"));
 149     }
 150 
 151     @Test
 152     // replace a base entry and a versioned entry
 153     public void test03() throws IOException {
 154         String jarfile = "test.jar";
 155 
 156         compile("test01");  //use same data as test01
 157 
 158         Path classes = Paths.get("classes");
 159         jar("cf", jarfile, "-C", classes.resolve("base").toString(), ".",
 160                 "--release", "9", "-C", classes.resolve("v9").toString(), ".")
 161                 .assertSuccess();
 162 
 163         checkMultiRelease(jarfile, true);
 164 
 165         Map<String,String[]> names = Map.of(
 166                 "version/Main.class",
 167                 new String[] {"base", "version", "Main.class"},
 168 
 169                 "version/Version.class",
 170                 new String[] {"base", "version", "Version.class"},
 171 
 172                 "META-INF/versions/9/version/Version.class",
 173                 new String[] {"v9", "version", "Version.class"}
 174         );
 175 
 176         compare(jarfile, names);
 177 
 178         // write the v9 version/Version.class entry in base and the v10
 179         // version/Version.class entry in versions/9 section
 180         jar("uf", jarfile, "-C", classes.resolve("v9").toString(), "version",
 181                 "--release", "9", "-C", classes.resolve("v10").toString(), ".")
 182                 .assertSuccess();
 183 
 184         checkMultiRelease(jarfile, true);
 185 
 186         names = Map.of(
 187                 "version/Main.class",
 188                 new String[] {"base", "version", "Main.class"},
 189 
 190                 "version/Version.class",
 191                 new String[] {"v9", "version", "Version.class"},
 192 
 193                 "META-INF/versions/9/version/Version.class",
 194                 new String[] {"v10", "version", "Version.class"}
 195         );
 196 
 197         delete(jarfile);
 198         deleteDir(Paths.get(usr, "classes"));
 199     }
 200 
 201     /*
 202      *  Test Infrastructure
 203      */
 204     private void compile(String test) throws IOException {
 205         Path classes = Paths.get(usr, "classes", "base");
 206         Files.createDirectories(classes);
 207         Path source = Paths.get(src, "data", test, "base", "version");
 208         javac(classes, source.resolve("Main.java"), source.resolve("Version.java"));
 209 
 210         classes = Paths.get(usr, "classes", "v9");
 211         Files.createDirectories(classes);
 212         source = Paths.get(src, "data", test, "v9", "version");
 213         javac(classes, source.resolve("Version.java"));
 214 
 215         classes = Paths.get(usr, "classes", "v10");
 216         Files.createDirectories(classes);
 217         source = Paths.get(src, "data", test, "v10", "version");
 218         javac(classes, source.resolve("Version.java"));
 219     }
 220 
 221     private void checkMultiRelease(String jarFile, boolean expected) throws IOException {
 222         try (JarFile jf = new JarFile(new File(jarFile), true, ZipFile.OPEN_READ,
 223                 JarFile.runtimeVersion())) {
 224             assertEquals(jf.isMultiRelease(), expected);
 225         }
 226     }
 227 
 228     // compares the bytes found in the jar entries with the bytes found in the
 229     // corresponding data files used to create the entries
 230     private void compare(String jarfile, Map<String,String[]> names) throws IOException {
 231         try (JarFile jf = new JarFile(jarfile)) {
 232             for (String name : names.keySet()) {
 233                 Path path = Paths.get("classes", names.get(name));
 234                 byte[] b1 = Files.readAllBytes(path);
 235                 byte[] b2;
 236                 JarEntry je = jf.getJarEntry(name);
 237                 try (InputStream is = jf.getInputStream(je)) {
 238                     b2 = is.readAllBytes();
 239                 }
 240                 assertEquals(b1,b2);
 241             }
 242         }
 243     }
 244 
 245     private void delete(String name) throws IOException {
 246         Files.delete(Paths.get(usr, name));
 247     }
 248 
 249     private void deleteDir(Path dir) throws IOException {
 250         Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
 251             @Override
 252             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 253                 Files.delete(file);
 254                 return FileVisitResult.CONTINUE;
 255             }
 256 
 257             @Override
 258             public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
 259                 Files.delete(dir);
 260                 return FileVisitResult.CONTINUE;
 261             }
 262         });
 263     }
 264 
 265     /*
 266      * The following methods were taken from modular jar and other jar tests
 267      */
 268 
 269     void javac(Path dest, Path... sourceFiles) throws IOException {
 270         String javac = JDKToolFinder.getJDKTool("javac");
 271 
 272         List<String> commands = new ArrayList<>();
 273         commands.add(javac);
 274         commands.add("-d");
 275         commands.add(dest.toString());
 276         Stream.of(sourceFiles).map(Object::toString).forEach(x -> commands.add(x));
 277 
 278         quickFail(run(new ProcessBuilder(commands)));
 279     }
 280 
 281     Result jarWithStdin(File stdinSource, String... args) {
 282         String jar = JDKToolFinder.getJDKTool("jar");
 283         List<String> commands = new ArrayList<>();
 284         commands.add(jar);
 285         Stream.of(args).forEach(x -> commands.add(x));
 286         ProcessBuilder p = new ProcessBuilder(commands);
 287         if (stdinSource != null)
 288             p.redirectInput(stdinSource);
 289         return run(p);
 290     }
 291 
 292     Result jar(String... args) {
 293         return jarWithStdin(null, args);
 294     }
 295 
 296     void quickFail(Result r) {
 297         if (r.ec != 0)
 298             throw new RuntimeException(r.output);
 299     }
 300 
 301     Result run(ProcessBuilder pb) {
 302         Process p;
 303         out.printf("Running: %s%n", pb.command());
 304         try {
 305             p = pb.start();
 306         } catch (IOException e) {
 307             throw new RuntimeException(
 308                     format("Couldn't start process '%s'", pb.command()), e);
 309         }
 310 
 311         String output;
 312         try {
 313             output = toString(p.getInputStream(), p.getErrorStream());
 314         } catch (IOException e) {
 315             throw new RuntimeException(
 316                     format("Couldn't read process output '%s'", pb.command()), e);
 317         }
 318 
 319         try {
 320             p.waitFor();
 321         } catch (InterruptedException e) {
 322             throw new RuntimeException(
 323                     format("Process hasn't finished '%s'", pb.command()), e);
 324         }
 325         return new Result(p.exitValue(), output);
 326     }
 327 
 328     String toString(InputStream in1, InputStream in2) throws IOException {
 329         try (ByteArrayOutputStream dst = new ByteArrayOutputStream();
 330              InputStream concatenated = new SequenceInputStream(in1, in2)) {
 331             concatenated.transferTo(dst);
 332             return new String(dst.toByteArray(), "UTF-8");
 333         }
 334     }
 335 
 336     static class Result {
 337         final int ec;
 338         final String output;
 339 
 340         private Result(int ec, String output) {
 341             this.ec = ec;
 342             this.output = output;
 343         }
 344         Result assertSuccess() {
 345             assertTrue(ec == 0, format("ec: %d, output: %s", ec, output));
 346             return this;
 347         }
 348         Result assertFailure() {
 349             assertTrue(ec != 0, format("ec: %d, output: %s", ec, output));
 350             return this;
 351         }
 352         Result resultChecker(Consumer<Result> r) { r.accept(this); return this; }
 353     }
 354 }