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