1 /*
   2  * Copyright (c) 2007, 2010, 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  * @bug 5030233 6214916 6356475 6571029 6684582 6742159 4459600 6758881 6753938
  27  *      6894719 6968053
  28  * @summary Argument parsing validation.
  29  * @compile -XDignore.symbol.file Arrrghs.java TestHelper.java
  30  * @run main Arrrghs
  31  */
  32 
  33 import java.io.BufferedReader;
  34 import java.io.File;
  35 import java.io.FileNotFoundException;
  36 import java.io.IOException;
  37 import java.io.InputStream;
  38 import java.io.InputStreamReader;
  39 import java.util.Map;
  40 
  41 public class Arrrghs {
  42     private Arrrghs(){}
  43     /**
  44      * This class provides various tests for arguments processing.
  45      * A group of tests to ensure that arguments are passed correctly to
  46      * a child java process upon a re-exec, this typically happens when
  47      * a version other than the one being executed is requested by the user.
  48      *
  49      * History: these set of tests  were part of Arrrghs.sh. The MKS shell
  50      * implementations were notoriously buggy. Implementing these tests purely
  51      * in Java is not only portable but also robust.
  52      *
  53      */
  54 
  55     // The version string to force a re-exec
  56     final static String VersionStr = "-version:1.1+";
  57 
  58     // The Cookie or the pattern we match in the debug output.
  59     final static String Cookie = "ReExec Args: ";
  60 
  61     /*
  62      * SIGH, On Windows all strings are quoted, we need to unwrap it
  63      */
  64     private static String removeExtraQuotes(String in) {
  65         if (TestHelper.isWindows) {
  66             // Trim the string and remove the enclosed quotes if any.
  67             in = in.trim();
  68             if (in.startsWith("\"") && in.endsWith("\"")) {
  69                 return in.substring(1, in.length()-1);
  70             }
  71         }
  72         return in;
  73     }
  74 
  75     /*
  76      * This method detects the cookie in the output stream of the process.
  77      */
  78     private static boolean detectCookie(InputStream istream,
  79             String expectedArguments) throws IOException {
  80         BufferedReader rd = new BufferedReader(new InputStreamReader(istream));
  81         boolean retval = false;
  82 
  83         String in = rd.readLine();
  84         while (in != null) {
  85             if (TestHelper.debug) System.out.println(in);
  86             if (in.startsWith(Cookie)) {
  87                 String detectedArgument = removeExtraQuotes(in.substring(Cookie.length()));
  88                 if (expectedArguments.equals(detectedArgument)) {
  89                     retval = true;
  90                 } else {
  91                     System.out.println("Error: Expected Arguments\t:'" +
  92                             expectedArguments + "'");
  93                     System.out.println(" Detected Arguments\t:'" +
  94                             detectedArgument + "'");
  95                 }
  96                 // Return the value asap if not in debug mode.
  97                 if (!TestHelper.debug) {
  98                     rd.close();
  99                     istream.close();
 100                     return retval;
 101                 }
 102             }
 103             in = rd.readLine();
 104         }
 105         return retval;
 106     }
 107 
 108     private static boolean doTest0(ProcessBuilder pb, String expectedArguments) {
 109         boolean retval = false;
 110         try {
 111             pb.redirectErrorStream(true);
 112             Process p = pb.start();
 113             retval = detectCookie(p.getInputStream(), expectedArguments);
 114             p.waitFor();
 115             p.destroy();
 116         } catch (Exception ex) {
 117             ex.printStackTrace();
 118             throw new RuntimeException(ex.getMessage());
 119         }
 120         return retval;
 121     }
 122 
 123     /**
 124      * This method return true  if the expected and detected arguments are the same.
 125      * Quoting could cause dissimilar testArguments and expected arguments.
 126      */
 127     static int doTest(String testArguments, String expectedPattern) {
 128         ProcessBuilder pb = new ProcessBuilder(TestHelper.javaCmd,
 129                 VersionStr, testArguments);
 130 
 131         Map<String, String> env = pb.environment();
 132         env.put("_JAVA_LAUNCHER_DEBUG", "true");
 133         return doTest0(pb, testArguments) ? 0 : 1;
 134     }
 135 
 136     /**
 137      * A convenience method for identical test pattern and expected arguments
 138      */
 139     static int doTest(String testPattern) {
 140         return doTest(testPattern, testPattern);
 141     }
 142 
 143     static void quoteParsingTests() {
 144         /*
 145          * Tests for 6214916
 146          * These tests require that a JVM (any JVM) be installed in the system registry.
 147          * If none is installed, skip this test.
 148          */
 149         TestHelper.TestResult tr =
 150                 TestHelper.doExec(TestHelper.javaCmd, VersionStr, "-version");
 151         if (!tr.isOK()) {
 152             System.err.println("Warning:Argument Passing Tests were skipped, " +
 153                     "no java found in system registry.");
 154             return;
 155         }
 156 
 157         // Basic test
 158         TestHelper.testExitValue += doTest("-a -b -c -d");
 159 
 160         // Basic test with many spaces
 161         TestHelper.testExitValue += doTest("-a    -b      -c       -d");
 162 
 163         // Quoted whitespace does matter ?
 164         TestHelper.testExitValue += doTest("-a \"\"-b      -c\"\" -d");
 165 
 166 
 167         // Escaped quotes outside of quotes as literals
 168         TestHelper.testExitValue += doTest("-a \\\"-b -c\\\" -d");
 169 
 170         // Check for escaped quotes inside of quotes as literal
 171         TestHelper.testExitValue += doTest("-a \"-b \\\"stuff\\\"\" -c -d");
 172 
 173         // A quote preceeded by an odd number of slashes is a literal quote
 174         TestHelper.testExitValue += doTest("-a -b\\\\\\\" -c -d");
 175 
 176         // A quote preceeded by an even number of slashes is a literal quote
 177         // see 6214916.
 178         TestHelper.testExitValue += doTest("-a -b\\\\\\\\\" -c -d");
 179 
 180         // Make sure that whitespace doesn't interfere with the removal of the
 181         // appropriate tokens. (space-tab-space preceeds -jre-restict-search).
 182         TestHelper.testExitValue += doTest("-a -b  \t -jre-restrict-search -c -d","-a -b -c -d");
 183 
 184         // Make sure that the mJRE tokens being stripped, aren't stripped if
 185         // they happen to appear as arguments to the main class.
 186         TestHelper.testExitValue += doTest("foo -version:1.1+");
 187 
 188         System.out.println("Completed arguments quoting tests with " +
 189                 TestHelper.testExitValue + " errors");
 190     }
 191 
 192     /*
 193      * These tests are usually run on non-existent targets to check error results
 194      */
 195     static void runBasicErrorMessageTests() {
 196         // Tests for 5030233
 197         TestHelper.TestResult tr = TestHelper.doExec(TestHelper.javaCmd, "-cp");
 198         tr.checkNegative();
 199         tr.isNotZeroOutput();
 200         System.out.println(tr);
 201 
 202         tr = TestHelper.doExec(TestHelper.javaCmd, "-classpath");
 203         tr.checkNegative();
 204         tr.isNotZeroOutput();
 205         System.out.println(tr);
 206 
 207         tr = TestHelper.doExec(TestHelper.javaCmd, "-jar");
 208         tr.checkNegative();
 209         tr.isNotZeroOutput();
 210         System.out.println(tr);
 211 
 212         tr = TestHelper.doExec(TestHelper.javacCmd, "-cp");
 213         tr.checkNegative();
 214         tr.isNotZeroOutput();
 215         System.out.println(tr);
 216 
 217         // Test for 6356475 "REGRESSION:"java -X" from cmdline fails"
 218         tr = TestHelper.doExec(TestHelper.javaCmd, "-X");
 219         tr.checkPositive();
 220         tr.isNotZeroOutput();
 221         System.out.println(tr);
 222 
 223         tr = TestHelper.doExec(TestHelper.javaCmd, "-help");
 224         tr.checkPositive();
 225         tr.isNotZeroOutput();
 226         System.out.println(tr);
 227 
 228         // 6753938, test for non-negative exit value for an incorrectly formed
 229         // command line,  '% java'
 230         tr = TestHelper.doExec(TestHelper.javaCmd);
 231         tr.checkNegative();
 232         tr.isNotZeroOutput();
 233         System.out.println(tr);
 234 
 235         // 6753938, test for non-negative exit value for an incorrectly formed
 236         // command line,  '% java -Xcomp'
 237         tr = TestHelper.doExec(TestHelper.javaCmd, "-Xcomp");
 238         tr.checkNegative();
 239         tr.isNotZeroOutput();
 240         System.out.println(tr);
 241     }
 242 
 243     /*
 244      * A set of tests which tests various dispositions of the main method.
 245      */
 246     static void runMainMethodTests() throws FileNotFoundException {
 247         TestHelper.TestResult tr = null;
 248 
 249         // a missing class
 250         TestHelper.createJar("MIA", new File("some.jar"), new File("Foo"),
 251                 (String[])null);
 252         tr = TestHelper.doExec(TestHelper.javaCmd, "-jar", "some.jar");
 253         tr.contains("Error: Could not find or load main class MIA");
 254         System.out.println(tr);
 255         // use classpath to check
 256         tr = TestHelper.doExec(TestHelper.javaCmd, "-cp", "some.jar", "MIA");
 257         tr.contains("Error: Could not find or load main class MIA");
 258         System.out.println(tr);
 259 
 260         // incorrect method access
 261         TestHelper.createJar(new File("some.jar"), new File("Foo"),
 262                 "private static void main(String[] args){}");
 263         tr = TestHelper.doExec(TestHelper.javaCmd, "-jar", "some.jar");
 264         tr.contains("Error: Main method not found in class Foo");
 265         System.out.println(tr);
 266         // use classpath to check
 267         tr = TestHelper.doExec(TestHelper.javaCmd, "-cp", "some.jar", "Foo");
 268         tr.contains("Error: Main method not found in class Foo");
 269         System.out.println(tr);
 270 
 271         // incorrect return type
 272         TestHelper.createJar(new File("some.jar"), new File("Foo"),
 273                 "public static int main(String[] args){return 1;}");
 274         tr = TestHelper.doExec(TestHelper.javaCmd, "-jar", "some.jar");
 275         tr.contains("Error: Main method must return a value of type void in class Foo");
 276         System.out.println(tr);
 277         // use classpath to check
 278         tr = TestHelper.doExec(TestHelper.javaCmd, "-cp", "some.jar", "Foo");
 279         tr.contains("Error: Main method must return a value of type void in class Foo");
 280         System.out.println(tr);
 281 
 282         // incorrect parameter type
 283         TestHelper.createJar(new File("some.jar"), new File("Foo"),
 284                 "public static void main(Object[] args){}");
 285         tr = TestHelper.doExec(TestHelper.javaCmd, "-jar", "some.jar");
 286         tr.contains("Error: Main method not found in class Foo");
 287         System.out.println(tr);
 288         // use classpath to check
 289         tr = TestHelper.doExec(TestHelper.javaCmd, "-cp", "some.jar", "Foo");
 290         tr.contains("Error: Main method not found in class Foo");
 291         System.out.println(tr);
 292 
 293         // incorrect method type - non-static
 294          TestHelper.createJar(new File("some.jar"), new File("Foo"),
 295                 "public void main(String[] args){}");
 296         tr = TestHelper.doExec(TestHelper.javaCmd, "-jar", "some.jar");
 297         tr.contains("Error: Main method is not static in class Foo");
 298         System.out.println(tr);
 299         // use classpath to check
 300         tr = TestHelper.doExec(TestHelper.javaCmd, "-cp", "some.jar", "Foo");
 301         tr.contains("Error: Main method is not static in class Foo");
 302         System.out.println(tr);
 303 
 304         // amongst a potpourri of kindred main methods, is the right one chosen ?
 305         TestHelper.createJar(new File("some.jar"), new File("Foo"),
 306             "void main(Object[] args){}",
 307             "int  main(Float[] args){return 1;}",
 308             "private void main() {}",
 309             "private static void main(int x) {}",
 310             "public int main(int argc, String[] argv) {return 1;}",
 311             "public static void main(String[] args) {System.out.println(\"THE_CHOSEN_ONE\");}");
 312         tr = TestHelper.doExec(TestHelper.javaCmd, "-jar", "some.jar");
 313         tr.contains("THE_CHOSEN_ONE");
 314         System.out.println(tr);
 315         // use classpath to check
 316         tr = TestHelper.doExec(TestHelper.javaCmd, "-cp", "some.jar", "Foo");
 317         tr.contains("THE_CHOSEN_ONE");
 318         System.out.println(tr);
 319 
 320         // test for extraneous whitespace in the Main-Class attribute
 321         TestHelper.createJar(" Foo ", new File("some.jar"), new File("Foo"),
 322                 "public static void main(String... args){}");
 323         tr = TestHelper.doExec(TestHelper.javaCmd, "-jar", "some.jar");
 324         tr.checkPositive();
 325         System.out.println(tr);
 326     }
 327     // tests 6968053, ie. we turn on the -Xdiag (for now) flag and check if
 328     // the suppressed stack traces are exposed.
 329     static void runDiagOptionTests() throws FileNotFoundException {
 330         TestHelper.TestResult tr = null;
 331         // a missing class
 332         TestHelper.createJar("MIA", new File("some.jar"), new File("Foo"),
 333                 (String[])null);
 334         tr = TestHelper.doExec(TestHelper.javaCmd, "-Xdiag", "-jar", "some.jar");
 335         tr.contains("Error: Could not find or load main class MIA");
 336         tr.contains("java.lang.ClassNotFoundException: MIA");
 337         System.out.println(tr);
 338 
 339         // use classpath to check
 340         tr = TestHelper.doExec(TestHelper.javaCmd,  "-Xdiag", "-cp", "some.jar", "MIA");
 341         tr.contains("Error: Could not find or load main class MIA");
 342         tr.contains("java.lang.ClassNotFoundException: MIA");
 343         System.out.println(tr);
 344 
 345         // a missing class on the classpath
 346         tr = TestHelper.doExec(TestHelper.javaCmd, "-Xdiag", "NonExistentClass");
 347         tr.contains("Error: Could not find or load main class NonExistentClass");
 348         tr.contains("java.lang.ClassNotFoundException: NonExistentClass");
 349         System.out.println(tr);
 350     }
 351 
 352     static void test6894719() {
 353         // test both arguments to ensure they exist
 354         TestHelper.TestResult tr = null;
 355         tr = TestHelper.doExec(TestHelper.javaCmd,
 356                 "-no-jre-restrict-search", "-version");
 357         tr.checkPositive();
 358         System.out.println(tr);
 359 
 360         tr = TestHelper.doExec(TestHelper.javaCmd,
 361                 "-jre-restrict-search", "-version");
 362         tr.checkPositive();
 363         System.out.println(tr);
 364     }
 365     /**
 366      * @param args the command line arguments
 367      * @throws java.io.FileNotFoundException
 368      */
 369     public static void main(String[] args) throws FileNotFoundException {
 370         if (TestHelper.debug) {
 371             System.out.println("Starting Arrrghs tests");
 372         }
 373         quoteParsingTests();
 374         runBasicErrorMessageTests();
 375         runMainMethodTests();
 376         test6894719();
 377         runDiagOptionTests();
 378         if (TestHelper.testExitValue > 0) {
 379             System.out.println("Total of " + TestHelper.testExitValue + " failed");
 380             System.exit(1);
 381         } else {
 382             System.out.println("All tests pass");
 383         }
 384     }
 385 }