1 /*
   2  * Copyright (c) 2012, 2018, 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  * @build FXLauncherTest jdk.test.lib.compiler.CompilerUtils
  28  * @bug 8001533 8004547 8035782 8202553
  29  * @summary Test launching FX application with java -jar
  30  * Test uses main method and blank main method, a jfx app class and an incorrect
  31  * jfx app class, a main-class for the manifest, a bogus one and none.
  32  * Now that FX is no longer bundled with the JDK, this test uses a
  33  * "mock" javafx.graphics module to test the FX launcher. It also verifies
  34  * that FX is, in fact, not included with the JDK.
  35  * All should execute except the incorrect fx app class entries.
  36  * @run main/othervm FXLauncherTest
  37  */
  38 import java.io.File;
  39 import java.nio.file.Path;
  40 import java.nio.file.Paths;
  41 import java.io.IOException;
  42 import java.util.ArrayList;
  43 import java.util.List;
  44 
  45 import jdk.test.lib.compiler.CompilerUtils;
  46 
  47 public class FXLauncherTest extends TestHelper {
  48     private static final String FX_MARKER_CLASS = "javafx.application.Application";
  49     private static void line() {
  50         System.out.println("_____________________________________________");
  51     }
  52     private static File MainJavaFile = null;
  53     private static final File FXtestJar =  new File("fxtest.jar");
  54     private static final File ManifestFile = new File("manifest.txt");
  55     private static final File ScratchDir = new File(".");
  56 
  57     private static final String TEST_SRC = System.getProperty("test.src");
  58     private static final Path SRC_DIR = Paths.get(TEST_SRC, "mockfx/src");
  59     private static final Path MODS_DIR = Paths.get("mods");
  60     private static String moduleDir = MODS_DIR.toString();
  61 
  62     /* standard main class can be used as java main for fx app class */
  63     static final String StdMainClass = "helloworld.HelloWorld";
  64     static final String ExtMainClass = "helloworld.ExtHello";
  65     static final String NonFXMainClass = "helloworld.HelloJava";
  66     static int testcount = 0;
  67 
  68     /* a main method and a blank. */
  69     static final String[] MAIN_METHODS = {
  70         "public static void main(String[] args) { launch(args); }",
  71         " "
  72     };
  73 
  74     // Array of parameters to pass to fx application.
  75     static final String[] APP_PARMS = { "one", "two" };
  76 
  77     // Create fx java file for test application
  78     static void createJavaFile(String mainmethod) {
  79         try {
  80             String mainClass = "HelloWorld";
  81             List<String> contents = new ArrayList<>();
  82             contents.add("package helloworld;");
  83             contents.add("import javafx.application.Application;");
  84             contents.add("import javafx.stage.Stage;");
  85             contents.add("public class HelloWorld extends Application {");
  86             contents.add(mainmethod);
  87             contents.add("@Override");
  88             contents.add("public void start(Stage primaryStage) {");
  89             contents.add("//    primaryStage.show(); no GUI for auto tests. ");
  90             contents.add("    System.out.println(\"HelloWorld.primaryStage.show();\");");
  91             contents.add("    System.out.println(\"Parameters:\");" );
  92             contents.add("    for(String p : getParameters().getUnnamed())");
  93             contents.add("        System.out.println(\"parameter: \" + p );" );
  94             contents.add("    System.exit(0);");
  95             contents.add("}");
  96             contents.add("}");
  97 
  98             // Create and compile java source.
  99             MainJavaFile = new File(mainClass + JAVA_FILE_EXT);
 100             createFile(MainJavaFile, contents);
 101             doFxCompile("-d", ".", mainClass + JAVA_FILE_EXT);
 102         } catch (java.io.IOException ioe) {
 103             ioe.printStackTrace();
 104             throw new RuntimeException("Failed creating HelloWorld.");
 105         }
 106     }
 107 
 108     /*
 109      * Create class that extends HelloWorld instead of Application
 110      */
 111     static void createExtJavaFile(String mainmethod) {
 112         try {
 113             String mainClass = "ExtHello";
 114             List<String> contents = new ArrayList<>();
 115             contents.add("package helloworld;");
 116             contents.add("public class ExtHello extends HelloWorld {");
 117             contents.add(mainmethod);
 118             contents.add("}");
 119             // Create and compile java source.
 120             MainJavaFile = new File(mainClass + JAVA_FILE_EXT);
 121             createFile(MainJavaFile, contents);
 122             doFxCompile("-cp", ".", "-d", ".", mainClass + JAVA_FILE_EXT);
 123         } catch (java.io.IOException ioe) {
 124             ioe.printStackTrace();
 125             throw new RuntimeException("Failed creating ExtHello.");
 126         }
 127     }
 128 
 129     /*
 130      * Create non-JavaFX class for testing if jfxrt.jar is being loaded
 131      * when it shouldn't be
 132      */
 133     static void createNonFXJavaFile() {
 134         try {
 135             String mainClass = "HelloJava";
 136             List<String> contents = new ArrayList<>();
 137             contents.add("package helloworld;");
 138             contents.add("public class HelloJava {");
 139             contents.add("    public static void main(String[] args) {");
 140             contents.add("        for(String aa : args)");
 141             contents.add("            System.out.println(\"arg: \" + aa);" );
 142             contents.add("    }");
 143             contents.add("}");
 144             // Create and compile java source.
 145             MainJavaFile = new File(mainClass + JAVA_FILE_EXT);
 146             createFile(MainJavaFile, contents);
 147             doFxCompile("-cp", ".", "-d", ".", mainClass + JAVA_FILE_EXT);
 148         } catch (java.io.IOException ioe) {
 149             ioe.printStackTrace();
 150             throw new RuntimeException("Failed creating HelloJava.");
 151         }
 152     }
 153 
 154     // Create manifest for test fx application
 155     static List<String> createManifestContents(String mainClassEntry, String fxMainEntry) {
 156         List<String> mcontents = new ArrayList<>();
 157         mcontents.add("Manifest-Version: 1.0");
 158         mcontents.add("Created-By: FXLauncherTest");
 159         if (mainClassEntry != null) {
 160             mcontents.add("Main-Class: " + mainClassEntry);
 161             System.out.println("Main-Class: " + mainClassEntry);
 162         }
 163         if (fxMainEntry != null) {
 164             mcontents.add("JavaFX-Application-Class: " + fxMainEntry);
 165             System.out.println("JavaFX-Application-Class: " + fxMainEntry);
 166         }
 167         return mcontents;
 168     }
 169 
 170     // Method to marshal createJar to TestHelper.createJar()
 171     static void createJar(File theJar, File manifestFile) {
 172         createJar("cvmf", manifestFile.getName(),
 173                   theJar.getAbsolutePath(), "helloworld");
 174     }
 175 
 176     static void saveFile(String tname, int testcount, File srcFile) {
 177         File newFile = new File(tname + "-" + testcount + "-" + srcFile.getName());
 178         System.out.println("renaming " + srcFile.getName() +
 179                            " to " + newFile.getName());
 180         srcFile.renameTo(newFile);
 181     }
 182 
 183     static void cleanupFiles() throws IOException {
 184         for(File f : ScratchDir.listFiles()) {
 185             recursiveDelete(f);
 186         }
 187     }
 188 
 189     static void checkStatus(TestResult tr, String testName, int testCount,
 190                             String mainclass) throws Exception {
 191         if (tr.testStatus) {
 192             System.out.println("PASS: " + testName + ":" + testCount +
 193                                " : test with " + mainclass);
 194             cleanupFiles();
 195         } else {
 196             saveFile(testName, testcount, FXtestJar);
 197             System.out.println("FAIL: " + testName + ":" + testCount +
 198                                " : test with " + mainclass);
 199             cleanupFiles();
 200             System.err.println(tr);
 201             throw new Exception("Failed: " + testName + ":" + testCount);
 202         }
 203     }
 204 
 205     public static void compileFXModule() {
 206         final String JAVAFX_GRAPHICS_MODULE = "javafx.graphics";
 207 
 208         try {
 209             // javac -d mods/javafx.graphics mockfx/src/javafx.graphics/**
 210             boolean compiled
 211                 = CompilerUtils.compile(SRC_DIR.resolve(JAVAFX_GRAPHICS_MODULE),
 212                                         MODS_DIR.resolve(JAVAFX_GRAPHICS_MODULE));
 213 
 214             if (!compiled) {
 215                 throw new RuntimeException("Error compiling mock javafx.graphics module");
 216             }
 217         } catch (IOException ioe) {
 218             throw new RuntimeException(ioe);
 219         }
 220     }
 221 
 222     static void doFxCompile(String...compilerArgs) {
 223         compileFXModule();
 224 
 225         String[] fxCompilerArgs = new String[compilerArgs.length + 2];
 226         fxCompilerArgs[0] = "--module-path=" + moduleDir;
 227         fxCompilerArgs[1] = "--add-modules=javafx.graphics";
 228         System.arraycopy(compilerArgs, 0, fxCompilerArgs, 2, compilerArgs.length);
 229         compile(fxCompilerArgs);
 230     }
 231 
 232     static TestResult doFxExec(String...cmds) {
 233         String[] fxCmds = new String[cmds.length + 2];
 234         fxCmds[0] = cmds[0];
 235         fxCmds[1] = "--module-path=" + moduleDir;
 236         fxCmds[2] = "--add-modules=javafx.graphics";
 237         System.arraycopy(cmds, 1, fxCmds, 3, cmds.length - 1);
 238         return doExec(fxCmds);
 239     }
 240 
 241     /*
 242      * Set Main-Class and iterate main_methods.
 243      * Try launching with both -jar and -cp methods, with and without FX main
 244      * class manifest entry.
 245      * All cases should run.
 246      *
 247      * See sun.launcher.LauncherHelper$FXHelper for more details on how JavaFX
 248      * applications are launched.
 249      */
 250     @Test
 251     static void testBasicFXApp() throws Exception {
 252         testBasicFXApp(true, false);    // -cp, no JAC
 253         testBasicFXApp(false, true);    // -jar, with JAC
 254         testBasicFXApp(false, false);   // -jar, no JAC
 255     }
 256 
 257     static void testBasicFXApp(boolean useCP, boolean setFXMainClass) throws Exception {
 258         String testname = "testBasicFXApp";
 259         if (useCP) {
 260             testname = testname.concat("_useCP");
 261         }
 262         String fxMC = StdMainClass;
 263         if (!setFXMainClass) {
 264             testname = testname.concat("_noJAC");
 265             fxMC = null;
 266         }
 267         for (String mm : MAIN_METHODS) {
 268             testcount++;
 269             line();
 270             System.out.println("test# " + testcount + "-  Main method: " + mm);
 271             createJavaFile(mm);
 272             createFile(ManifestFile, createManifestContents(StdMainClass, fxMC));
 273             createJar(FXtestJar, ManifestFile);
 274             String sTestJar = FXtestJar.getAbsolutePath();
 275             final TestResult tr;
 276             if (useCP) {
 277                 tr = doFxExec(javaCmd, "-cp", sTestJar, StdMainClass, APP_PARMS[0], APP_PARMS[1]);
 278             } else {
 279                 tr = doFxExec(javaCmd, "-jar", sTestJar, APP_PARMS[0], APP_PARMS[1]);
 280             }
 281             tr.checkPositive();
 282             if (tr.testStatus && tr.contains("HelloWorld.primaryStage.show()")) {
 283                 for (String p : APP_PARMS) {
 284                     if (!tr.contains(p)) {
 285                         System.err.println("ERROR: Did not find "
 286                                 + p + " in output!");
 287                     }
 288                 }
 289             }
 290             checkStatus(tr, testname, testcount, StdMainClass);
 291         }
 292     }
 293 
 294     /*
 295      * Set Main-Class and iterate main methods.
 296      * Main class extends another class that extends Application.
 297      * Try launching with both -jar and -cp methods.
 298      * All cases should run.
 299      */
 300     @Test
 301     static void testExtendFXApp() throws Exception {
 302         testExtendFXApp(true, false);   // -cp, no JAC
 303         testExtendFXApp(false, true);   // -jar, with JAC
 304         testExtendFXApp(false, false);  // -jar, no JAC
 305     }
 306 
 307     static void testExtendFXApp(boolean useCP, boolean setFXMainClass) throws Exception {
 308         String testname = "testExtendFXApp";
 309         if (useCP) {
 310             testname = testname.concat("_useCP");
 311         }
 312         String fxMC = ExtMainClass;
 313         if (!setFXMainClass) {
 314             testname = testname.concat("_noJAC");
 315             fxMC = null;
 316         }
 317         for (String mm : MAIN_METHODS) {
 318             testcount++;
 319             line();
 320             System.out.println("test# " + testcount + "-  Main method: " + mm);
 321             createJavaFile(mm);
 322             createExtJavaFile(mm);
 323             createFile(ManifestFile, createManifestContents(ExtMainClass, fxMC));
 324             createJar(FXtestJar, ManifestFile);
 325             String sTestJar = FXtestJar.getAbsolutePath();
 326             final TestResult tr;
 327             if (useCP) {
 328                 tr = doFxExec(javaCmd, "-cp", sTestJar, ExtMainClass, APP_PARMS[0], APP_PARMS[1]);
 329             } else {
 330                 tr = doFxExec(javaCmd, "-jar", sTestJar, APP_PARMS[0], APP_PARMS[1]);
 331             }
 332             tr.checkPositive();
 333             if (tr.testStatus && tr.contains("HelloWorld.primaryStage.show()")) {
 334                 for (String p : APP_PARMS) {
 335                     if (!tr.contains(p)) {
 336                         System.err.println("ERROR: Did not find "
 337                                 + p + " in output!");
 338                     }
 339                 }
 340             }
 341             checkStatus(tr, testname, testcount, ExtMainClass);
 342         }
 343     }
 344 
 345     /*
 346      * Ensure we can NOT launch a FX app jar with no Main-Class manifest entry
 347      */
 348     @Test
 349     static void testMissingMC() throws Exception {
 350         String testname = "testMissingMC";
 351         testcount++;
 352         line();
 353         System.out.println("test# " + testcount + ": abort on missing Main-Class");
 354         createJavaFile(" "); // no main() needed
 355         createFile(ManifestFile, createManifestContents(null, StdMainClass)); // No MC, but supply JAC
 356         createJar(FXtestJar, ManifestFile);
 357         String sTestJar = FXtestJar.getAbsolutePath();
 358         TestResult tr = doFxExec(javaCmd, "-jar", sTestJar, APP_PARMS[0], APP_PARMS[1]);
 359         tr.checkNegative(); // should abort if no Main-Class
 360         if (tr.testStatus) {
 361             if (!tr.contains("no main manifest attribute")) {
 362                 System.err.println("ERROR: launcher did not abort properly");
 363             }
 364         } else {
 365             System.err.println("ERROR: jar executed with no Main-Class!");
 366         }
 367         checkStatus(tr, testname, testcount, StdMainClass);
 368     }
 369 
 370     /*
 371      * test to ensure that we don't load any extraneous fx jars when
 372      * launching a standard java application
 373      * Test both -cp and -jar methods since they use different code paths.
 374      * Neither case should cause jfxrt.jar to be loaded.
 375      */
 376     @Test
 377     static void testExtraneousJars() throws Exception {
 378         testExtraneousJars(true);
 379         testExtraneousJars(false);
 380     }
 381 
 382     static void testExtraneousJars(boolean useCP) throws Exception {
 383         String testname = "testExtraneousJars";
 384         if (useCP) {
 385             testname = testname.concat("_useCP");
 386         }
 387         testcount++;
 388         line();
 389         System.out.println("test# " + testcount
 390                 + ": test for erroneous jfxrt.jar loading");
 391         createNonFXJavaFile();
 392         createFile(ManifestFile, createManifestContents(NonFXMainClass, null));
 393         createJar(FXtestJar, ManifestFile);
 394         String sTestJar = FXtestJar.getAbsolutePath();
 395         final TestResult tr;
 396 
 397         if (useCP) {
 398             tr = doFxExec(javaCmd, "-verbose:class", "-cp", sTestJar, NonFXMainClass, APP_PARMS[0], APP_PARMS[1]);
 399         } else {
 400             tr = doFxExec(javaCmd, "-verbose:class", "-jar", sTestJar, APP_PARMS[0], APP_PARMS[1]);
 401         }
 402         tr.checkPositive();
 403         if (tr.testStatus) {
 404             if (!tr.notContains("jfxrt.jar")) {
 405                 System.out.println("testing for extraneous jfxrt jar");
 406                 System.out.println(tr);
 407                 throw new Exception("jfxrt.jar is being loaded, it should not be!");
 408             }
 409             if (!tr.notContains("sun.launcher.LauncherHelper$FXHelper")) {
 410                 System.out.println("testing for extraneous 'sun.launcher.LauncherHelper$FXHelper'");
 411                 System.out.println(tr);
 412                 throw new Exception("FXHelper is being loaded, it should not be!");
 413             }
 414             for (String p : APP_PARMS) {
 415                 if (!tr.contains(p)) {
 416                     System.err.println("ERROR: Did not find "
 417                             + p + " in output!");
 418                 }
 419             }
 420         }
 421         checkStatus(tr, testname, testcount, NonFXMainClass);
 422     }
 423 
 424     public static void main(String... args) throws Exception {
 425 
 426         // Ensure that FX is not part of jdk
 427         Class<?> fxClass = null;
 428         try {
 429             fxClass = Class.forName(FX_MARKER_CLASS);
 430         } catch (ClassNotFoundException ex) {
 431             // do nothing
 432         }
 433         if (fxClass != null) {
 434             throw new RuntimeException("JavaFX modules erroneously included in the JDK");
 435         }
 436 
 437         FXLauncherTest fxt = new FXLauncherTest();
 438         fxt.run(args);
 439         if (testExitValue > 0) {
 440             System.out.println("Total of " + testExitValue
 441                     + " failed. Test cases covered: "
 442                     + FXLauncherTest.testcount);
 443             System.exit(1);
 444         } else {
 445             System.out.println("All tests pass. Test cases covered: "
 446                     + FXLauncherTest.testcount);
 447         }
 448     }
 449 
 450 }