1 /**
   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.
   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 jdk.compiler
  28  *          jdk.jlink
  29  * @build jdk.test.lib.compiler.CompilerUtils
  30  * @run testng JmodNegativeTest
  31  * @summary Negative tests for jmod
  32  */
  33 
  34 import java.io.*;
  35 import java.nio.file.Files;
  36 import java.nio.file.Path;
  37 import java.nio.file.Paths;
  38 import java.util.Arrays;
  39 import java.util.List;
  40 import java.util.function.Consumer;
  41 import java.util.function.Supplier;
  42 import java.util.spi.ToolProvider;
  43 import java.util.zip.ZipOutputStream;
  44 import jdk.test.lib.util.FileUtils;
  45 import jdk.test.lib.compiler.CompilerUtils;
  46 import org.testng.annotations.BeforeTest;
  47 import org.testng.annotations.DataProvider;
  48 import org.testng.annotations.Test;
  49 
  50 import static java.io.File.pathSeparator;
  51 import static java.nio.charset.StandardCharsets.UTF_8;
  52 import static org.testng.Assert.assertTrue;
  53 
  54 public class JmodNegativeTest {
  55 
  56     static final ToolProvider JMOD_TOOL = ToolProvider.findFirst("jmod")
  57         .orElseThrow(() ->
  58             new RuntimeException("jmod tool not found")
  59         );
  60 
  61     static final String TEST_SRC = System.getProperty("test.src", ".");
  62     static final Path SRC_DIR = Paths.get(TEST_SRC, "src");
  63     static final Path EXPLODED_DIR = Paths.get("build");
  64     static final Path MODS_DIR = Paths.get("jmods");
  65 
  66     @BeforeTest
  67     public void buildExplodedModules() throws IOException {
  68         if (Files.exists(EXPLODED_DIR))
  69             FileUtils.deleteFileTreeWithRetry(EXPLODED_DIR);
  70 
  71         for (String name : new String[] { "foo"/*, "bar", "baz"*/ } ) {
  72             Path dir = EXPLODED_DIR.resolve(name);
  73             assertTrue(compileModule(name, dir.resolve("classes")));
  74         }
  75 
  76         if (Files.exists(MODS_DIR))
  77             FileUtils.deleteFileTreeWithRetry(MODS_DIR);
  78         Files.createDirectories(MODS_DIR);
  79     }
  80 
  81     @Test
  82     public void testNoArgs() {
  83         jmod()
  84             .assertFailure()
  85             .resultChecker(r ->
  86                 assertContains(r.output, "Error: one of create, extract, list, describe, or hash must be specified")
  87             );
  88     }
  89 
  90     @Test
  91     public void testBadAction() {
  92         jmod("badAction")
  93             .assertFailure()
  94             .resultChecker(r ->
  95                 assertContains(r.output, "Error: mode must be one of create, extract, list, describe, or hash")
  96             );
  97 
  98         jmod("--badOption")
  99             .assertFailure()
 100             .resultChecker(r ->
 101                 assertContains(r.output, "Error: 'badOption' is not a recognized option")
 102             );
 103     }
 104 
 105     @Test
 106     public void testTooManyArgs() throws IOException {
 107         Path jmod = MODS_DIR.resolve("doesNotExist.jmod");
 108         FileUtils.deleteFileIfExistsWithRetry(jmod);
 109 
 110         jmod("create",
 111              jmod.toString(),
 112              "AAA")
 113             .assertFailure()
 114             .resultChecker(r ->
 115                 assertContains(r.output, "Error: unknown option(s): [AAA]")
 116             );
 117     }
 118 
 119     @Test
 120     public void testCreateNoArgs() {
 121         jmod("create")
 122             .assertFailure()
 123             .resultChecker(r ->
 124                 assertContains(r.output, "Error: jmod-file must be specified")
 125             );
 126     }
 127 
 128     @Test
 129     public void testListNoArgs() {
 130         jmod("list")
 131             .assertFailure()
 132             .resultChecker(r ->
 133                 assertContains(r.output, "Error: jmod-file must be specified")
 134             );
 135     }
 136 
 137     @Test
 138     public void testListFileDoesNotExist() throws IOException {
 139         Path jmod = MODS_DIR.resolve("doesNotExist.jmod");
 140         FileUtils.deleteFileIfExistsWithRetry(jmod);
 141 
 142         jmod("list",
 143              jmod.toString())
 144             .assertFailure()
 145             .resultChecker(r ->
 146                 assertContains(r.output, "Error: no jmod file found: "
 147                         + jmod.toString())
 148             );
 149     }
 150 
 151     @Test
 152     public void testListJmodIsDir() throws IOException {
 153         Path jmod = MODS_DIR.resolve("testListJmodIsDir.jmod");
 154         if (Files.notExists(jmod))
 155             Files.createDirectory(jmod);
 156 
 157         jmod("list",
 158              jmod.toString())
 159             .assertFailure()
 160             .resultChecker(r ->
 161                 assertContains(r.output, "Error: error opening jmod file")
 162             );
 163     }
 164 
 165     @Test
 166     public void testlistJmodMalformed() throws IOException {
 167         Path jmod = MODS_DIR.resolve("testlistJmodMalformed.jmod");
 168         if (Files.notExists(jmod))
 169             Files.createFile(jmod);
 170 
 171         jmod("list",
 172              jmod.toString())
 173             .assertFailure()
 174             .resultChecker(r ->
 175                 assertContains(r.output, "Error: error opening jmod file")
 176             );
 177     }
 178 
 179     @Test
 180     public void testHashModulesModulePathNotSpecified() {
 181         jmod("create",
 182              "--hash-modules", "anyPattern.*",
 183              "output.jmod")
 184             .assertFailure()
 185             .resultChecker(r ->
 186                 assertContains(r.output, "Error: --module-path must be "
 187                         +"specified when hashing modules")
 188             );
 189     }
 190 
 191     @Test
 192     public void testCreateJmodAlreadyExists() throws IOException {
 193         Path jmod = MODS_DIR.resolve("testCreateJmodAlreadyExists.jmod");
 194         if (Files.notExists(jmod))
 195             Files.createFile(jmod);
 196 
 197         jmod("create",
 198              "--class-path", Paths.get(".").toString(), // anything that exists
 199              jmod.toString())
 200             .assertFailure()
 201             .resultChecker(r ->
 202                 assertContains(r.output, "Error: file already exists: " + jmod.toString())
 203             );
 204     }
 205 
 206     @Test
 207     public void testCreateJmodIsDir() throws IOException {
 208         Path jmod = MODS_DIR.resolve("testCreateJmodAlreadyExists");
 209         if (Files.notExists(jmod))
 210             Files.createDirectory(jmod);
 211 
 212         jmod("create",
 213              "--class-path", Paths.get(".").toString(), // anything that exists
 214              jmod.toString())
 215             .assertFailure()
 216             .resultChecker(r ->
 217                 assertContains(r.output, "Error: file already exists: " + jmod.toString())
 218             );
 219     }
 220 
 221     @Test
 222     public void testInvalidModuleVersion() throws IOException {
 223         Path jmod = MODS_DIR.resolve("testEmptyModuleVersion.jmod");
 224         FileUtils.deleteFileIfExistsWithRetry(jmod);
 225         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 226 
 227         for (String version : new String[] { "", "NOT_A_VALID_VERSION" }) {
 228             jmod("create",
 229                  "--class-path", cp,
 230                  "--module-version", version,
 231                  jmod.toString())
 232                 .assertFailure()
 233                 .resultChecker(r ->
 234                     assertContains(r.output, "Error: invalid module version")
 235                 );
 236         }
 237     }
 238 
 239     @Test
 240     public void testEmptyFileInClasspath() throws IOException {
 241         Path jmod = MODS_DIR.resolve("testEmptyFileInClasspath.jmod");
 242         FileUtils.deleteFileIfExistsWithRetry(jmod);
 243         Path jar = MODS_DIR.resolve("NotARealJar_Empty.jar");
 244         FileUtils.deleteFileIfExistsWithRetry(jar);
 245         Files.createFile(jar);
 246 
 247         jmod("create",
 248              "--class-path", jar.toString(),
 249              jmod.toString())
 250             .assertFailure()
 251             .resultChecker(r ->
 252                 assertContains(r.output, "Error: module-info.class not found")
 253             );
 254     }
 255 
 256     @Test
 257     public void testEmptyJarInClasspath() throws IOException {
 258         Path jmod = MODS_DIR.resolve("testEmptyJarInClasspath.jmod");
 259         FileUtils.deleteFileIfExistsWithRetry(jmod);
 260         Path jar = MODS_DIR.resolve("empty.jar");
 261         FileUtils.deleteFileIfExistsWithRetry(jar);
 262         try (FileOutputStream fos = new FileOutputStream(jar.toFile());
 263              ZipOutputStream zos = new ZipOutputStream(fos)) {
 264             // empty
 265         }
 266 
 267         jmod("create",
 268              "--class-path", jar.toString(),
 269              jmod.toString())
 270             .assertFailure()
 271             .resultChecker(r ->
 272                 assertContains(r.output, "Error: module-info.class not found")
 273             );
 274     }
 275 
 276     @Test
 277     public void testModuleInfoNotFound() throws IOException {
 278         Path jmod = MODS_DIR.resolve("output.jmod");
 279         FileUtils.deleteFileIfExistsWithRetry(jmod);
 280         Path jar = MODS_DIR.resolve("empty");
 281         FileUtils.deleteFileIfExistsWithRetry(jar);
 282         Files.createDirectory(jar);
 283 
 284         jmod("create",
 285              "--class-path", jar.toString(),
 286              jmod.toString())
 287             .assertFailure()
 288             .resultChecker(r ->
 289                 assertContains(r.output, "Error: module-info.class not found")
 290             );
 291     }
 292 
 293     @Test
 294     public void testModuleInfoIsDir() throws IOException {
 295         Path jmod = MODS_DIR.resolve("output.jmod");
 296         FileUtils.deleteFileIfExistsWithRetry(jmod);
 297         Path cp = MODS_DIR.resolve("module-info.class");
 298         FileUtils.deleteFileIfExistsWithRetry(cp);
 299         Files.createDirectory(cp);
 300         Files.createFile(cp.resolve("nada.txt"));
 301 
 302         jmod("create",
 303              "--class-path", cp.toString(),
 304              jmod.toString())
 305             .assertFailure()
 306             .resultChecker(r ->
 307                 assertContains(r.output, "Error: module-info.class not found")
 308             );
 309     }
 310 
 311     @Test
 312     public void testNoModuleHash() throws IOException {
 313         Path jmod = MODS_DIR.resolve("output.jmod");
 314         FileUtils.deleteFileIfExistsWithRetry(jmod);
 315         Path emptyDir = Paths.get("empty");
 316         if (Files.exists(emptyDir))
 317             FileUtils.deleteFileTreeWithRetry(emptyDir);
 318         Files.createDirectory(emptyDir);
 319         String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 320 
 321         jmod("create",
 322              "--class-path", cp,
 323              "--hash-modules", ".*",
 324              "--module-path", emptyDir.toString(),
 325             jmod.toString())
 326             .resultChecker(r ->
 327                 assertContains(r.output, "No hashes recorded: " +
 328                     "no module specified for hashing depends on foo")
 329             );
 330     }
 331 
 332     @Test
 333     public void testEmptyFileInModulePath() throws IOException {
 334         Path jmod = MODS_DIR.resolve("output.jmod");
 335         FileUtils.deleteFileIfExistsWithRetry(jmod);
 336         Path empty = MODS_DIR.resolve("emptyFile.jmod");
 337         FileUtils.deleteFileIfExistsWithRetry(empty);
 338         Files.createFile(empty);
 339         try {
 340             String cp = EXPLODED_DIR.resolve("foo").resolve("classes").toString();
 341 
 342             jmod("create",
 343                  "--class-path", cp,
 344                  "--hash-modules", ".*",
 345                  "--module-path", MODS_DIR.toString(),
 346                  jmod.toString())
 347                 .assertFailure();
 348         } finally {
 349             FileUtils.deleteFileWithRetry(empty);
 350         }
 351     }
 352 
 353     @Test
 354     public void testFileInModulePath() throws IOException {
 355         Path jmod = MODS_DIR.resolve("output.jmod");
 356         FileUtils.deleteFileIfExistsWithRetry(jmod);
 357         Path file = MODS_DIR.resolve("testFileInModulePath.txt");
 358         FileUtils.deleteFileIfExistsWithRetry(file);
 359         Files.createFile(file);
 360 
 361         jmod("create",
 362              "--hash-modules", ".*",
 363              "--module-path", file.toString(),
 364              jmod.toString())
 365             .assertFailure()
 366             .resultChecker(r ->
 367                 assertContains(r.output, "Error: path must be a directory")
 368             );
 369     }
 370 
 371     @DataProvider(name = "pathDoesNotExist")
 372     public Object[][] pathDoesNotExist() throws IOException {
 373         Path jmod = MODS_DIR.resolve("output.jmod");
 374         FileUtils.deleteFileIfExistsWithRetry(jmod);
 375         FileUtils.deleteFileIfExistsWithRetry(Paths.get("doesNotExist"));
 376 
 377         List<Supplier<JmodResult>> tasks = Arrays.asList(
 378                 () -> jmod("create",
 379                            "--hash-modules", "anyPattern",
 380                            "--module-path", "doesNotExist",
 381                            "output.jmod"),
 382                 () -> jmod("create",
 383                            "--class-path", "doesNotExist",
 384                            "output.jmod"),
 385                 () -> jmod("create",
 386                            "--class-path", "doesNotExist.jar",
 387                            "output.jmod"),
 388                 () -> jmod("create",
 389                            "--cmds", "doesNotExist",
 390                            "output.jmod"),
 391                 () -> jmod("create",
 392                            "--config", "doesNotExist",
 393                            "output.jmod"),
 394                 () -> jmod("create",
 395                            "--libs", "doesNotExist",
 396                            "output.jmod") );
 397 
 398         String errMsg = "Error: path not found: doesNotExist";
 399         return tasks.stream().map(t -> new Object[] {t, errMsg} )
 400                              .toArray(Object[][]::new);
 401     }
 402 
 403     @Test(dataProvider = "pathDoesNotExist")
 404     public void testPathDoesNotExist(Supplier<JmodResult> supplier,
 405                                      String errMsg)
 406     {
 407         supplier.get()
 408                 .assertFailure()
 409                 .resultChecker(r -> {
 410                     assertContains(r.output, errMsg);
 411                 });
 412     }
 413 
 414     @DataProvider(name = "partOfPathDoesNotExist")
 415     public Object[][] partOfPathDoesNotExist() throws IOException {
 416         Path jmod = MODS_DIR.resolve("output.jmod");
 417         FileUtils.deleteFileIfExistsWithRetry(jmod);
 418         FileUtils.deleteFileIfExistsWithRetry(Paths.get("doesNotExist"));
 419 
 420         Path emptyDir = Paths.get("empty");
 421         if (Files.exists(emptyDir))
 422             FileUtils.deleteFileTreeWithRetry(emptyDir);
 423         Files.createDirectory(emptyDir);
 424 
 425         List<Supplier<JmodResult>> tasks = Arrays.asList(
 426             () -> jmod("create",
 427                        "--hash-modules", "anyPattern",
 428                        "--module-path","empty" + pathSeparator + "doesNotExist",
 429                        "output.jmod"),
 430             () -> jmod("create",
 431                        "--class-path", "empty" + pathSeparator + "doesNotExist",
 432                        "output.jmod"),
 433             () -> jmod("create",
 434                        "--class-path", "empty" + pathSeparator + "doesNotExist.jar",
 435                        "output.jmod"),
 436             () -> jmod("create",
 437                        "--cmds", "empty" + pathSeparator + "doesNotExist",
 438                        "output.jmod"),
 439             () -> jmod("create",
 440                        "--config", "empty" + pathSeparator + "doesNotExist",
 441                        "output.jmod"),
 442             () -> jmod("create",
 443                        "--libs", "empty" + pathSeparator + "doesNotExist",
 444                        "output.jmod") );
 445 
 446         String errMsg = "Error: path not found: doesNotExist";
 447         return tasks.stream().map(t -> new Object[] {t, errMsg} )
 448                              .toArray(Object[][]::new);
 449     }
 450 
 451     @Test(dataProvider = "partOfPathDoesNotExist")
 452     public void testPartOfPathNotExist(Supplier<JmodResult> supplier,
 453                                        String errMsg)
 454     {
 455         supplier.get()
 456                 .assertFailure()
 457                 .resultChecker(r -> {
 458                     assertContains(r.output, errMsg);
 459                 });
 460     }
 461 
 462     @DataProvider(name = "pathIsFile")
 463     public Object[][] pathIsFile() throws IOException {
 464         Path jmod = MODS_DIR.resolve("output.jmod");
 465         FileUtils.deleteFileIfExistsWithRetry(jmod);
 466         Path aFile = Paths.get("aFile.txt");
 467         if (Files.exists(aFile) && !Files.isRegularFile(aFile))
 468             throw new InternalError("Unexpected file:" + aFile);
 469         else
 470             Files.createFile(aFile);
 471 
 472         List<Supplier<JmodResult>> tasks = Arrays.asList(
 473                 () -> jmod("create",
 474                            "--class-path", "aFile.txt",
 475                            "output.jmod"),
 476                 () -> jmod("create",
 477                            "--module-path", "aFile.txt",
 478                            "output.jmod"),
 479                 () -> jmod("create",
 480                            "--cmds", "aFile.txt",
 481                            "output.jmod"),
 482                 () -> jmod("create",
 483                            "--config", "aFile.txt",
 484                            "output.jmod"),
 485                 () -> jmod("create",
 486                            "--libs", "aFile.txt",
 487                            "output.jmod") );
 488 
 489         String errMsg = "Error: path must be a directory: aFile.txt";
 490         Object[][] a = tasks.stream().map(t -> new Object[] {t, errMsg} )
 491                                      .toArray(Object[][]::new);
 492         a[0][1] = "invalid class path entry: aFile.txt";  // class path err msg
 493         return a;
 494     }
 495 
 496     @Test(dataProvider = "pathIsFile")
 497     public void testPathIsFile(Supplier<JmodResult> supplier,
 498                                String errMsg)
 499     {
 500         supplier.get()
 501                 .assertFailure()
 502                 .resultChecker(r -> {
 503                     assertContains(r.output, errMsg);
 504                 });
 505     }
 506 
 507     // ---
 508 
 509     static boolean compileModule(String name, Path dest) throws IOException {
 510         return CompilerUtils.compile(SRC_DIR.resolve(name), dest);
 511     }
 512 
 513     static void assertContains(String output, String subString) {
 514         if (output.contains(subString))
 515             assertTrue(true);
 516         else
 517             assertTrue(false,"Expected to find [" + subString + "], in output ["
 518                              + output + "]");
 519     }
 520 
 521     static JmodResult jmod(String... args) {
 522         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 523         PrintStream ps = new PrintStream(baos);
 524         System.out.println("jmod " + Arrays.asList(args));
 525         int ec = JMOD_TOOL.run(ps, ps, args);
 526         return new JmodResult(ec, new String(baos.toByteArray(), UTF_8));
 527     }
 528 
 529     static class JmodResult {
 530         final int exitCode;
 531         final String output;
 532 
 533         JmodResult(int exitValue, String output) {
 534             this.exitCode = exitValue;
 535             this.output = output;
 536         }
 537         JmodResult assertFailure() { assertTrue(exitCode != 0, output); return this; }
 538         JmodResult resultChecker(Consumer<JmodResult> r) { r.accept(this); return this; }
 539     }
 540 }