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