1 /*
   2  * Copyright (c) 2015, 2018, 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 import java.io.*;
  25 import java.lang.module.ModuleDescriptor;
  26 import java.lang.reflect.Method;
  27 import java.nio.file.*;
  28 import java.util.*;
  29 import java.util.function.Consumer;
  30 import java.util.jar.JarEntry;
  31 import java.util.jar.JarFile;
  32 import java.util.jar.JarInputStream;
  33 import java.util.jar.Manifest;
  34 import java.util.regex.Pattern;
  35 import java.util.stream.Collectors;
  36 import java.util.stream.Stream;
  37 
  38 import jdk.test.lib.util.FileUtils;
  39 import jdk.test.lib.JDKToolFinder;
  40 import org.testng.annotations.BeforeTest;
  41 import org.testng.annotations.DataProvider;
  42 import org.testng.annotations.Test;
  43 
  44 import static java.lang.String.format;
  45 import static java.lang.System.out;
  46 
  47 /*
  48  * @test
  49  * @bug 8167328 8171830 8165640 8174248 8176772 8196748 8191533 8210454
  50  * @library /test/lib
  51  * @modules jdk.compiler
  52  *          jdk.jartool
  53  * @build jdk.test.lib.Platform
  54  *        jdk.test.lib.util.FileUtils
  55  *        jdk.test.lib.JDKToolFinder
  56  * @compile Basic.java
  57  * @run testng Basic
  58  * @summary Tests for plain Modular jars & Multi-Release Modular jars
  59  */
  60 
  61 public class Basic {
  62     static final Path TEST_SRC = Paths.get(System.getProperty("test.src", "."));
  63     static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
  64     static final Path MODULE_CLASSES = TEST_CLASSES.resolve("build");
  65     static final Path MRJAR_DIR = MODULE_CLASSES.resolve("mrjar");
  66 
  67     static final String VM_OPTIONS = System.getProperty("test.vm.opts", "");
  68     static final String TOOL_VM_OPTIONS = System.getProperty("test.tool.vm.opts", "");
  69     static final String JAVA_OPTIONS = System.getProperty("test.java.opts", "");
  70 
  71     // Details based on the checked in module source
  72     static TestModuleData FOO = new TestModuleData("foo",
  73                                                    "1.123",
  74                                                    "jdk.test.foo.Foo",
  75                                                    "Hello World!!!",
  76                                                    null, // no hashes
  77                                                    Set.of("java.base"),
  78                                                    Set.of("jdk.test.foo"),
  79                                                    null, // no uses
  80                                                    null, // no provides
  81                                                    Set.of("jdk.test.foo.internal",
  82                                                           "jdk.test.foo.resources"));
  83     static TestModuleData BAR = new TestModuleData("bar",
  84                                                    "4.5.6.7",
  85                                                    "jdk.test.bar.Bar",
  86                                                    "Hello from Bar!",
  87                                                    null, // no hashes
  88                                                    Set.of("java.base", "foo"),
  89                                                    null, // no exports
  90                                                    null, // no uses
  91                                                    null, // no provides
  92                                                    Set.of("jdk.test.bar",
  93                                                           "jdk.test.bar.internal"));
  94 
  95     static class TestModuleData {
  96         final String moduleName;
  97         final Set<String> requires;
  98         final Set<String> exports;
  99         final Set<String> uses;
 100         final Set<String> provides;
 101         final String mainClass;
 102         final String version;
 103         final String message;
 104         final String hashes;
 105         final Set<String> packages;
 106 
 107         TestModuleData(String mn, String v, String mc, String m, String h,
 108                        Set<String> requires, Set<String> exports, Set<String> uses,
 109                        Set<String> provides, Set<String> contains) {
 110             moduleName = mn; mainClass = mc; version = v; message = m; hashes = h;
 111             this.requires = requires != null ? requires : Collections.emptySet();
 112             this.exports = exports != null ? exports : Collections.emptySet();
 113             this.uses = uses != null ? uses : Collections.emptySet();;
 114             this.provides = provides != null ? provides : Collections.emptySet();
 115             this.packages = Stream.concat(this.exports.stream(), contains.stream())
 116                                   .collect(Collectors.toSet());
 117         }
 118         static TestModuleData from(String s) {
 119             try {
 120                 BufferedReader reader = new BufferedReader(new StringReader(s));
 121                 String line;
 122                 String message = null;
 123                 String name = null, version = null, mainClass = null;
 124                 String hashes = null;
 125                 Set<String> requires, exports, uses, provides, conceals;
 126                 requires = exports = uses = provides = conceals = null;
 127                 while ((line = reader.readLine()) != null) {
 128                     if (line.startsWith("message:")) {
 129                         message = line.substring("message:".length());
 130                     } else if (line.startsWith("nameAndVersion:")) {
 131                         line = line.substring("nameAndVersion:".length());
 132                         int i = line.indexOf('@');
 133                         if (i != -1) {
 134                             name = line.substring(0, i);
 135                             version = line.substring(i + 1, line.length());
 136                         } else {
 137                             name = line;
 138                         }
 139                     } else if (line.startsWith("mainClass:")) {
 140                         mainClass = line.substring("mainClass:".length());
 141                     } else if (line.startsWith("requires:")) {
 142                         line = line.substring("requires:".length());
 143                         requires = stringToSet(line);
 144                     } else if (line.startsWith("exports:")) {
 145                         line = line.substring("exports:".length());
 146                         exports = stringToSet(line);
 147                     } else if (line.startsWith("uses:")) {
 148                         line = line.substring("uses:".length());
 149                         uses = stringToSet(line);
 150                     } else if (line.startsWith("provides:")) {
 151                         line = line.substring("provides:".length());
 152                         provides = stringToSet(line);
 153                     } else if (line.startsWith("hashes:")) {
 154                         hashes = line.substring("hashes:".length());
 155                     } else if (line.startsWith("contains:")) {
 156                         line = line.substring("contains:".length());
 157                         conceals = stringToSet(line);
 158                     } else if (line.contains("VM warning:")) {
 159                         continue;  // ignore server vm warning see#8196748
 160                     } else {
 161                         throw new AssertionError("Unknown value " + line);
 162                     }
 163                 }
 164 
 165                 return new TestModuleData(name, version, mainClass, message,
 166                                           hashes, requires, exports, uses,
 167                                           provides, conceals);
 168             } catch (IOException x) {
 169                 throw new UncheckedIOException(x);
 170             }
 171         }
 172         static Set<String> stringToSet(String commaList) {
 173             Set<String> s = new HashSet<>();
 174             int i = commaList.indexOf(',');
 175             if (i != -1) {
 176                 String[] p = commaList.split(",");
 177                 Stream.of(p).forEach(s::add);
 178             } else {
 179                 s.add(commaList);
 180             }
 181             return s;
 182         }
 183     }
 184 
 185     static void assertModuleData(Result r, TestModuleData expected) {
 186         //out.printf("%s%n", r.output);
 187         TestModuleData received = TestModuleData.from(r.output);
 188         if (expected.message != null)
 189             assertTrue(expected.message.equals(received.message),
 190                        "Expected message:", expected.message, ", got:", received.message);
 191         assertTrue(expected.moduleName.equals(received.moduleName),
 192                    "Expected moduleName: ", expected.moduleName, ", got:", received.moduleName);
 193         assertTrue(expected.version.equals(received.version),
 194                    "Expected version: ", expected.version, ", got:", received.version);
 195         assertTrue(expected.mainClass.equals(received.mainClass),
 196                    "Expected mainClass: ", expected.mainClass, ", got:", received.mainClass);
 197         assertSetsEqual(expected.requires, received.requires);
 198         assertSetsEqual(expected.exports, received.exports);
 199         assertSetsEqual(expected.uses, received.uses);
 200         assertSetsEqual(expected.provides, received.provides);
 201         assertSetsEqual(expected.packages, received.packages);
 202     }
 203 
 204     static void assertSetsEqual(Set<String> s1, Set<String> s2) {
 205         if (!s1.equals(s2)) {
 206             org.testng.Assert.assertTrue(false, s1 + " vs " + s2);
 207         }
 208      }
 209 
 210     @BeforeTest
 211     public void compileModules() throws Exception {
 212         compileModule(FOO.moduleName);
 213         compileModule(BAR.moduleName, MODULE_CLASSES);
 214         compileModule("baz");  // for service provider consistency checking
 215 
 216         // copy resources
 217         copyResource(TEST_SRC.resolve("src").resolve(FOO.moduleName),
 218                      MODULE_CLASSES.resolve(FOO.moduleName),
 219                      "jdk/test/foo/resources/foo.properties");
 220 
 221         setupMRJARModuleInfo(FOO.moduleName);
 222         setupMRJARModuleInfo(BAR.moduleName);
 223         setupMRJARModuleInfo("baz");
 224     }
 225 
 226     @Test
 227     public void createFoo() throws IOException {
 228         Path mp = Paths.get("createFoo");
 229         createTestDir(mp);
 230         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 231         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 232 
 233         jar("--create",
 234             "--file=" + modularJar.toString(),
 235             "--main-class=" + FOO.mainClass,
 236             "--module-version=" + FOO.version,
 237             "--no-manifest",
 238             "-C", modClasses.toString(), ".")
 239             .assertSuccess();
 240 
 241         assertSetsEqual(readPackagesAttribute(modularJar),
 242                         Set.of("jdk.test.foo",
 243                                "jdk.test.foo.resources",
 244                                "jdk.test.foo.internal"));
 245 
 246         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 247             .assertSuccess()
 248             .resultChecker(r -> assertModuleData(r, FOO));
 249         try (InputStream fis = Files.newInputStream(modularJar);
 250              JarInputStream jis = new JarInputStream(fis)) {
 251             assertTrue(!jarContains(jis, "./"),
 252                        "Unexpected ./ found in ", modularJar.toString());
 253         }
 254     }
 255 
 256     /** Similar to createFoo, but with a Multi-Release Modular jar. */
 257     @Test
 258     public void createMRMJarFoo() throws IOException {
 259         Path mp = Paths.get("createMRMJarFoo");
 260         createTestDir(mp);
 261         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 262         Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
 263         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 264 
 265         // Positive test, create
 266         jar("--create",
 267             "--file=" + modularJar.toString(),
 268             "--main-class=" + FOO.mainClass,
 269             "--module-version=" + FOO.version,
 270             "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
 271             "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
 272             "-C", modClasses.toString(), ".")
 273             .assertSuccess();
 274         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 275             .assertSuccess()
 276             .resultChecker(r -> assertModuleData(r, FOO));
 277     }
 278 
 279 
 280     @Test
 281     public void updateFoo() throws IOException {
 282         Path mp = Paths.get("updateFoo");
 283         createTestDir(mp);
 284         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 285         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 286 
 287         jar("--create",
 288             "--file=" + modularJar.toString(),
 289             "--no-manifest",
 290             "-C", modClasses.toString(), "jdk")
 291             .assertSuccess();
 292         jar("--update",
 293             "--file=" + modularJar.toString(),
 294             "--main-class=" + FOO.mainClass,
 295             "--module-version=" + FOO.version,
 296             "--no-manifest",
 297             "-C", modClasses.toString(), "module-info.class")
 298             .assertSuccess();
 299         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 300             .assertSuccess()
 301             .resultChecker(r -> assertModuleData(r, FOO));
 302     }
 303 
 304     @Test
 305     public void updateMRMJarFoo() throws IOException {
 306         Path mp = Paths.get("updateMRMJarFoo");
 307         createTestDir(mp);
 308         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 309         Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
 310         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 311 
 312         jar("--create",
 313             "--file=" + modularJar.toString(),
 314             "--no-manifest",
 315             "-C", modClasses.toString(), "jdk")
 316             .assertSuccess();
 317         jar("--update",
 318             "--file=" + modularJar.toString(),
 319             "--main-class=" + FOO.mainClass,
 320             "--module-version=" + FOO.version,
 321             "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
 322             "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
 323             "-C", modClasses.toString(), "module-info.class")
 324             .assertSuccess();
 325         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 326             .assertSuccess()
 327             .resultChecker(r -> assertModuleData(r, FOO));
 328     }
 329 
 330     @Test
 331     public void partialUpdateFooMainClass() throws IOException {
 332         Path mp = Paths.get("partialUpdateFooMainClass");
 333         createTestDir(mp);
 334         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 335         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 336 
 337         // A "bad" main class in first create ( and no version )
 338         jar("--create",
 339             "--file=" + modularJar.toString(),
 340             "--main-class=" + "jdk.test.foo.IAmNotTheEntryPoint",
 341             "--no-manifest",
 342             "-C", modClasses.toString(), ".")  // includes module-info.class
 343            .assertSuccess();
 344         jar("--update",
 345             "--file=" + modularJar.toString(),
 346             "--main-class=" + FOO.mainClass,
 347             "--module-version=" + FOO.version,
 348             "--no-manifest")
 349             .assertSuccess();
 350         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 351             .assertSuccess()
 352             .resultChecker(r -> assertModuleData(r, FOO));
 353     }
 354 
 355     @Test
 356     public void partialUpdateFooVersion() throws IOException {
 357         Path mp = Paths.get("partialUpdateFooVersion");
 358         createTestDir(mp);
 359         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 360         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 361 
 362         // A "bad" version in first create ( and no main class )
 363         jar("--create",
 364             "--file=" + modularJar.toString(),
 365             "--module-version=" + "100000000",
 366             "--no-manifest",
 367             "-C", modClasses.toString(), ".")  // includes module-info.class
 368             .assertSuccess();
 369         jar("--update",
 370             "--file=" + modularJar.toString(),
 371             "--main-class=" + FOO.mainClass,
 372             "--module-version=" + FOO.version,
 373             "--no-manifest")
 374             .assertSuccess();
 375         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 376             .assertSuccess()
 377             .resultChecker(r -> assertModuleData(r, FOO));
 378     }
 379 
 380     @Test
 381     public void partialUpdateFooNotAllFiles() throws IOException {
 382         Path mp = Paths.get("partialUpdateFooNotAllFiles");
 383         createTestDir(mp);
 384         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 385         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 386 
 387         // Not all files, and none from non-exported packages,
 388         // i.e. no concealed list in first create
 389         jar("--create",
 390             "--file=" + modularJar.toString(),
 391             "--no-manifest",
 392             "-C", modClasses.toString(), "module-info.class",
 393             "-C", modClasses.toString(), "jdk/test/foo/Foo.class",
 394             "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties")
 395             .assertSuccess();
 396         jar("--update",
 397             "--file=" + modularJar.toString(),
 398             "--main-class=" + FOO.mainClass,
 399             "--module-version=" + FOO.version,
 400             "--no-manifest",
 401             "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class")
 402             .assertSuccess();
 403         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 404             .assertSuccess()
 405             .resultChecker(r -> assertModuleData(r, FOO));
 406     }
 407 
 408     @Test
 409     public void partialUpdateMRMJarFooNotAllFiles() throws IOException {
 410         Path mp = Paths.get("partialUpdateMRMJarFooNotAllFiles");
 411         createTestDir(mp);
 412         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 413         Path mrjarDir = MRJAR_DIR.resolve(FOO.moduleName);
 414         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 415 
 416         jar("--create",
 417             "--file=" + modularJar.toString(),
 418             "--module-version=" + FOO.version,
 419             "-C", modClasses.toString(), ".")
 420             .assertSuccess();
 421         jar("--update",
 422             "--file=" + modularJar.toString(),
 423             "--main-class=" + FOO.mainClass,
 424             "--module-version=" + FOO.version,
 425             "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
 426             "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class")
 427             .assertSuccess();
 428         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 429             .assertSuccess()
 430             .resultChecker(r -> assertModuleData(r, FOO));
 431     }
 432 
 433     @Test
 434     public void partialUpdateFooAllFilesAndAttributes() throws IOException {
 435         Path mp = Paths.get("partialUpdateFooAllFilesAndAttributes");
 436         createTestDir(mp);
 437         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 438         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 439 
 440         // all attributes and files
 441         jar("--create",
 442             "--file=" + modularJar.toString(),
 443             "--main-class=" + FOO.mainClass,
 444             "--module-version=" + FOO.version,
 445             "--no-manifest",
 446             "-C", modClasses.toString(), ".")
 447             .assertSuccess();
 448         jar("--update",
 449             "--file=" + modularJar.toString(),
 450             "--main-class=" + FOO.mainClass,
 451             "--module-version=" + FOO.version,
 452             "--no-manifest",
 453             "-C", modClasses.toString(), ".")
 454             .assertSuccess();
 455         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 456             .assertSuccess()
 457             .resultChecker(r -> assertModuleData(r, FOO));
 458     }
 459 
 460     @Test
 461     public void partialUpdateFooModuleInfo() throws IOException {
 462         Path mp = Paths.get("partialUpdateFooModuleInfo");
 463         createTestDir(mp);
 464         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 465         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 466         Path barModInfo = MODULE_CLASSES.resolve(BAR.moduleName);
 467 
 468         jar("--create",
 469             "--file=" + modularJar.toString(),
 470             "--main-class=" + FOO.mainClass,
 471             "--module-version=" + FOO.version,
 472             "--no-manifest",
 473             "-C", modClasses.toString(), ".")
 474             .assertSuccess();
 475         jar("--update",
 476             "--file=" + modularJar.toString(),
 477             "--no-manifest",
 478             "-C", barModInfo.toString(), "module-info.class")  // stuff in bar's info
 479             .assertSuccess();
 480         jar("-d",
 481             "--file=" + modularJar.toString())
 482             .assertSuccess()
 483             .resultChecker(r -> {
 484                 // Expect "bar jar:file:/.../!module-info.class"
 485                 // conceals jdk.test.foo, conceals jdk.test.foo.internal"
 486                 String uri = "jar:" + modularJar.toUri().toString() + "/!module-info.class";
 487                 assertTrue(r.output.contains("bar " + uri),
 488                            "Expecting to find \"bar " + uri + "\"",
 489                            "in output, but did not: [" + r.output + "]");
 490                 Pattern p = Pattern.compile(
 491                         "contains\\s+jdk.test.foo\\s+contains\\s+jdk.test.foo.internal");
 492                 assertTrue(p.matcher(r.output).find(),
 493                            "Expecting to find \"contains jdk.test.foo,...\"",
 494                            "in output, but did not: [" + r.output + "]");
 495             });
 496     }
 497 
 498 
 499     @Test
 500     public void partialUpdateFooPackagesAttribute() throws IOException {
 501         Path mp = Paths.get("partialUpdateFooPackagesAttribute");
 502         createTestDir(mp);
 503         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 504         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 505 
 506         // Not all files, and none from non-exported packages,
 507         // i.e. no concealed list in first create
 508         jar("--create",
 509             "--file=" + modularJar.toString(),
 510             "--no-manifest",
 511             "-C", modClasses.toString(), "module-info.class",
 512             "-C", modClasses.toString(), "jdk/test/foo/Foo.class")
 513             .assertSuccess();
 514 
 515         assertSetsEqual(readPackagesAttribute(modularJar),
 516                         Set.of("jdk.test.foo"));
 517 
 518         jar("--update",
 519             "--file=" + modularJar.toString(),
 520             "-C", modClasses.toString(), "jdk/test/foo/resources/foo.properties")
 521             .assertSuccess();
 522 
 523         assertSetsEqual(readPackagesAttribute(modularJar),
 524                         Set.of("jdk.test.foo", "jdk.test.foo.resources"));
 525 
 526         jar("--update",
 527             "--file=" + modularJar.toString(),
 528             "--main-class=" + FOO.mainClass,
 529             "--module-version=" + FOO.version,
 530             "--no-manifest",
 531             "-C", modClasses.toString(), "jdk/test/foo/internal/Message.class")
 532             .assertSuccess();
 533 
 534         assertSetsEqual(readPackagesAttribute(modularJar),
 535                         Set.of("jdk.test.foo",
 536                                "jdk.test.foo.resources",
 537                                "jdk.test.foo.internal"));
 538 
 539         java(mp, FOO.moduleName + "/" + FOO.mainClass)
 540             .assertSuccess()
 541             .resultChecker(r -> assertModuleData(r, FOO));
 542     }
 543 
 544     private Set<String> readPackagesAttribute(Path jar) {
 545         return getModuleDescriptor(jar).packages();
 546     }
 547 
 548     @Test
 549     public void hashBarInFooModule() throws IOException {
 550         Path mp = Paths.get("dependencesFooBar");
 551         createTestDir(mp);
 552 
 553         Path modClasses = MODULE_CLASSES.resolve(BAR.moduleName);
 554         Path modularJar = mp.resolve(BAR.moduleName + ".jar");
 555         jar("--create",
 556             "--file=" + modularJar.toString(),
 557             "--main-class=" + BAR.mainClass,
 558             "--module-version=" + BAR.version,
 559             "--no-manifest",
 560             "-C", modClasses.toString(), ".")
 561             .assertSuccess();
 562 
 563         modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 564         modularJar = mp.resolve(FOO.moduleName + ".jar");
 565         jar("--create",
 566             "--file=" + modularJar.toString(),
 567             "--main-class=" + FOO.mainClass,
 568             "--module-version=" + FOO.version,
 569             "--module-path=" + mp.toString(),
 570             "--hash-modules=" + "bar",
 571             "--no-manifest",
 572             "-C", modClasses.toString(), ".")
 573             .assertSuccess();
 574 
 575         java(mp, BAR.moduleName + "/" + BAR.mainClass,
 576              "--add-exports", "java.base/jdk.internal.misc=bar",
 577              "--add-exports", "java.base/jdk.internal.module=bar")
 578             .assertSuccess()
 579             .resultChecker(r -> {
 580                 assertModuleData(r, BAR);
 581                 TestModuleData received = TestModuleData.from(r.output);
 582                 assertTrue(received.hashes != null, "Expected non-null hashes value.");
 583             });
 584     }
 585 
 586     @Test
 587     public void invalidHashInFooModule() throws IOException {
 588         Path mp = Paths.get("badDependencyFooBar");
 589         createTestDir(mp);
 590 
 591         Path barClasses = MODULE_CLASSES.resolve(BAR.moduleName);
 592         Path barJar = mp.resolve(BAR.moduleName + ".jar");
 593         jar("--create",
 594             "--file=" + barJar.toString(),
 595             "--main-class=" + BAR.mainClass,
 596             "--module-version=" + BAR.version,
 597             "--no-manifest",
 598             "-C", barClasses.toString(), ".").assertSuccess();
 599 
 600         Path fooClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 601         Path fooJar = mp.resolve(FOO.moduleName + ".jar");
 602         jar("--create",
 603             "--file=" + fooJar.toString(),
 604             "--main-class=" + FOO.mainClass,
 605             "--module-version=" + FOO.version,
 606             "-p", mp.toString(),  // test short-form
 607             "--hash-modules=" + "bar",
 608             "--no-manifest",
 609             "-C", fooClasses.toString(), ".").assertSuccess();
 610 
 611         // Rebuild bar.jar with a change that will cause its hash to be different
 612         FileUtils.deleteFileWithRetry(barJar);
 613         jar("--create",
 614             "--file=" + barJar.toString(),
 615             "--main-class=" + BAR.mainClass,
 616             "--module-version=" + BAR.version + ".1", // a newer version
 617             "--no-manifest",
 618             "-C", barClasses.toString(), ".").assertSuccess();
 619 
 620         java(mp, BAR.moduleName + "/" + BAR.mainClass,
 621              "--add-exports", "java.base/jdk.internal.misc=bar",
 622              "--add-exports", "java.base/jdk.internal.module=bar")
 623             .assertFailure()
 624             .resultChecker(r -> {
 625                 // Expect similar output: "java.lang.module.ResolutionException: Hash
 626                 // of bar (WdktSIQSkd4+CEacpOZoeDrCosMATNrIuNub9b5yBeo=) differs to
 627                 // expected hash (iepvdv8xTeVrFgMtUhcFnmetSub6qQHCHc92lSaSEg0=)"
 628                 Pattern p = Pattern.compile(".*Hash of bar.*differs to expected hash.*");
 629                 assertTrue(p.matcher(r.output).find(),
 630                       "Expecting error message containing \"Hash of bar ... differs to"
 631                               + " expected hash...\" but got: [", r.output + "]");
 632             });
 633     }
 634 
 635     @Test
 636     public void badOptionsFoo() throws IOException {
 637         Path mp = Paths.get("badOptionsFoo");
 638         createTestDir(mp);
 639         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 640         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 641 
 642         jar("--create",
 643             "--file=" + modularJar.toString(),
 644             "--module-version=" + 1.1,   // no module-info.class
 645             "-C", modClasses.toString(), "jdk")
 646             .assertFailure();      // TODO: expected failure message
 647 
 648          jar("--create",
 649              "--file=" + modularJar.toString(),
 650              "--hash-modules=" + ".*",   // no module-info.class
 651              "-C", modClasses.toString(), "jdk")
 652              .assertFailure();      // TODO: expected failure message
 653     }
 654 
 655     @Test
 656     public void servicesCreateWithoutFailure() throws IOException {
 657         Path mp = Paths.get("servicesCreateWithoutFailure");
 658         createTestDir(mp);
 659         Path modClasses = MODULE_CLASSES.resolve("baz");
 660         Path modularJar = mp.resolve("baz" + ".jar");
 661 
 662         // Positive test, create
 663         jar("--create",
 664             "--file=" + modularJar.toString(),
 665             "-C", modClasses.toString(), "module-info.class",
 666             "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
 667             "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
 668             .assertSuccess();
 669 
 670         for (String option : new String[]  {"--describe-module", "-d" }) {
 671             jar(option,
 672                 "--file=" + modularJar.toString())
 673                 .assertSuccess()
 674                 .resultChecker(r ->
 675                     assertTrue(r.output.contains("provides jdk.test.baz.BazService with jdk.test.baz.internal.BazServiceImpl"),
 676                                "Expected to find ", "provides jdk.test.baz.BazService with jdk.test.baz.internal.BazServiceImpl",
 677                                " in [", r.output, "]")
 678                 );
 679         }
 680     }
 681 
 682     @Test
 683     public void servicesCreateWithoutServiceImpl() throws IOException {
 684         Path mp = Paths.get("servicesWithoutServiceImpl");
 685         createTestDir(mp);
 686         Path modClasses = MODULE_CLASSES.resolve("baz");
 687         Path modularJar = mp.resolve("baz" + ".jar");
 688 
 689         // Omit service impl
 690         jar("--create",
 691             "--file=" + modularJar.toString(),
 692             "-C", modClasses.toString(), "module-info.class",
 693             "-C", modClasses.toString(), "jdk/test/baz/BazService.class")
 694             .assertFailure();
 695     }
 696 
 697     @Test
 698     public void servicesUpdateWithoutFailure() throws IOException {
 699         Path mp = Paths.get("servicesUpdateWithoutFailure");
 700         createTestDir(mp);
 701         Path modClasses = MODULE_CLASSES.resolve("baz");
 702         Path modularJar = mp.resolve("baz" + ".jar");
 703 
 704         // Positive test, update
 705         jar("--create",
 706             "--file=" + modularJar.toString(),
 707             "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
 708             "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
 709             .assertSuccess();
 710         jar("--update",
 711             "--file=" + modularJar.toString(),
 712             "-C", modClasses.toString(), "module-info.class")
 713             .assertSuccess();
 714     }
 715 
 716     @Test
 717     public void servicesUpdateWithoutServiceImpl() throws IOException {
 718         Path mp = Paths.get("servicesUpdateWithoutServiceImpl");
 719         createTestDir(mp);
 720         Path modClasses = MODULE_CLASSES.resolve("baz");
 721         Path modularJar = mp.resolve("baz" + ".jar");
 722 
 723         // Omit service impl
 724         jar("--create",
 725             "--file=" + modularJar.toString(),
 726             "-C", modClasses.toString(), "jdk/test/baz/BazService.class")
 727             .assertSuccess();
 728         jar("--update",
 729             "--file=" + modularJar.toString(),
 730             "-C", modClasses.toString(), "module-info.class")
 731             .assertFailure();
 732     }
 733 
 734     @Test
 735     public void servicesCreateWithoutFailureMRMJAR() throws IOException {
 736         Path mp = Paths.get("servicesCreateWithoutFailureMRMJAR");
 737         createTestDir(mp);
 738         Path modClasses = MODULE_CLASSES.resolve("baz");
 739         Path mrjarDir = MRJAR_DIR.resolve("baz");
 740         Path modularJar = mp.resolve("baz" + ".jar");
 741 
 742         jar("--create",
 743             "--file=" + modularJar.toString(),
 744             "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
 745             "-C", modClasses.toString(), "module-info.class",
 746             "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
 747             "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
 748             "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
 749             .assertSuccess();
 750     }
 751 
 752     @Test
 753     public void servicesCreateWithoutFailureNonRootMRMJAR() throws IOException {
 754         // without a root module-info.class
 755         Path mp = Paths.get("servicesCreateWithoutFailureNonRootMRMJAR");
 756         createTestDir(mp);
 757         Path modClasses = MODULE_CLASSES.resolve("baz");
 758         Path mrjarDir = MRJAR_DIR.resolve("baz");
 759         Path modularJar = mp.resolve("baz.jar");
 760 
 761         jar("--create",
 762             "--file=" + modularJar.toString(),
 763             "--main-class=" + "jdk.test.baz.Baz",
 764             "-m", mrjarDir.resolve("META-INF/MANIFEST.MF").toRealPath().toString(),
 765             "-C", mrjarDir.toString(), "META-INF/versions/9/module-info.class",
 766             "-C", modClasses.toString(), "jdk/test/baz/BazService.class",
 767             "-C", modClasses.toString(), "jdk/test/baz/Baz.class",
 768             "-C", modClasses.toString(), "jdk/test/baz/internal/BazServiceImpl.class")
 769             .assertSuccess();
 770 
 771 
 772         for (String option : new String[]  {"--describe-module", "-d" }) {
 773 
 774             jar(option,
 775                 "--file=" + modularJar.toString(),
 776                 "--release", "9")
 777                 .assertSuccess()
 778                 .resultChecker(r ->
 779                     assertTrue(r.output.contains("main-class jdk.test.baz.Baz"),
 780                               "Expected to find ", "main-class jdk.test.baz.Baz",
 781                                " in [", r.output, "]"));
 782 
 783             jarWithStdin(modularJar.toFile(), option, "--release", "9")
 784                 .assertSuccess()
 785                 .resultChecker(r ->
 786                     assertTrue(r.output.contains("main-class jdk.test.baz.Baz"),
 787                               "Expected to find ", "main-class jdk.test.baz.Baz",
 788                                " in [", r.output, "]"));
 789 
 790         }
 791         // run module main class
 792         java(mp, "baz/jdk.test.baz.Baz")
 793             .assertSuccess()
 794             .resultChecker(r ->
 795                assertTrue(r.output.contains("mainClass:jdk.test.baz.Baz"),
 796                           "Expected to find ", "mainClass:jdk.test.baz.Baz",
 797                           " in [", r.output, "]"));
 798     }
 799 
 800     @Test
 801     public void exportCreateWithMissingPkg() throws IOException {
 802 
 803         Path foobar = TEST_SRC.resolve("src").resolve("foobar");
 804         Path dst = Files.createDirectories(MODULE_CLASSES.resolve("foobar"));
 805         javac(dst, null, sourceList(foobar));
 806 
 807         Path mp = Paths.get("exportWithMissingPkg");
 808         createTestDir(mp);
 809         Path modClasses = dst;
 810         Path modularJar = mp.resolve("foofoo.jar");
 811 
 812         jar("--create",
 813             "--file=" + modularJar.toString(),
 814             "-C", modClasses.toString(), "module-info.class",
 815             "-C", modClasses.toString(), "jdk/test/foo/Foo.class")
 816             .assertFailure();
 817     }
 818 
 819     @Test
 820     public void describeModuleFoo() throws IOException {
 821         Path mp = Paths.get("describeModuleFoo");
 822         createTestDir(mp);
 823         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 824         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 825 
 826         jar("--create",
 827             "--file=" + modularJar.toString(),
 828             "--main-class=" + FOO.mainClass,
 829             "--module-version=" + FOO.version,
 830             "--no-manifest",
 831             "-C", modClasses.toString(), ".")
 832             .assertSuccess();
 833 
 834         for (String option : new String[]  {"--describe-module", "-d" }) {
 835             jar(option,
 836                 "--file=" + modularJar.toString())
 837                 .assertSuccess()
 838                 .resultChecker(r ->
 839                     assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version),
 840                                "Expected to find ", FOO.moduleName + "@" + FOO.version,
 841                                " in [", r.output, "]")
 842                 );
 843 
 844             jar(option,
 845                 "--file=" + modularJar.toString(),
 846                 modularJar.toString())
 847             .assertFailure();
 848 
 849             jar(option, modularJar.toString())
 850             .assertFailure();
 851         }
 852     }
 853 
 854     @Test
 855     public void describeModuleFooFromStdin() throws IOException {
 856         Path mp = Paths.get("describeModuleFooFromStdin");
 857         createTestDir(mp);
 858         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 859         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 860 
 861         jar("--create",
 862             "--file=" + modularJar.toString(),
 863             "--main-class=" + FOO.mainClass,
 864             "--module-version=" + FOO.version,
 865             "--no-manifest",
 866             "-C", modClasses.toString(), ".")
 867             .assertSuccess();
 868 
 869         for (String option : new String[]  {"--describe-module", "-d" }) {
 870             jarWithStdin(modularJar.toFile(),
 871                          option)
 872                          .assertSuccess()
 873                          .resultChecker(r ->
 874                              assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version),
 875                                 "Expected to find ", FOO.moduleName + "@" + FOO.version,
 876                                 " in [", r.output, "]")
 877                 );
 878         }
 879     }
 880 
 881     /**
 882      * Validate that you can update a jar only specifying --module-version
 883      * @throws IOException
 884      */
 885     @Test
 886     public void updateFooModuleVersion() throws IOException {
 887         Path mp = Paths.get("updateFooModuleVersion");
 888         createTestDir(mp);
 889         Path modClasses = MODULE_CLASSES.resolve(FOO.moduleName);
 890         Path modularJar = mp.resolve(FOO.moduleName + ".jar");
 891         String newFooVersion = "87.0";
 892 
 893         jar("--create",
 894             "--file=" + modularJar.toString(),
 895             "--main-class=" + FOO.mainClass,
 896             "--module-version=" + FOO.version,
 897             "--no-manifest",
 898             "-C", modClasses.toString(), ".")
 899             .assertSuccess();
 900 
 901         jarWithStdin(modularJar.toFile(), "--describe-module")
 902                 .assertSuccess()
 903                 .resultChecker(r ->
 904                         assertTrue(r.output.contains(FOO.moduleName + "@" + FOO.version),
 905                                 "Expected to find ", FOO.moduleName + "@" + FOO.version,
 906                                 " in [", r.output, "]")
 907                 );
 908 
 909         jar("--update",
 910             "--file=" + modularJar.toString(),
 911             "--module-version=" + newFooVersion)
 912             .assertSuccess();
 913 
 914         for (String option : new String[]  {"--describe-module", "-d" }) {
 915             jarWithStdin(modularJar.toFile(),
 916                          option)
 917                          .assertSuccess()
 918                          .resultChecker(r ->
 919                              assertTrue(r.output.contains(FOO.moduleName + "@" + newFooVersion),
 920                                 "Expected to find ", FOO.moduleName + "@" + newFooVersion,
 921                                 " in [", r.output, "]")
 922                 );
 923         }
 924     }
 925 
 926     @DataProvider(name = "autoNames")
 927     public Object[][] autoNames() {
 928         return new Object[][] {
 929             // JAR file name                module-name[@version]
 930             { "foo.jar",                    "foo" },
 931             { "foo1.jar",                   "foo1" },
 932             { "foo4j.jar",                  "foo4j", },
 933             { "foo-1.2.3.4.jar",            "foo@1.2.3.4" },
 934             { "foo-bar.jar",                "foo.bar" },
 935             { "foo-1.2-SNAPSHOT.jar",       "foo@1.2-SNAPSHOT" },
 936         };
 937     }
 938 
 939     @Test(dataProvider = "autoNames")
 940     public void describeAutomaticModule(String jarName, String mid)
 941         throws IOException
 942     {
 943         Path mp = Paths.get("describeAutomaticModule");
 944         createTestDir(mp);
 945         Path regularJar = mp.resolve(jarName);
 946         Path t = Paths.get("t");
 947         if (Files.notExists(t))
 948             Files.createFile(t);
 949 
 950         jar("--create",
 951             "--file=" + regularJar.toString(),
 952             t.toString())
 953             .assertSuccess();
 954 
 955         for (String option : new String[]  {"--describe-module", "-d" }) {
 956             jar(option,
 957                 "--file=" + regularJar.toString())
 958                 .assertSuccess()
 959                 .resultChecker(r -> {
 960                     assertTrue(r.output.contains("No module descriptor found"));
 961                     assertTrue(r.output.contains("Derived automatic module"));
 962                     assertTrue(r.output.contains(mid + " automatic"),
 963                                "Expected [", "module " + mid,"] in [", r.output, "]");
 964                     }
 965                 );
 966         }
 967     }
 968 
 969     // -- Infrastructure
 970 
 971     static Result jarWithStdin(File stdinSource, String... args) {
 972         String jar = getJDKTool("jar");
 973         List<String> commands = new ArrayList<>();
 974         commands.add(jar);
 975         if (!TOOL_VM_OPTIONS.isEmpty()) {
 976             commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1)));
 977         }
 978         Stream.of(args).forEach(commands::add);
 979         ProcessBuilder p = new ProcessBuilder(commands);
 980         if (stdinSource != null)
 981             p.redirectInput(stdinSource);
 982         return run(p);
 983     }
 984 
 985     static Result jar(String... args) {
 986         return jarWithStdin(null, args);
 987     }
 988 
 989     static Path compileModule(String mn) throws IOException {
 990         return compileModule(mn, null);
 991     }
 992 
 993     static Path compileModule(String mn, Path mp)
 994         throws IOException
 995     {
 996         Path sourcePath = TEST_SRC.resolve("src").resolve(mn);
 997         Path build = Files.createDirectories(MODULE_CLASSES.resolve(mn));
 998         javac(build, mp, sourceList(sourcePath));
 999         return build;
1000     }
1001 
1002     static void copyResource(Path srcDir, Path dir, String resource)
1003         throws IOException
1004     {
1005         Path dest = dir.resolve(resource);
1006         Files.deleteIfExists(dest);
1007 
1008         Files.createDirectories(dest.getParent());
1009         Files.copy(srcDir.resolve(resource), dest);
1010     }
1011 
1012     static void setupMRJARModuleInfo(String moduleName) throws IOException {
1013         Path modClasses = MODULE_CLASSES.resolve(moduleName);
1014         Path metaInfDir = MRJAR_DIR.resolve(moduleName).resolve("META-INF");
1015         Path versionSection = metaInfDir.resolve("versions").resolve("9");
1016         createTestDir(versionSection);
1017 
1018         Path versionModuleInfo = versionSection.resolve("module-info.class");
1019         System.out.println("copying " + modClasses.resolve("module-info.class") + " to " + versionModuleInfo);
1020         Files.copy(modClasses.resolve("module-info.class"), versionModuleInfo);
1021 
1022         Manifest manifest = new Manifest();
1023         manifest.getMainAttributes().putValue("Manifest-Version", "1.0");
1024         manifest.getMainAttributes().putValue("Multi-Release", "true");
1025         try (OutputStream os = Files.newOutputStream(metaInfDir.resolve("MANIFEST.MF"))) {
1026             manifest.write(os);
1027         }
1028     }
1029 
1030     static ModuleDescriptor getModuleDescriptor(Path jar) {
1031         ClassLoader cl = ClassLoader.getSystemClassLoader();
1032         try (JarFile jf = new JarFile(jar.toFile())) {
1033             JarEntry entry = jf.getJarEntry("module-info.class");
1034             try (InputStream in = jf.getInputStream(entry)) {
1035                 return ModuleDescriptor.read(in);
1036             }
1037         } catch (IOException ioe) {
1038             throw new UncheckedIOException(ioe);
1039         }
1040     }
1041 
1042     // Re-enable when there is support in javax.tools for module path
1043 //    static void javac(Path dest, Path... sourceFiles) throws IOException {
1044 //        out.printf("Compiling %d source files %s%n", sourceFiles.length,
1045 //                   Arrays.asList(sourceFiles));
1046 //        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
1047 //        try (StandardJavaFileManager fileManager =
1048 //                     compiler.getStandardFileManager(null, null, null)) {
1049 //
1050 //            List<File> files = Stream.of(sourceFiles)
1051 //                                     .map(p -> p.toFile())
1052 //                                     .collect(Collectors.toList());
1053 //            List<File> dests = Stream.of(dest)
1054 //                                     .map(p -> p.toFile())
1055 //                                     .collect(Collectors.toList());
1056 //            Iterable<? extends JavaFileObject> compilationUnits =
1057 //                    fileManager.getJavaFileObjectsFromFiles(files);
1058 //            fileManager.setLocation(StandardLocation.CLASS_OUTPUT, dests);
1059 //            JavaCompiler.CompilationTask task =
1060 //                    compiler.getTask(null, fileManager, null, null, null, compilationUnits);
1061 //            boolean passed = task.call();
1062 //            if (!passed)
1063 //                throw new RuntimeException("Error compiling " + files);
1064 //        }
1065 //    }
1066 
1067     static void javac(Path dest, Path... sourceFiles) throws IOException {
1068         javac(dest, null, sourceFiles);
1069     }
1070 
1071     static void javac(Path dest, Path modulePath, Path... sourceFiles)
1072         throws IOException
1073     {
1074         String javac = getJDKTool("javac");
1075 
1076         List<String> commands = new ArrayList<>();
1077         commands.add(javac);
1078         if (!TOOL_VM_OPTIONS.isEmpty()) {
1079             commands.addAll(Arrays.asList(TOOL_VM_OPTIONS.split("\\s+", -1)));
1080         }
1081         commands.add("-d");
1082         commands.add(dest.toString());
1083         if (dest.toString().contains("bar")) {
1084             commands.add("--add-exports");
1085             commands.add("java.base/jdk.internal.misc=bar");
1086             commands.add("--add-exports");
1087             commands.add("java.base/jdk.internal.module=bar");
1088         }
1089         if (modulePath != null) {
1090             commands.add("--module-path");
1091             commands.add(modulePath.toString());
1092         }
1093         Stream.of(sourceFiles).map(Object::toString).forEach(x -> commands.add(x));
1094 
1095         quickFail(run(new ProcessBuilder(commands)));
1096     }
1097 
1098     static Result java(Path modulePath, String entryPoint, String... args) {
1099         String java = getJDKTool("java");
1100 
1101         List<String> commands = new ArrayList<>();
1102         commands.add(java);
1103         if (!VM_OPTIONS.isEmpty()) {
1104             commands.addAll(Arrays.asList(VM_OPTIONS.split("\\s+", -1)));
1105         }
1106         if (!JAVA_OPTIONS.isEmpty()) {
1107             commands.addAll(Arrays.asList(JAVA_OPTIONS.split("\\s+", -1)));
1108         }
1109         Stream.of(args).forEach(x -> commands.add(x));
1110         commands.add("--module-path");
1111         commands.add(modulePath.toString());
1112         commands.add("-m");
1113         commands.add(entryPoint);
1114 
1115         return run(new ProcessBuilder(commands));
1116     }
1117 
1118     static Path[] sourceList(Path directory) throws IOException {
1119         return Files.find(directory, Integer.MAX_VALUE,
1120                           (file, attrs) -> (file.toString().endsWith(".java")))
1121                     .toArray(Path[]::new);
1122     }
1123 
1124     static void createTestDir(Path p) throws IOException{
1125         if (Files.exists(p))
1126             FileUtils.deleteFileTreeWithRetry(p);
1127         Files.createDirectories(p);
1128     }
1129 
1130     static boolean jarContains(JarInputStream jis, String entryName)
1131         throws IOException
1132     {
1133         JarEntry e;
1134         while((e = jis.getNextJarEntry()) != null) {
1135             if (e.getName().equals(entryName))
1136                 return true;
1137         }
1138         return false;
1139     }
1140 
1141     static void quickFail(Result r) {
1142         if (r.ec != 0)
1143             throw new RuntimeException(r.output);
1144     }
1145 
1146     static Result run(ProcessBuilder pb) {
1147         Process p;
1148         out.printf("Running: %s%n", pb.command());
1149         try {
1150             p = pb.start();
1151         } catch (IOException e) {
1152             throw new RuntimeException(
1153                     format("Couldn't start process '%s'", pb.command()), e);
1154         }
1155 
1156         String output;
1157         try {
1158             output = toString(p.getInputStream(), p.getErrorStream());
1159         } catch (IOException e) {
1160             throw new RuntimeException(
1161                     format("Couldn't read process output '%s'", pb.command()), e);
1162         }
1163 
1164         try {
1165             p.waitFor();
1166         } catch (InterruptedException e) {
1167             throw new RuntimeException(
1168                     format("Process hasn't finished '%s'", pb.command()), e);
1169         }
1170         return new Result(p.exitValue(), output);
1171     }
1172 
1173     static final String DEFAULT_IMAGE_BIN = System.getProperty("java.home")
1174             + File.separator + "bin" + File.separator;
1175 
1176     static String getJDKTool(String name) {
1177         try {
1178             return JDKToolFinder.getJDKTool(name);
1179         } catch (Exception x) {
1180             return DEFAULT_IMAGE_BIN + name;
1181         }
1182     }
1183 
1184     static String toString(InputStream in1, InputStream in2) throws IOException {
1185         try (ByteArrayOutputStream dst = new ByteArrayOutputStream();
1186              InputStream concatenated = new SequenceInputStream(in1, in2)) {
1187             concatenated.transferTo(dst);
1188             return new String(dst.toByteArray(), "UTF-8");
1189         }
1190     }
1191 
1192     static class Result {
1193         final int ec;
1194         final String output;
1195 
1196         private Result(int ec, String output) {
1197             this.ec = ec;
1198             this.output = output;
1199         }
1200         Result assertSuccess() {
1201             assertTrue(ec == 0, "Expected ec 0, got: ", ec, " , output [", output, "]");
1202             return this;
1203         }
1204         Result assertFailure() {
1205             assertTrue(ec != 0, "Expected ec != 0, got:", ec, " , output [", output, "]");
1206             return this;
1207         }
1208         Result resultChecker(Consumer<Result> r) { r.accept(this); return this; }
1209     }
1210 
1211     static void assertTrue(boolean cond, Object ... failedArgs) {
1212         if (cond)
1213             return;
1214         StringBuilder sb = new StringBuilder();
1215         for (Object o : failedArgs)
1216             sb.append(o);
1217         org.testng.Assert.assertTrue(false, sb.toString());
1218     }
1219 
1220     // Standalone entry point.
1221     public static void main(String[] args) throws Throwable {
1222         Basic test = new Basic();
1223         test.compileModules();
1224         for (Method m : Basic.class.getDeclaredMethods()) {
1225             if (m.getAnnotation(Test.class) != null) {
1226                 System.out.println("Invoking " + m.getName());
1227                 m.invoke(test);
1228             }
1229         }
1230     }
1231 }