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