1 /* 2 * Copyright (c) 2015, 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 8027634 27 * @summary Verify syntax of argument file 28 * @build TestHelper 29 * @run main ArgFileSyntax 30 */ 31 import java.io.File; 32 import java.io.IOException; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.regex.Matcher; 40 import java.util.regex.Pattern; 41 42 public class ArgFileSyntax extends TestHelper { 43 private File createArgFile(List<String> lines) throws IOException { 44 File argFile = new File("argfile"); 45 argFile.delete(); 46 createAFile(argFile, lines); 47 return argFile; 48 } 49 50 private void verifyOutput(List<String> args, TestResult tr) { 51 if (args.isEmpty()) { 52 return; 53 } 54 55 int i = 1; 56 for (String x : args) { 57 tr.matches(".*argv\\[" + i + "\\] = " + Pattern.quote(x) + ".*"); 58 i++; 59 } 60 if (! tr.testStatus) { 61 System.out.println(tr); 62 throw new RuntimeException("test fails"); 63 } 64 } 65 66 // arg file content, expected options 67 static String[] testCases[][] = { 68 { // empty file 69 {}, {} 70 }, 71 { // comments and # inside quote 72 { "# a couple of -X flags", 73 "-Xmx32m", 74 "-XshowSettings #inline comment", 75 "-Dpound.in.quote=\"This property contains #.\"", 76 "# add -version", 77 "-version", 78 "# trail comment" 79 }, 80 { "-Xmx32m", 81 "-XshowSettings", 82 "-Dpound.in.quote=This property contains #.", 83 "-version" 84 } 85 }, 86 { // open quote with continuation directive 87 // multiple options in a line 88 { "-cp \"c:\\\\java lib\\\\all;\\", 89 " c:\\\\lib\"", 90 "-Xmx32m -XshowSettings", 91 "-version" 92 }, 93 { "-cp", 94 "c:\\java lib\\all;c:\\lib", 95 "-Xmx32m", 96 "-XshowSettings", 97 "-version" 98 } 99 }, 100 { // no continuation on open quote 101 // multiple lines in a property 102 { "-cp \"c:\\\\open quote\\\\all;", 103 " # c:\\\\lib\"", 104 "-Dmultiple.lines=\"line 1\\nline 2\\n\\rline 3\"", 105 "-Dopen.quote=\"Open quote to EOL", 106 "-Dcontinue.with.leadingWS=\"Continue with\\", 107 " \\ leading WS.", 108 "-Dcontinue.without.leadingWS=\"Continue without \\", 109 " leading WS.", 110 "-Descape.seq=\"escaped chars: \\\"\\a\\b\\c\\f\\t\\v\\9\\6\\23\\82\\28\\377\\477\\278\\287\\n\"", 111 "-version" 112 }, 113 { "-cp", 114 "c:\\open quote\\all;", 115 "-Dmultiple.lines=line 1", 116 // line 2 and line 3 shoule be in output, but not as arg[x]= 117 "-Dopen.quote=Open quote to EOL", 118 "-Dcontinue.with.leadingWS=Continue with leading WS.", 119 "-Dcontinue.without.leadingWS=Continue without leading WS.", 120 // cannot verify \n and \r as that break output lines 121 "-Descape.seq=escaped chars: \"abc\f\tv96238228377477278287", 122 "-version" 123 } 124 }, 125 { // No need to escape if not in quote 126 // also quote part of a token 127 { "-cp c:\\\"partial quote\"\\all", 128 "-Xmx32m -XshowSettings", 129 "-version" 130 }, 131 { "-cp", 132 "c:\\partial quote\\all", 133 "-Xmx32m", 134 "-XshowSettings", 135 "-version" 136 } 137 }, 138 { // No recursive expansion 139 { "-Xmx32m", 140 "-cp", 141 " # @cpfile should remains @cpfile", 142 "@cpfile", 143 "-version" 144 }, 145 { "-Xmx32m", 146 "-cp", 147 "@cpfile", 148 "-version" 149 } 150 }, 151 { // Mix quotation 152 { "-Dsingle.in.double=\"Mix 'single' in double\"", 153 "-Ddouble.in.single='Mix \"double\" in single'", 154 "-Dsingle.in.single='Escape \\\'single\\\' in single'", 155 "-Ddouble.in.double=\"Escape \\\"double\\\" in double\"" 156 }, 157 { "-Dsingle.in.double=Mix 'single' in double", 158 "-Ddouble.in.single=Mix \"double\" in single", 159 "-Dsingle.in.single=Escape 'single' in single", 160 "-Ddouble.in.double=Escape \"double\" in double" 161 }, 162 }, 163 { // \t\f as whitespace and in escape 164 { "-Xmx32m\t-Xint\f-version", 165 "-Dcontinue.with.leadingws=\"Line1\\", 166 " \t\fcontinue with \\f<ff> and \\t<tab>" 167 }, 168 { "-Xmx32m", 169 "-Xint", 170 "-version", 171 "-Dcontinue.with.leadingws=Line1continue with \f<ff> and \t<tab>" 172 } 173 } 174 }; 175 176 public List<List<List<String>>> loadCases() { 177 List<List<List<String>>> rv = new ArrayList<>(); 178 for (String[][] testCaseArray: testCases) { 179 List<List<String>> testCase = new ArrayList<>(2); 180 testCase.add(Arrays.asList(testCaseArray[0])); 181 testCase.add(Arrays.asList(testCaseArray[1])); 182 rv.add(testCase); 183 } 184 185 // long lines 186 String bag = "-Dgarbage="; 187 String ver = "-version"; 188 // a token 8192 long 189 char[] data = new char[8192 - bag.length()]; 190 Arrays.fill(data, 'O'); 191 List<String> scratch = new ArrayList<>(); 192 scratch.add("-Xmx32m"); 193 scratch.add(bag + String.valueOf(data)); 194 scratch.add(ver); 195 rv.add(Collections.nCopies(2, scratch)); 196 197 data = new char[8192 + 1024]; 198 Arrays.fill(data, 'O'); 199 scratch = new ArrayList<>(); 200 scratch.add(bag + String.valueOf(data)); 201 scratch.add(ver); 202 rv.add(Collections.nCopies(2, scratch)); 203 204 return rv; 205 } 206 207 // ensure the arguments in the file are read in correctly 208 private void verifyParsing(List<String> lines, List<String> args) throws IOException { 209 File argFile = createArgFile(lines); 210 String fname = "@" + argFile.getName(); 211 Map<String, String> env = new HashMap<>(); 212 env.put(JLDEBUG_KEY, "true"); 213 214 TestResult tr; 215 if (args.contains("-version")) { 216 tr = doExec(env, javaCmd, fname); 217 } else { 218 tr = doExec(env, javaCmd, fname, "-version"); 219 } 220 tr.checkPositive(); 221 verifyOutput(args, tr); 222 223 String lastArg = args.contains("-version") ? "-Dlast.arg" : "-version"; 224 tr = doExec(env, javaCmd, "-Xint", fname, lastArg); 225 List<String> scratch = new ArrayList<>(); 226 scratch.add("-Xint"); 227 scratch.addAll(args); 228 scratch.add(lastArg); 229 verifyOutput(scratch, tr); 230 231 argFile.delete(); 232 } 233 234 @Test 235 public void testSyntax() throws IOException { 236 List<List<List<String>>> allcases = loadCases(); 237 for (List<List<String>> test: allcases) { 238 verifyParsing(test.get(0), test.get(1)); 239 } 240 } 241 242 @Test 243 public void badCases() throws IOException { 244 List<String> lines = Arrays.asList( 245 "-Dno.escape=\"Forgot to escape backslash\\\" -version"); 246 File argFile = createArgFile(lines); 247 String fname = "@" + argFile.getName(); 248 Map<String, String> env = new HashMap<>(); 249 env.put(JLDEBUG_KEY, "true"); 250 251 TestResult tr = doExec(env, javaCmd, fname); 252 tr.contains("argv[1] = -Dno.escape=Forgot to escape backslash\" -version"); 253 tr.checkNegative(); 254 if (!tr.testStatus) { 255 System.out.println(tr); 256 throw new RuntimeException("test fails"); 257 } 258 argFile.delete(); 259 } 260 261 public static void main(String... args) throws Exception { 262 ArgFileSyntax a = new ArgFileSyntax(); 263 a.run(args); 264 if (testExitValue > 0) { 265 System.out.println("Total of " + testExitValue + " failed"); 266 System.exit(1); 267 } else { 268 System.out.println("All tests pass"); 269 } 270 } 271 }