jdk/test/tools/jmod/hashes/HashesTest.java
changeset 43109 fe275140c3f1
parent 42703 20c39ea4a507
child 43712 5dfd0950317c
equal deleted inserted replaced
43108:9849ccac1f10 43109:fe275140c3f1
     1 /**
     1 /**
     2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
     2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     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
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.
     7  * published by the Free Software Foundation.
    21  * questions.
    21  * questions.
    22  */
    22  */
    23 
    23 
    24 /*
    24 /*
    25  * @test
    25  * @test
       
    26  * @bug 8160286
    26  * @summary Test the recording and checking of module hashes
    27  * @summary Test the recording and checking of module hashes
    27  * @author Andrei Eremeev
       
    28  * @library /lib/testlibrary
    28  * @library /lib/testlibrary
    29  * @modules java.base/jdk.internal.misc
    29  * @modules java.base/jdk.internal.misc
    30  *          java.base/jdk.internal.module
    30  *          java.base/jdk.internal.module
       
    31  *          jdk.compiler
       
    32  *          jdk.jartool
    31  *          jdk.jlink
    33  *          jdk.jlink
    32  *          jdk.compiler
    34  * @build CompilerUtils ModuleInfoMaker
    33  * @build CompilerUtils
       
    34  * @run testng HashesTest
    35  * @run testng HashesTest
    35  */
    36  */
    36 
    37 
       
    38 import java.io.File;
    37 import java.io.IOException;
    39 import java.io.IOException;
    38 import java.io.InputStream;
    40 import java.io.InputStream;
       
    41 import java.io.UncheckedIOException;
    39 import java.lang.module.ModuleDescriptor;
    42 import java.lang.module.ModuleDescriptor;
    40 import java.lang.module.ModuleFinder;
    43 import java.lang.module.ModuleFinder;
    41 import java.lang.module.ModuleReader;
    44 import java.lang.module.ModuleReader;
    42 import java.lang.module.ModuleReference;
    45 import java.lang.module.ModuleReference;
    43 import java.nio.file.FileVisitResult;
    46 import java.nio.file.FileVisitResult;
    51 import java.util.Collections;
    54 import java.util.Collections;
    52 import java.util.List;
    55 import java.util.List;
    53 import java.util.Set;
    56 import java.util.Set;
    54 import java.util.spi.ToolProvider;
    57 import java.util.spi.ToolProvider;
    55 import java.util.stream.Collectors;
    58 import java.util.stream.Collectors;
       
    59 import java.util.stream.Stream;
    56 
    60 
    57 import jdk.internal.module.ModuleInfo;
    61 import jdk.internal.module.ModuleInfo;
    58 import jdk.internal.module.ModuleHashes;
    62 import jdk.internal.module.ModuleHashes;
    59 import jdk.internal.module.ModulePath;
    63 import jdk.internal.module.ModulePath;
    60 
    64 
    61 import org.testng.annotations.BeforeTest;
       
    62 import org.testng.annotations.Test;
    65 import org.testng.annotations.Test;
    63 
    66 
    64 import static org.testng.Assert.*;
    67 import static org.testng.Assert.*;
       
    68 import static java.lang.module.ModuleDescriptor.Requires.Modifier.*;
    65 
    69 
    66 public class HashesTest {
    70 public class HashesTest {
    67     static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod")
    71     static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod")
    68         .orElseThrow(() ->
    72         .orElseThrow(() ->
    69             new RuntimeException("jmod tool not found")
    73             new RuntimeException("jmod tool not found")
    70         );
    74         );
    71 
    75     static final ToolProvider JAR_TOOL = ToolProvider.findFirst("jar")
    72     private final Path testSrc = Paths.get(System.getProperty("test.src"));
    76         .orElseThrow(() ->
    73     private final Path modSrc = testSrc.resolve("src");
    77             new RuntimeException("jar tool not found")
    74     private final Path mods = Paths.get("mods");
    78         );
    75     private final Path jmods = Paths.get("jmods");
    79 
    76     private final String[] modules = new String[] { "m1", "m2", "m3"};
    80     private final Path mods;
    77 
    81     private final Path srcDir;
    78     @BeforeTest
    82     private final Path lib;
    79     private void setup() throws Exception {
    83     private final ModuleInfoMaker builder;
    80         if (Files.exists(jmods)) {
    84     HashesTest(Path dest) throws IOException {
    81             deleteDirectory(jmods);
    85         if (Files.exists(dest)) {
    82         }
    86             deleteDirectory(dest);
    83         Files.createDirectories(jmods);
    87         }
    84 
    88         this.mods = dest.resolve("mods");
    85         // build m2, m3 required by m1
    89         this.srcDir = dest.resolve("src");
    86         compileModule("m2", modSrc);
    90         this.lib = dest.resolve("lib");
    87         jmod("m2");
    91         this.builder = new ModuleInfoMaker(srcDir);
    88 
    92 
    89         compileModule("m3", modSrc);
    93         Files.createDirectories(lib);
    90         jmod("m3");
    94         Files.createDirectories(mods);
    91 
    95     }
    92         // build m1
    96 
    93         compileModule("m1", modSrc);
    97     @Test
       
    98     public static void test() throws IOException {
       
    99         Path dest = Paths.get("test");
       
   100         HashesTest ht = new HashesTest(dest);
       
   101 
       
   102         // create modules for test cases
       
   103         ht.makeModule("m2");
       
   104         ht.makeModule("m3");
       
   105         ht.makeModule("m1", "m2", "m3");
       
   106 
       
   107         ht.makeModule("org.bar", TRANSITIVE, "m1");
       
   108         ht.makeModule("org.foo", TRANSITIVE, "org.bar");
       
   109 
       
   110         // create JMOD for m1, m2, m3
       
   111         ht.makeJmod("m2");
       
   112         ht.makeJmod("m3");
       
   113 
    94         // no hash is recorded since m1 has outgoing edges
   114         // no hash is recorded since m1 has outgoing edges
    95         jmod("m1", "--module-path", jmods.toString(), "--hash-modules", ".*");
   115         ht.jmodHashModules("m1", ".*");
    96 
   116 
    97         // compile org.bar and org.foo
   117         // no hash is recorded in m1, m2, m3
    98         compileModule("org.bar", modSrc);
   118         assertTrue(ht.hashes("m1") == null);
    99         compileModule("org.foo", modSrc);
   119         assertTrue(ht.hashes("m2") == null);
   100     }
   120         assertTrue(ht.hashes("m3") == null);
   101 
       
   102     @Test
       
   103     public void test() throws Exception {
       
   104         for (String mn : modules) {
       
   105             assertTrue(hashes(mn) == null);
       
   106         }
       
   107 
   121 
   108         // hash m1 in m2
   122         // hash m1 in m2
   109         jmod("m2", "--module-path", jmods.toString(), "--hash-modules", "m1");
   123         ht.jmodHashModules("m2",  "m1");
   110         checkHashes(hashes("m2"), "m1");
   124         ht.checkHashes("m2", "m1");
   111 
   125 
   112         // hash m1 in m2
   126         // hash m1 in m2
   113         jmod("m2", "--module-path", jmods.toString(), "--hash-modules", ".*");
   127         ht.jmodHashModules("m2",  ".*");
   114         checkHashes(hashes("m2"), "m1");
   128         ht.checkHashes("m2", "m1");
   115 
   129 
   116         // create m2.jmod with no hash
   130         // create m2.jmod with no hash
   117         jmod("m2");
   131         ht.makeJmod("m2");
   118         // run jmod hash command to hash m1 in m2 and m3
   132         // run jmod hash command to hash m1 in m2 and m3
   119         runJmod(Arrays.asList("hash", "--module-path", jmods.toString(),
   133         runJmod(List.of("hash", "--module-path", ht.lib.toString(),
   120                 "--hash-modules", ".*"));
   134                         "--hash-modules", ".*"));
   121         checkHashes(hashes("m2"), "m1");
   135         ht.checkHashes("m2", "m1");
   122         checkHashes(hashes("m3"), "m1");
   136         ht.checkHashes("m3", "m1");
   123 
   137 
   124         jmod("org.bar");
   138         // check transitive requires
   125         jmod("org.foo");
   139         ht.makeJmod("org.bar");
   126 
   140         ht.makeJmod("org.foo");
   127         jmod("org.bar", "--module-path", jmods.toString(), "--hash-modules", "org.*");
   141 
   128         checkHashes(hashes("org.bar"), "org.foo");
   142         ht.jmodHashModules("org.bar", "org.*");
   129 
   143         ht.checkHashes("org.bar", "org.foo");
   130         jmod("m3", "--module-path", jmods.toString(), "--hash-modules", ".*");
   144 
   131         checkHashes(hashes("m3"), "org.foo", "org.bar", "m1");
   145         ht.jmodHashModules( "m3", ".*");
   132     }
   146         ht.checkHashes("m3", "org.foo", "org.bar", "m1");
   133 
   147     }
   134     private void checkHashes(ModuleHashes hashes, String... hashModules) {
   148 
   135         assertTrue(hashes.names().equals(Set.of(hashModules)));
   149     @Test
   136     }
   150     public static void multiBaseModules() throws IOException {
   137 
   151         Path dest = Paths.get("test2");
   138     private ModuleHashes hashes(String name) throws Exception {
   152         HashesTest ht = new HashesTest(dest);
       
   153 
       
   154         /*
       
   155          * y2 -----------> y1
       
   156          *    |______
       
   157          *    |      |
       
   158          *    V      V
       
   159          *    z3 -> z2
       
   160          *    |      |
       
   161          *    |      V
       
   162          *    |---> z1
       
   163          */
       
   164 
       
   165         ht.makeModule("z1");
       
   166         ht.makeModule("z2", "z1");
       
   167         ht.makeModule("z3", "z1", "z2");
       
   168 
       
   169         ht.makeModule("y1");
       
   170         ht.makeModule("y2", "y1", "z2", "z3");
       
   171 
       
   172         Set<String> ys = Set.of("y1", "y2");
       
   173         Set<String> zs = Set.of("z1", "z2", "z3");
       
   174 
       
   175         // create JMOD files
       
   176         Stream.concat(ys.stream(), zs.stream()).forEach(ht::makeJmod);
       
   177 
       
   178         // run jmod hash command
       
   179         runJmod(List.of("hash", "--module-path", ht.lib.toString(),
       
   180                         "--hash-modules", ".*"));
       
   181 
       
   182         /*
       
   183          * z1 and y1 are the modules with hashes recorded.
       
   184          */
       
   185         ht.checkHashes("y1", "y2");
       
   186         ht.checkHashes("z1", "z2", "z3", "y2");
       
   187         Stream.concat(ys.stream(), zs.stream())
       
   188               .filter(mn -> !mn.equals("y1") && !mn.equals("z1"))
       
   189               .forEach(mn -> assertTrue(ht.hashes(mn) == null));
       
   190     }
       
   191 
       
   192     @Test
       
   193     public static void mixJmodAndJarFile() throws IOException {
       
   194         Path dest = Paths.get("test3");
       
   195         HashesTest ht = new HashesTest(dest);
       
   196 
       
   197         /*
       
   198          * j3 -----------> j2
       
   199          *    |______
       
   200          *    |      |
       
   201          *    V      V
       
   202          *    m3 -> m2
       
   203          *    |      |
       
   204          *    |      V
       
   205          *    |---> m1 -> j1 -> jdk.jlink
       
   206          */
       
   207 
       
   208         ht.makeModule("j1");
       
   209         ht.makeModule("j2");
       
   210         ht.makeModule("m1", "j1");
       
   211         ht.makeModule("m2", "m1");
       
   212         ht.makeModule("m3", "m1", "m2");
       
   213 
       
   214         ht.makeModule("j3", "j2", "m2", "m3");
       
   215 
       
   216         Set<String> jars = Set.of("j1", "j2", "j3");
       
   217         Set<String> jmods = Set.of("m1", "m2", "m3");
       
   218 
       
   219         // create JMOD and JAR files
       
   220         jars.forEach(ht::makeJar);
       
   221         jmods.forEach(ht::makeJmod);
       
   222 
       
   223         // run jmod hash command
       
   224         runJmod(List.of("hash", "--module-path", ht.lib.toString(),
       
   225                         "--hash-modules", "^j.*|^m.*"));
       
   226 
       
   227         /*
       
   228          * j1 and j2 are the modules with hashes recorded.
       
   229          */
       
   230         ht.checkHashes("j2", "j3");
       
   231         ht.checkHashes("j1", "m1", "m2", "m3", "j3");
       
   232         Stream.concat(jars.stream(), jmods.stream())
       
   233               .filter(mn -> !mn.equals("j1") && !mn.equals("j2"))
       
   234               .forEach(mn -> assertTrue(ht.hashes(mn) == null));
       
   235     }
       
   236 
       
   237     @Test
       
   238     public static void upgradeableModule() throws IOException {
       
   239         Path mpath = Paths.get(System.getProperty("java.home"), "jmods");
       
   240         if (!Files.exists(mpath)) {
       
   241             return;
       
   242         }
       
   243 
       
   244         Path dest = Paths.get("test4");
       
   245         HashesTest ht = new HashesTest(dest);
       
   246         ht.makeModule("m1");
       
   247         ht.makeModule("java.xml.bind", "m1");
       
   248         ht.makeModule("java.xml.ws", "java.xml.bind");
       
   249         ht.makeModule("m2", "java.xml.ws");
       
   250 
       
   251         ht.makeJmod("m1");
       
   252         ht.makeJmod("m2");
       
   253         ht.makeJmod("java.xml.ws");
       
   254         ht.makeJmod("java.xml.bind",
       
   255                     "--module-path",
       
   256                     ht.lib.toString() + File.pathSeparator + mpath,
       
   257                     "--hash-modules", "^java.xml.*|^m.*");
       
   258 
       
   259         ht.checkHashes("java.xml.bind", "java.xml.ws", "m2");
       
   260     }
       
   261 
       
   262     @Test
       
   263     public static void testImageJmods() throws IOException {
       
   264         Path mpath = Paths.get(System.getProperty("java.home"), "jmods");
       
   265         if (!Files.exists(mpath)) {
       
   266             return;
       
   267         }
       
   268 
       
   269         Path dest = Paths.get("test5");
       
   270         HashesTest ht = new HashesTest(dest);
       
   271         ht.makeModule("m1", "jdk.compiler", "jdk.attach");
       
   272         ht.makeModule("m2", "m1");
       
   273         ht.makeModule("m3", "java.compiler");
       
   274 
       
   275         ht.makeJmod("m1");
       
   276         ht.makeJmod("m2");
       
   277 
       
   278         runJmod(List.of("hash",
       
   279                         "--module-path",
       
   280                         mpath.toString() + File.pathSeparator + ht.lib.toString(),
       
   281                         "--hash-modules", ".*"));
       
   282 
       
   283         validateImageJmodsTest(ht, mpath);
       
   284     }
       
   285 
       
   286     @Test
       
   287     public static void testImageJmods1() throws IOException {
       
   288         Path mpath = Paths.get(System.getProperty("java.home"), "jmods");
       
   289         if (!Files.exists(mpath)) {
       
   290             return;
       
   291         }
       
   292 
       
   293         Path dest = Paths.get("test6");
       
   294         HashesTest ht = new HashesTest(dest);
       
   295         ht.makeModule("m1", "jdk.compiler", "jdk.attach");
       
   296         ht.makeModule("m2", "m1");
       
   297         ht.makeModule("m3", "java.compiler");
       
   298 
       
   299         ht.makeJar("m2");
       
   300         ht.makeJar("m1",
       
   301                     "--module-path",
       
   302                     mpath.toString() + File.pathSeparator + ht.lib.toString(),
       
   303                     "--hash-modules", ".*");
       
   304         validateImageJmodsTest(ht, mpath);
       
   305     }
       
   306 
       
   307     private static void validateImageJmodsTest(HashesTest ht, Path mpath)
       
   308         throws IOException
       
   309     {
       
   310         // hash is recorded in m1 and not any other packaged modules on module path
       
   311         ht.checkHashes("m1", "m2");
       
   312         assertTrue(ht.hashes("m2") == null);
       
   313 
       
   314         // should not override any JDK packaged modules
   139         ModuleFinder finder = new ModulePath(Runtime.version(),
   315         ModuleFinder finder = new ModulePath(Runtime.version(),
   140                                              true,
   316                                              true,
   141                                              jmods.resolve(name + ".jmod"));
   317                                              mpath);
       
   318         assertTrue(ht.hashes(finder,"jdk.compiler") == null);
       
   319         assertTrue(ht.hashes(finder,"jdk.attach") == null);
       
   320     }
       
   321 
       
   322     private void checkHashes(String mn, String... hashModules) throws IOException {
       
   323         ModuleHashes hashes = hashes(mn);
       
   324         assertTrue(hashes.names().equals(Set.of(hashModules)));
       
   325     }
       
   326 
       
   327     private ModuleHashes hashes(String name) {
       
   328         ModuleFinder finder = new ModulePath(Runtime.version(),
       
   329                                              true,
       
   330                                              lib);
       
   331         return hashes(finder, name);
       
   332     }
       
   333 
       
   334     private ModuleHashes hashes(ModuleFinder finder, String name) {
   142         ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new);
   335         ModuleReference mref = finder.find(name).orElseThrow(RuntimeException::new);
   143         ModuleReader reader = mref.open();
   336         try {
   144         try (InputStream in = reader.open("module-info.class").get()) {
   337             ModuleReader reader = mref.open();
   145             ModuleHashes hashes = ModuleInfo.read(in, null).recordedHashes();
   338             try (InputStream in = reader.open("module-info.class").get()) {
   146             System.out.format("hashes in module %s %s%n", name,
   339                 ModuleHashes hashes = ModuleInfo.read(in, null).recordedHashes();
       
   340                 System.out.format("hashes in module %s %s%n", name,
   147                     (hashes != null) ? "present" : "absent");
   341                     (hashes != null) ? "present" : "absent");
   148             if (hashes != null) {
   342                 if (hashes != null) {
   149                 hashes.names().stream()
   343                     hashes.names().stream().sorted().forEach(n ->
   150                     .sorted()
   344                         System.out.format("  %s %s%n", n, toHex(hashes.hashFor(n)))
   151                     .forEach(n -> System.out.format("  %s %s%n", n, hashes.hashFor(n)));
   345                     );
   152             }
   346                 }
   153             return hashes;
   347                 return hashes;
   154         } finally {
   348             } finally {
   155             reader.close();
   349                 reader.close();
   156         }
   350             }
       
   351         } catch (IOException e) {
       
   352             throw new UncheckedIOException(e);
       
   353         }
       
   354     }
       
   355 
       
   356     private String toHex(byte[] ba) {
       
   357         StringBuilder sb = new StringBuilder(ba.length);
       
   358         for (byte b: ba) {
       
   359             sb.append(String.format("%02x", b & 0xff));
       
   360         }
       
   361         return sb.toString();
   157     }
   362     }
   158 
   363 
   159     private void deleteDirectory(Path dir) throws IOException {
   364     private void deleteDirectory(Path dir) throws IOException {
   160         Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
   365         Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
   161             @Override
   366             @Override
   174                 return FileVisitResult.CONTINUE;
   379                 return FileVisitResult.CONTINUE;
   175             }
   380             }
   176         });
   381         });
   177     }
   382     }
   178 
   383 
       
   384 
       
   385     private void makeModule(String mn, String... deps) throws IOException {
       
   386         makeModule(mn, null, deps);
       
   387     }
       
   388 
       
   389     private void makeModule(String mn, ModuleDescriptor.Requires.Modifier mod,  String... deps)
       
   390         throws IOException
       
   391     {
       
   392         if (mod != null && mod != TRANSITIVE && mod != STATIC) {
       
   393             throw new IllegalArgumentException(mod.toString());
       
   394         }
       
   395 
       
   396         StringBuilder sb = new StringBuilder();
       
   397         sb.append("module " + mn + " {").append("\n");
       
   398         Arrays.stream(deps).forEach(req -> {
       
   399             sb.append("    requires ");
       
   400             if (mod != null) {
       
   401                 sb.append(mod.toString().toLowerCase()).append(" ");
       
   402             }
       
   403             sb.append(req + ";\n");
       
   404         });
       
   405         sb.append("}\n");
       
   406         builder.writeJavaFiles(mn, sb.toString());
       
   407 
       
   408         compileModule(mn, srcDir);
       
   409     }
       
   410 
   179     private void compileModule(String moduleName, Path src) throws IOException {
   411     private void compileModule(String moduleName, Path src) throws IOException {
   180         Path msrc = src.resolve(moduleName);
   412         Path msrc = src.resolve(moduleName);
   181         assertTrue(CompilerUtils.compile(msrc, mods, "--module-source-path", src.toString()));
   413         assertTrue(CompilerUtils.compile(msrc, mods, "--module-source-path", src.toString()));
   182     }
   414     }
   183 
   415 
   184     private void jmod(String moduleName, String... options) throws IOException {
   416     private void jmodHashModules(String moduleName, String hashModulesPattern) {
       
   417         makeJmod(moduleName, "--module-path", lib.toString(),
       
   418                  "--hash-modules", hashModulesPattern);
       
   419     }
       
   420 
       
   421     private void makeJmod(String moduleName, String... options) {
   185         Path mclasses = mods.resolve(moduleName);
   422         Path mclasses = mods.resolve(moduleName);
   186         Path outfile = jmods.resolve(moduleName + ".jmod");
   423         Path outfile = lib.resolve(moduleName + ".jmod");
   187         List<String> args = new ArrayList<>();
   424         List<String> args = new ArrayList<>();
   188         args.add("create");
   425         args.add("create");
   189         Collections.addAll(args, options);
   426         Collections.addAll(args, options);
   190         Collections.addAll(args, "--class-path", mclasses.toString(),
   427         Collections.addAll(args, "--class-path", mclasses.toString(),
   191                            outfile.toString());
   428                            outfile.toString());
   192 
   429 
   193         if (Files.exists(outfile))
   430         if (Files.exists(outfile)) {
   194             Files.delete(outfile);
   431             try {
   195 
   432                 Files.delete(outfile);
       
   433             } catch (IOException e) {
       
   434                 throw new UncheckedIOException(e);
       
   435             }
       
   436         }
   196         runJmod(args);
   437         runJmod(args);
   197     }
   438     }
   198 
   439 
   199     private void runJmod(List<String> args) {
   440     private static void runJmod(List<String> args) {
   200         int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()]));
   441         int rc = JMOD_TOOL.run(System.out, System.out, args.toArray(new String[args.size()]));
   201         System.out.println("jmod options: " + args.stream().collect(Collectors.joining(" ")));
   442         System.out.println("jmod " + args.stream().collect(Collectors.joining(" ")));
   202         if (rc != 0) {
   443         if (rc != 0) {
   203             throw new AssertionError("Jmod failed: rc = " + rc);
   444             throw new AssertionError("jmod failed: rc = " + rc);
       
   445         }
       
   446     }
       
   447 
       
   448     private void makeJar(String moduleName, String... options) {
       
   449         Path mclasses = mods.resolve(moduleName);
       
   450         Path outfile = lib.resolve(moduleName + ".jar");
       
   451         List<String> args = new ArrayList<>();
       
   452         Stream.concat(Stream.of("--create",
       
   453                                 "--file=" + outfile.toString()),
       
   454                       Arrays.stream(options))
       
   455               .forEach(args::add);
       
   456         args.add("-C");
       
   457         args.add(mclasses.toString());
       
   458         args.add(".");
       
   459 
       
   460         if (Files.exists(outfile)) {
       
   461             try {
       
   462                 Files.delete(outfile);
       
   463             } catch (IOException e) {
       
   464                 throw new UncheckedIOException(e);
       
   465             }
       
   466         }
       
   467 
       
   468         int rc = JAR_TOOL.run(System.out, System.out, args.toArray(new String[args.size()]));
       
   469         System.out.println("jar " + args.stream().collect(Collectors.joining(" ")));
       
   470         if (rc != 0) {
       
   471             throw new AssertionError("jar failed: rc = " + rc);
   204         }
   472         }
   205     }
   473     }
   206 }
   474 }