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