AbstractScriptRunnable.java revision 473:f6588f168d79
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        final String failListString = System.getProperty(TEST_JS_FAIL_LIST);
113        if (failListString != null) {
114            final String[] failedTests = failListString.split(" ");
115            for (final String failedTest : failedTests) {
116                failList.add(failedTest.trim());
117            }
118        }
119    }
120
121    // run this test - compile or compile-and-run depending on option passed
122    public void runTest() throws IOException {
123        log(toString());
124        Thread.currentThread().setName(testFile.getPath());
125        if (shouldRun) {
126            // Analysis of failing tests list -
127            // if test is in failing list it must fail
128            // to not wrench passrate (used for crashing tests).
129            if (failList.contains(testFile.getName())) {
130                fail(String.format("Test %s is forced to fail (see %s)", testFile, TEST_JS_FAIL_LIST));
131            }
132
133            execute();
134        } else {
135            compile();
136        }
137    }
138
139    @Override
140    public String toString() {
141        return "Test(compile" + (expectCompileFailure ? "-" : "") + (shouldRun ? ", run" : "") + (expectRunFailure ? "-" : "") + "): " + testFile;
142    }
143
144    // compile-only command line arguments
145    protected List<String> getCompilerArgs() {
146        final List<String> args = new ArrayList<>();
147        args.add("--compile-only");
148        args.addAll(engineOptions);
149        args.add(testFile.getPath());
150        return args;
151    }
152
153    // shared context or not?
154    protected static final boolean sharedContext;
155    static {
156        sharedContext = Boolean.getBoolean(TEST_JS_SHARED_CONTEXT);
157    }
158    private static ThreadLocal<ScriptEvaluator> evaluators = new ThreadLocal<>();
159
160    /**
161     * Create a script evaluator or return from cache
162     * @return a ScriptEvaluator object
163     */
164    protected ScriptEvaluator getEvaluator() {
165        synchronized (AbstractScriptRunnable.class) {
166            ScriptEvaluator evaluator = evaluators.get();
167            if (evaluator == null) {
168                if (sharedContext) {
169                    final String[] args;
170                    if (framework.indexOf(' ') > 0) {
171                        args = framework.split("\\s+");
172                    } else {
173                        args = new String[] { framework };
174                    }
175                    evaluator = new SharedContextEvaluator(args);
176                    evaluators.set(evaluator);
177                } else {
178                    evaluator = new SeparateContextEvaluator();
179                    evaluators.set(evaluator);
180                }
181            }
182            return evaluator;
183        }
184    }
185
186    /**
187     * Evaluate one or more scripts with given output and error streams
188     *
189     * @param out OutputStream for script output
190     * @param err OutputStream for script errors
191     * @param args arguments for script evaluation
192     * @return success or error code from script execution
193     */
194    protected int evaluateScript(final OutputStream out, final OutputStream err, final String[] args) {
195        try {
196            return getEvaluator().run(out, err, args);
197        } catch (final IOException e) {
198            throw new UnsupportedOperationException("I/O error in initializing shell - cannot redirect output to file");
199        }
200    }
201
202    // arguments to be passed to compile-and-run this script
203    protected List<String> getRuntimeArgs() {
204        final ArrayList<String> args = new ArrayList<>();
205        // add engine options first
206        args.addAll(engineOptions);
207
208        // framework script if any
209        if (framework != null) {
210            if (framework.indexOf(' ') > 0) {
211                args.addAll(Arrays.asList(framework.split("\\s+")));
212            } else {
213                args.add(framework);
214            }
215        }
216
217        // test script
218        args.add(testFile.getPath());
219
220        // script arguments
221        if (!scriptArguments.isEmpty()) {
222            args.add("--");
223            args.addAll(scriptArguments);
224        }
225
226        return args;
227    }
228
229    // compares actual test output with .EXPECTED output
230    protected void compare(final BufferedReader actual, final BufferedReader expected, final boolean compareCompilerMsg) throws IOException {
231        int lineCount = 0;
232        while (true) {
233            final String es = expected.readLine();
234            String as = actual.readLine();
235            if (compareCompilerMsg) {
236                while (as != null && as.startsWith("--")) {
237                    as = actual.readLine();
238                }
239            }
240            ++lineCount;
241
242            if (es == null && as == null) {
243                if (expectRunFailure) {
244                    fail("Expected runtime failure");
245                } else {
246                    break;
247                }
248            } else if (expectRunFailure && ((es == null) || as == null || !es.equals(as))) {
249                break;
250            } else if (es == null) {
251                fail("Expected output for " + testFile + " ends prematurely at line " + lineCount);
252            } else if (as == null) {
253                fail("Program output for " + testFile + " ends prematurely at line " + lineCount);
254            } else if (es.equals(as)) {
255                continue;
256            } else if (compareCompilerMsg && equalsCompilerMsgs(es, as)) {
257                continue;
258            } else {
259                fail("Test " + testFile + " failed at line " + lineCount + " - " + " \n  expected: '" + escape(es) + "'\n     found: '" + escape(as) + "'");
260            }
261        }
262    }
263
264    // logs the message
265    protected abstract void log(String msg);
266    // throw failure message
267    protected abstract void fail(String msg);
268    // compile this script but don't run it
269    protected abstract void compile() throws IOException;
270    // compile and run this script
271    protected abstract void execute();
272
273    private boolean equalsCompilerMsgs(final String es, final String as) {
274        final int split = es.indexOf(':');
275        // Replace both types of separators ('/' and '\') with the one from
276        // current environment
277        return (split >= 0) && as.equals(es.substring(0, split).replaceAll("[/\\\\]", Matcher.quoteReplacement(File.separator)) + es.substring(split));
278    }
279
280    private void escape(final String value, final StringBuilder out) {
281        final int len = value.length();
282        for (int i = 0; i < len; i++) {
283            final char ch = value.charAt(i);
284            if (ch == '\n') {
285                out.append("\\n");
286            } else if (ch < ' ' || ch == 127) {
287                out.append(String.format("\\%03o", (int) ch));
288            } else if (ch > 127) {
289                out.append(String.format("\\u%04x", (int) ch));
290            } else {
291                out.append(ch);
292            }
293        }
294    }
295
296    private String escape(final String value) {
297        final StringBuilder sb = new StringBuilder();
298        escape(value, sb);
299        return sb.toString();
300    }
301}
302