AbstractScriptRunnable.java revision 475:fbd21b00197b
1/* 2 * Copyright (c) 2010, 2013, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.nashorn.internal.test.framework; 27 28import static jdk.nashorn.internal.test.framework.TestConfig.OPTIONS_CHECK_COMPILE_MSG; 29import static jdk.nashorn.internal.test.framework.TestConfig.OPTIONS_COMPARE; 30import static jdk.nashorn.internal.test.framework.TestConfig.OPTIONS_EXPECT_COMPILE_FAIL; 31import static jdk.nashorn.internal.test.framework.TestConfig.OPTIONS_EXPECT_RUN_FAIL; 32import static jdk.nashorn.internal.test.framework.TestConfig.OPTIONS_FORK; 33import static jdk.nashorn.internal.test.framework.TestConfig.OPTIONS_IGNORE_STD_ERROR; 34import static jdk.nashorn.internal.test.framework.TestConfig.OPTIONS_RUN; 35import static jdk.nashorn.internal.test.framework.TestConfig.TEST_JS_FAIL_LIST; 36import static jdk.nashorn.internal.test.framework.TestConfig.TEST_JS_SHARED_CONTEXT; 37 38import java.io.BufferedReader; 39import java.io.File; 40import java.io.IOException; 41import java.io.OutputStream; 42import java.util.ArrayList; 43import java.util.Arrays; 44import java.util.HashSet; 45import java.util.List; 46import java.util.Map; 47import java.util.Set; 48import java.util.regex.Matcher; 49 50/** 51 * Abstract class to compile and run one .js script file. 52 */ 53public abstract class AbstractScriptRunnable { 54 // some test scripts need a "framework" script - whose features are used 55 // in the test script. This optional framework script can be null. 56 57 protected final String framework; 58 // Script file that is being tested 59 protected final File testFile; 60 // build directory where test output, stderr etc are redirected 61 protected final File buildDir; 62 // should run the test or just compile? 63 protected final boolean shouldRun; 64 // is compiler error expected? 65 protected final boolean expectCompileFailure; 66 // is runtime error expected? 67 protected final boolean expectRunFailure; 68 // is compiler error captured and checked against known error strings? 69 protected final boolean checkCompilerMsg; 70 // .EXPECTED file compared for this or test? 71 protected final boolean compare; 72 // should test run in a separate process? 73 protected final boolean fork; 74 // ignore stderr output? 75 protected final boolean ignoreStdError; 76 // Foo.js.OUTPUT file where test stdout messages go 77 protected final String outputFileName; 78 // Foo.js.ERROR where test's stderr messages go. 79 protected final String errorFileName; 80 // copy of Foo.js.EXPECTED file 81 protected final String copyExpectedFileName; 82 // Foo.js.EXPECTED - output expected by running Foo.js 83 protected final String expectedFileName; 84 // options passed to Nashorn engine 85 protected final List<String> engineOptions; 86 // arguments passed to script - these are visible as "arguments" array to script 87 protected final List<String> scriptArguments; 88 // Tests that are forced to fail always 89 protected final Set<String> failList = new HashSet<>(); 90 91 public AbstractScriptRunnable(final String framework, final File testFile, final List<String> engineOptions, final Map<String, String> testOptions, final List<String> scriptArguments) { 92 this.framework = framework; 93 this.testFile = testFile; 94 this.buildDir = TestHelper.makeBuildDir(testFile); 95 this.engineOptions = engineOptions; 96 this.scriptArguments = scriptArguments; 97 98 this.expectCompileFailure = testOptions.containsKey(OPTIONS_EXPECT_COMPILE_FAIL); 99 this.shouldRun = testOptions.containsKey(OPTIONS_RUN); 100 this.expectRunFailure = testOptions.containsKey(OPTIONS_EXPECT_RUN_FAIL); 101 this.checkCompilerMsg = testOptions.containsKey(OPTIONS_CHECK_COMPILE_MSG); 102 this.ignoreStdError = testOptions.containsKey(OPTIONS_IGNORE_STD_ERROR); 103 this.compare = testOptions.containsKey(OPTIONS_COMPARE); 104 this.fork = testOptions.containsKey(OPTIONS_FORK); 105 106 final String testName = testFile.getName(); 107 this.outputFileName = buildDir + File.separator + testName + ".OUTPUT"; 108 this.errorFileName = buildDir + File.separator + testName + ".ERROR"; 109 this.copyExpectedFileName = buildDir + File.separator + testName + ".EXPECTED"; 110 this.expectedFileName = testFile.getPath() + ".EXPECTED"; 111 112 if (failListString != null) { 113 final String[] failedTests = failListString.split(" "); 114 for (final String failedTest : failedTests) { 115 failList.add(failedTest.trim()); 116 } 117 } 118 } 119 120 // run this test - compile or compile-and-run depending on option passed 121 public void runTest() throws IOException { 122 log(toString()); 123 Thread.currentThread().setName(testFile.getPath()); 124 if (shouldRun) { 125 // Analysis of failing tests list - 126 // if test is in failing list it must fail 127 // to not wrench passrate (used for crashing tests). 128 if (failList.contains(testFile.getName())) { 129 fail(String.format("Test %s is forced to fail (see %s)", testFile, TEST_JS_FAIL_LIST)); 130 } 131 132 execute(); 133 } else { 134 compile(); 135 } 136 } 137 138 @Override 139 public String toString() { 140 return "Test(compile" + (expectCompileFailure ? "-" : "") + (shouldRun ? ", run" : "") + (expectRunFailure ? "-" : "") + "): " + testFile; 141 } 142 143 // compile-only command line arguments 144 protected List<String> getCompilerArgs() { 145 final List<String> args = new ArrayList<>(); 146 args.add("--compile-only"); 147 args.addAll(engineOptions); 148 args.add(testFile.getPath()); 149 return args; 150 } 151 152 // shared context or not? 153 protected static final boolean sharedContext = Boolean.getBoolean(TEST_JS_SHARED_CONTEXT); 154 protected static final String failListString = System.getProperty(TEST_JS_FAIL_LIST); 155 // VM options when a @fork test is executed by a separate process 156 protected static final String[] forkJVMOptions; 157 static { 158 String vmOptions = System.getProperty(TestConfig.TEST_FORK_JVM_OPTIONS); 159 forkJVMOptions = (vmOptions != null)? vmOptions.split(" ") : new String[0]; 160 } 161 162 private static ThreadLocal<ScriptEvaluator> evaluators = new ThreadLocal<>(); 163 164 /** 165 * Create a script evaluator or return from cache 166 * @return a ScriptEvaluator object 167 */ 168 protected ScriptEvaluator getEvaluator() { 169 synchronized (AbstractScriptRunnable.class) { 170 ScriptEvaluator evaluator = evaluators.get(); 171 if (evaluator == null) { 172 if (sharedContext) { 173 final String[] args; 174 if (framework.indexOf(' ') > 0) { 175 args = framework.split("\\s+"); 176 } else { 177 args = new String[] { framework }; 178 } 179 evaluator = new SharedContextEvaluator(args); 180 evaluators.set(evaluator); 181 } else { 182 evaluator = new SeparateContextEvaluator(); 183 evaluators.set(evaluator); 184 } 185 } 186 return evaluator; 187 } 188 } 189 190 /** 191 * Evaluate one or more scripts with given output and error streams 192 * 193 * @param out OutputStream for script output 194 * @param err OutputStream for script errors 195 * @param args arguments for script evaluation 196 * @return success or error code from script execution 197 */ 198 protected int evaluateScript(final OutputStream out, final OutputStream err, final String[] args) { 199 try { 200 return getEvaluator().run(out, err, args); 201 } catch (final IOException e) { 202 throw new UnsupportedOperationException("I/O error in initializing shell - cannot redirect output to file"); 203 } 204 } 205 206 // arguments to be passed to compile-and-run this script 207 protected List<String> getRuntimeArgs() { 208 final ArrayList<String> args = new ArrayList<>(); 209 // add engine options first 210 args.addAll(engineOptions); 211 212 // framework script if any 213 if (framework != null) { 214 if (framework.indexOf(' ') > 0) { 215 args.addAll(Arrays.asList(framework.split("\\s+"))); 216 } else { 217 args.add(framework); 218 } 219 } 220 221 // test script 222 args.add(testFile.getPath()); 223 224 // script arguments 225 if (!scriptArguments.isEmpty()) { 226 args.add("--"); 227 args.addAll(scriptArguments); 228 } 229 230 return args; 231 } 232 233 // compares actual test output with .EXPECTED output 234 protected void compare(final BufferedReader actual, final BufferedReader expected, final boolean compareCompilerMsg) throws IOException { 235 int lineCount = 0; 236 while (true) { 237 final String es = expected.readLine(); 238 String as = actual.readLine(); 239 if (compareCompilerMsg) { 240 while (as != null && as.startsWith("--")) { 241 as = actual.readLine(); 242 } 243 } 244 ++lineCount; 245 246 if (es == null && as == null) { 247 if (expectRunFailure) { 248 fail("Expected runtime failure"); 249 } else { 250 break; 251 } 252 } else if (expectRunFailure && ((es == null) || as == null || !es.equals(as))) { 253 break; 254 } else if (es == null) { 255 fail("Expected output for " + testFile + " ends prematurely at line " + lineCount); 256 } else if (as == null) { 257 fail("Program output for " + testFile + " ends prematurely at line " + lineCount); 258 } else if (es.equals(as)) { 259 continue; 260 } else if (compareCompilerMsg && equalsCompilerMsgs(es, as)) { 261 continue; 262 } else { 263 fail("Test " + testFile + " failed at line " + lineCount + " - " + " \n expected: '" + escape(es) + "'\n found: '" + escape(as) + "'"); 264 } 265 } 266 } 267 268 // logs the message 269 protected abstract void log(String msg); 270 // throw failure message 271 protected abstract void fail(String msg); 272 // compile this script but don't run it 273 protected abstract void compile() throws IOException; 274 // compile and run this script 275 protected abstract void execute(); 276 277 private boolean equalsCompilerMsgs(final String es, final String as) { 278 final int split = es.indexOf(':'); 279 // Replace both types of separators ('/' and '\') with the one from 280 // current environment 281 return (split >= 0) && as.equals(es.substring(0, split).replaceAll("[/\\\\]", Matcher.quoteReplacement(File.separator)) + es.substring(split)); 282 } 283 284 private void escape(final String value, final StringBuilder out) { 285 final int len = value.length(); 286 for (int i = 0; i < len; i++) { 287 final char ch = value.charAt(i); 288 if (ch == '\n') { 289 out.append("\\n"); 290 } else if (ch < ' ' || ch == 127) { 291 out.append(String.format("\\%03o", (int) ch)); 292 } else if (ch > 127) { 293 out.append(String.format("\\u%04x", (int) ch)); 294 } else { 295 out.append(ch); 296 } 297 } 298 } 299 300 private String escape(final String value) { 301 final StringBuilder sb = new StringBuilder(); 302 escape(value, sb); 303 return sb.toString(); 304 } 305} 306