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