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