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