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