ScopeTest.java revision 1786:80120e9b3273
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 */
25package jdk.nashorn.api.scripting.test;
26
27import static org.testng.Assert.assertEquals;
28import static org.testng.Assert.assertNotNull;
29import static org.testng.Assert.assertFalse;
30import static org.testng.Assert.assertTrue;
31import static org.testng.Assert.fail;
32import javax.script.Bindings;
33import javax.script.Compilable;
34import javax.script.CompiledScript;
35import javax.script.Invocable;
36import javax.script.ScriptContext;
37import javax.script.ScriptEngine;
38import javax.script.ScriptEngineFactory;
39import javax.script.ScriptEngineManager;
40import javax.script.ScriptException;
41import javax.script.SimpleBindings;
42import javax.script.SimpleScriptContext;
43import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
44import jdk.nashorn.api.scripting.ScriptObjectMirror;
45import jdk.nashorn.api.scripting.URLReader;
46import org.testng.Assert;
47import org.testng.annotations.Test;
48
49/**
50 * Tests for jsr223 Bindings "scope" (engine, global scopes)
51 */
52@SuppressWarnings("javadoc")
53public class ScopeTest {
54
55    @Test
56    public void createBindingsTest() {
57        final ScriptEngineManager m = new ScriptEngineManager();
58        final ScriptEngine e = m.getEngineByName("nashorn");
59        final Bindings b = e.createBindings();
60        b.put("foo", 42.0);
61        Object res = null;
62        try {
63            res = e.eval("foo == 42.0", b);
64        } catch (final ScriptException | NullPointerException se) {
65            se.printStackTrace();
66            fail(se.getMessage());
67        }
68
69        assertEquals(res, Boolean.TRUE);
70    }
71
72    @Test
73    public void engineScopeTest() {
74        final ScriptEngineManager m = new ScriptEngineManager();
75        final ScriptEngine e = m.getEngineByName("nashorn");
76        final Bindings engineScope = e.getBindings(ScriptContext.ENGINE_SCOPE);
77
78        // check few ECMA standard built-in global properties
79        assertNotNull(engineScope.get("Object"));
80        assertNotNull(engineScope.get("TypeError"));
81        assertNotNull(engineScope.get("eval"));
82
83        // can access via ScriptEngine.get as well
84        assertNotNull(e.get("Object"));
85        assertNotNull(e.get("TypeError"));
86        assertNotNull(e.get("eval"));
87
88        // Access by either way should return same object
89        assertEquals(engineScope.get("Array"), e.get("Array"));
90        assertEquals(engineScope.get("EvalError"), e.get("EvalError"));
91        assertEquals(engineScope.get("undefined"), e.get("undefined"));
92
93        // try exposing a new variable from scope
94        engineScope.put("myVar", "foo");
95        try {
96            assertEquals(e.eval("myVar"), "foo");
97        } catch (final ScriptException se) {
98            se.printStackTrace();
99            fail(se.getMessage());
100        }
101
102        // update "myVar" in script an check the value from scope
103        try {
104            e.eval("myVar = 'nashorn';");
105        } catch (final ScriptException se) {
106            se.printStackTrace();
107            fail(se.getMessage());
108        }
109
110        // now check modified value from scope and engine
111        assertEquals(engineScope.get("myVar"), "nashorn");
112        assertEquals(e.get("myVar"), "nashorn");
113    }
114
115    @Test
116    public void multiGlobalTest() {
117        final ScriptEngineManager m = new ScriptEngineManager();
118        final ScriptEngine e = m.getEngineByName("nashorn");
119        final Bindings b = e.createBindings();
120        final ScriptContext newCtxt = new SimpleScriptContext();
121        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
122
123        try {
124            final Object obj1 = e.eval("Object");
125            final Object obj2 = e.eval("Object", newCtxt);
126            Assert.assertNotEquals(obj1, obj2);
127            Assert.assertNotNull(obj1);
128            Assert.assertNotNull(obj2);
129            Assert.assertEquals(obj1.toString(), obj2.toString());
130
131            e.eval("x = 'hello'");
132            e.eval("x = 'world'", newCtxt);
133            Object x1 = e.getContext().getAttribute("x");
134            Object x2 = newCtxt.getAttribute("x");
135            Assert.assertNotEquals(x1, x2);
136            Assert.assertEquals(x1, "hello");
137            Assert.assertEquals(x2, "world");
138
139            x1 = e.eval("x");
140            x2 = e.eval("x", newCtxt);
141            Assert.assertNotEquals(x1, x2);
142            Assert.assertEquals(x1, "hello");
143            Assert.assertEquals(x2, "world");
144
145            final ScriptContext origCtxt = e.getContext();
146            e.setContext(newCtxt);
147            e.eval("y = new Object()");
148            e.eval("y = new Object()", origCtxt);
149
150            final Object y1 = origCtxt.getAttribute("y");
151            final Object y2 = newCtxt.getAttribute("y");
152            Assert.assertNotEquals(y1, y2);
153            final Object yeval1 = e.eval("y");
154            final Object yeval2 = e.eval("y", origCtxt);
155            Assert.assertNotEquals(yeval1, yeval2);
156            Assert.assertEquals("[object Object]", y1.toString());
157            Assert.assertEquals("[object Object]", y2.toString());
158        } catch (final ScriptException se) {
159            se.printStackTrace();
160            fail(se.getMessage());
161        }
162    }
163
164    @Test
165    public void userEngineScopeBindingsTest() throws ScriptException {
166        final ScriptEngineManager m = new ScriptEngineManager();
167        final ScriptEngine e = m.getEngineByName("nashorn");
168        e.eval("function func() {}");
169
170        final ScriptContext newContext = new SimpleScriptContext();
171        newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
172        // we are using a new bindings - so it should have 'func' defined
173        final Object value = e.eval("typeof func", newContext);
174        assertTrue(value.equals("undefined"));
175    }
176
177    @Test
178    public void userEngineScopeBindingsNoLeakTest() throws ScriptException {
179        final ScriptEngineManager m = new ScriptEngineManager();
180        final ScriptEngine e = m.getEngineByName("nashorn");
181        final ScriptContext newContext = new SimpleScriptContext();
182        newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
183        e.eval("function foo() {}", newContext);
184
185        // in the default context's ENGINE_SCOPE, 'foo' shouldn't exist
186        assertTrue(e.eval("typeof foo").equals("undefined"));
187    }
188
189    @Test
190    public void userEngineScopeBindingsRetentionTest() throws ScriptException {
191        final ScriptEngineManager m = new ScriptEngineManager();
192        final ScriptEngine e = m.getEngineByName("nashorn");
193        final ScriptContext newContext = new SimpleScriptContext();
194        newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
195        e.eval("function foo() {}", newContext);
196
197        // definition retained with user's ENGINE_SCOPE Binding
198        assertTrue(e.eval("typeof foo", newContext).equals("function"));
199
200        final Bindings oldBindings = newContext.getBindings(ScriptContext.ENGINE_SCOPE);
201        // but not in another ENGINE_SCOPE binding
202        newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
203        assertTrue(e.eval("typeof foo", newContext).equals("undefined"));
204
205        // restore ENGINE_SCOPE and check again
206        newContext.setBindings(oldBindings, ScriptContext.ENGINE_SCOPE);
207        assertTrue(e.eval("typeof foo", newContext).equals("function"));
208    }
209
210    @Test
211    // check that engine.js definitions are visible in all new global instances
212    public void checkBuiltinsInNewBindingsTest() throws ScriptException {
213        final ScriptEngineManager m = new ScriptEngineManager();
214        final ScriptEngine e = m.getEngineByName("nashorn");
215
216        // check default global instance has engine.js definitions
217        final Bindings g = (Bindings) e.eval("this");
218        Object value = g.get("__noSuchProperty__");
219        assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
220        value = g.get("print");
221        assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
222
223        // check new global instance created has engine.js definitions
224        final Bindings b = e.createBindings();
225        value = b.get("__noSuchProperty__");
226        assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
227        value = b.get("print");
228        assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
229
230        // put a mapping into GLOBAL_SCOPE
231        final Bindings globalScope = e.getContext().getBindings(ScriptContext.GLOBAL_SCOPE);
232        globalScope.put("x", "hello");
233
234        // GLOBAL_SCOPE mapping should be visible from default ScriptContext eval
235        assertTrue(e.eval("x").equals("hello"));
236
237        final ScriptContext ctx = new SimpleScriptContext();
238        ctx.setBindings(globalScope, ScriptContext.GLOBAL_SCOPE);
239        ctx.setBindings(b, ScriptContext.ENGINE_SCOPE);
240
241        // GLOBAL_SCOPE mapping should be visible from non-default ScriptContext eval
242        assertTrue(e.eval("x", ctx).equals("hello"));
243
244        // try some arbitray Bindings for ENGINE_SCOPE
245        final Bindings sb = new SimpleBindings();
246        ctx.setBindings(sb, ScriptContext.ENGINE_SCOPE);
247
248        // GLOBAL_SCOPE mapping should be visible from non-default ScriptContext eval
249        assertTrue(e.eval("x", ctx).equals("hello"));
250
251        // engine.js builtins are still defined even with arbitrary Bindings
252        assertTrue(e.eval("typeof print", ctx).equals("function"));
253        assertTrue(e.eval("typeof __noSuchProperty__", ctx).equals("function"));
254
255        // ENGINE_SCOPE definition should 'hide' GLOBAL_SCOPE definition
256        sb.put("x", "newX");
257        assertTrue(e.eval("x", ctx).equals("newX"));
258    }
259
260    /**
261     * Test multi-threaded access to defined global variables for shared script classes with multiple globals.
262     */
263    @Test
264    public static void multiThreadedVarTest() throws ScriptException, InterruptedException {
265        final ScriptEngineManager m = new ScriptEngineManager();
266        final ScriptEngine e = m.getEngineByName("nashorn");
267        final Bindings b = e.createBindings();
268        final ScriptContext origContext = e.getContext();
269        final ScriptContext newCtxt = new SimpleScriptContext();
270        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
271        final String sharedScript = "foo";
272
273        assertEquals(e.eval("var foo = 'original context';", origContext), null);
274        assertEquals(e.eval("var foo = 'new context';", newCtxt), null);
275
276        final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
277        final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
278        t1.start();
279        t2.start();
280        t1.join();
281        t2.join();
282
283        assertEquals(e.eval("var foo = 'newer context';", newCtxt), null);
284        final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
285        final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
286
287        t3.start();
288        t4.start();
289        t3.join();
290        t4.join();
291
292        assertEquals(e.eval(sharedScript), "original context");
293        assertEquals(e.eval(sharedScript, newCtxt), "newer context");
294    }
295
296    /**
297     * Test multi-threaded access to undefined global variables for shared script classes with multiple globals.
298     */
299    @Test
300    public static void multiThreadedGlobalTest() throws ScriptException, InterruptedException {
301        final ScriptEngineManager m = new ScriptEngineManager();
302        final ScriptEngine e = m.getEngineByName("nashorn");
303        final Bindings b = e.createBindings();
304        final ScriptContext origContext = e.getContext();
305        final ScriptContext newCtxt = new SimpleScriptContext();
306        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
307
308        assertEquals(e.eval("foo = 'original context';", origContext), "original context");
309        assertEquals(e.eval("foo = 'new context';", newCtxt), "new context");
310        final String sharedScript = "foo";
311
312        final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
313        final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
314        t1.start();
315        t2.start();
316        t1.join();
317        t2.join();
318
319        final Object obj3 = e.eval("delete foo; foo = 'newer context';", newCtxt);
320        assertEquals(obj3, "newer context");
321        final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
322        final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
323
324        t3.start();
325        t4.start();
326        t3.join();
327        t4.join();
328
329        Assert.assertEquals(e.eval(sharedScript), "original context");
330        Assert.assertEquals(e.eval(sharedScript, newCtxt), "newer context");
331    }
332
333    /**
334     * Test multi-threaded access using the postfix ++ operator for shared script classes with multiple globals.
335     */
336    @Test
337    public static void multiThreadedIncTest() throws ScriptException, InterruptedException {
338        final ScriptEngineManager m = new ScriptEngineManager();
339        final ScriptEngine e = m.getEngineByName("nashorn");
340        final Bindings b = e.createBindings();
341        final ScriptContext origContext = e.getContext();
342        final ScriptContext newCtxt = new SimpleScriptContext();
343        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
344
345        assertEquals(e.eval("var x = 0;", origContext), null);
346        assertEquals(e.eval("var x = 2;", newCtxt), null);
347        final String sharedScript = "x++;";
348
349        final Thread t1 = new Thread(new Runnable() {
350            @Override
351            public void run() {
352                try {
353                    for (int i = 0; i < 1000; i++) {
354                        assertEquals(e.eval(sharedScript, origContext), (double)i);
355                    }
356                } catch (final ScriptException se) {
357                    fail(se.toString());
358                }
359            }
360        });
361        final Thread t2 = new Thread(new Runnable() {
362            @Override
363            public void run() {
364                try {
365                    for (int i = 2; i < 1000; i++) {
366                        assertEquals(e.eval(sharedScript, newCtxt), (double)i);
367                    }
368                } catch (final ScriptException se) {
369                    fail(se.toString());
370                }
371            }
372        });
373        t1.start();
374        t2.start();
375        t1.join();
376        t2.join();
377    }
378
379    /**
380     * Test multi-threaded access to primitive prototype properties for shared script classes with multiple globals.
381     */
382    @Test
383    public static void multiThreadedPrimitiveTest() throws ScriptException, InterruptedException {
384        final ScriptEngineManager m = new ScriptEngineManager();
385        final ScriptEngine e = m.getEngineByName("nashorn");
386        final Bindings b = e.createBindings();
387        final ScriptContext origContext = e.getContext();
388        final ScriptContext newCtxt = new SimpleScriptContext();
389        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
390
391        final Object obj1 = e.eval("String.prototype.foo = 'original context';", origContext);
392        final Object obj2 = e.eval("String.prototype.foo = 'new context';", newCtxt);
393        assertEquals(obj1, "original context");
394        assertEquals(obj2, "new context");
395        final String sharedScript = "''.foo";
396
397        final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
398        final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
399        t1.start();
400        t2.start();
401        t1.join();
402        t2.join();
403
404        final Object obj3 = e.eval("delete String.prototype.foo; Object.prototype.foo = 'newer context';", newCtxt);
405        assertEquals(obj3, "newer context");
406        final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
407        final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
408
409        t3.start();
410        t4.start();
411        t3.join();
412        t4.join();
413
414        Assert.assertEquals(e.eval(sharedScript), "original context");
415        Assert.assertEquals(e.eval(sharedScript, newCtxt), "newer context");
416    }
417
418
419    /**
420     * Test multi-threaded access to prototype user accessor properties for shared script classes with multiple globals.
421     */
422    @Test
423    public static void multiThreadedAccessorTest() throws ScriptException, InterruptedException {
424        final ScriptEngineManager m = new ScriptEngineManager();
425        final ScriptEngine e = m.getEngineByName("nashorn");
426        final Bindings b = e.createBindings();
427        final ScriptContext origContext = e.getContext();
428        final ScriptContext newCtxt = new SimpleScriptContext();
429        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
430
431        e.eval("Object.defineProperty(Object.prototype, 'foo', { get: function() 'original context' })", origContext);
432        e.eval("Object.defineProperty(Object.prototype, 'foo', { get: function() 'new context', configurable: true })", newCtxt);
433        final String sharedScript = "({}).foo";
434
435        final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
436        final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
437        t1.start();
438        t2.start();
439        t1.join();
440        t2.join();
441
442        final Object obj3 = e.eval("delete Object.prototype.foo; Object.prototype.foo = 'newer context';", newCtxt);
443        assertEquals(obj3, "newer context");
444        final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
445        final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
446
447        t3.start();
448        t4.start();
449        t3.join();
450        t4.join();
451    }
452
453    /**
454     * Test multi-threaded access to primitive prototype user accessor properties for shared script classes with multiple globals.
455     */
456    @Test
457    public static void multiThreadedPrimitiveAccessorTest() throws ScriptException, InterruptedException {
458        final ScriptEngineManager m = new ScriptEngineManager();
459        final ScriptEngine e = m.getEngineByName("nashorn");
460        final Bindings b = e.createBindings();
461        final ScriptContext origContext = e.getContext();
462        final ScriptContext newCtxt = new SimpleScriptContext();
463        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
464
465        e.eval("Object.defineProperty(String.prototype, 'foo', { get: function() 'original context' })", origContext);
466        e.eval("Object.defineProperty(String.prototype, 'foo', { get: function() 'new context' })", newCtxt);
467        final String sharedScript = "''.foo";
468
469        final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
470        final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
471        t1.start();
472        t2.start();
473        t1.join();
474        t2.join();
475
476        final Object obj3 = e.eval("delete String.prototype.foo; Object.prototype.foo = 'newer context';", newCtxt);
477        assertEquals(obj3, "newer context");
478        final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
479        final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
480
481        t3.start();
482        t4.start();
483        t3.join();
484        t4.join();
485    }
486
487    /**
488     * Test multi-threaded scope function invocation for shared script classes with multiple globals.
489     */
490    @Test
491    public static void multiThreadedFunctionTest() throws ScriptException, InterruptedException {
492        final ScriptEngineManager m = new ScriptEngineManager();
493        final ScriptEngine e = m.getEngineByName("nashorn");
494        final Bindings b = e.createBindings();
495        final ScriptContext origContext = e.getContext();
496        final ScriptContext newCtxt = new SimpleScriptContext();
497        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
498
499        e.eval(new URLReader(ScopeTest.class.getResource("resources/func.js")), origContext);
500        assertEquals(origContext.getAttribute("scopeVar"), 1);
501        assertEquals(e.eval("scopeTest()"), 1);
502
503        e.eval(new URLReader(ScopeTest.class.getResource("resources/func.js")), newCtxt);
504        assertEquals(newCtxt.getAttribute("scopeVar"), 1);
505        assertEquals(e.eval("scopeTest();", newCtxt), 1);
506
507        assertEquals(e.eval("scopeVar = 3;", newCtxt), 3);
508        assertEquals(newCtxt.getAttribute("scopeVar"), 3);
509
510
511        final Thread t1 = new Thread(new ScriptRunner(e, origContext, "scopeTest()", 1, 1000));
512        final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, "scopeTest()", 3, 1000));
513
514        t1.start();
515        t2.start();
516        t1.join();
517        t2.join();
518
519    }
520
521    /**
522     * Test multi-threaded access to global getters and setters for shared script classes with multiple globals.
523     */
524    @Test
525    public static void getterSetterTest() throws ScriptException, InterruptedException {
526        final ScriptEngineManager m = new ScriptEngineManager();
527        final ScriptEngine e = m.getEngineByName("nashorn");
528        final Bindings b = e.createBindings();
529        final ScriptContext origContext = e.getContext();
530        final ScriptContext newCtxt = new SimpleScriptContext();
531        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
532        final String sharedScript = "accessor1";
533
534        e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), origContext);
535        assertEquals(e.eval("accessor1 = 1;"), 1);
536        assertEquals(e.eval(sharedScript), 1);
537
538        e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), newCtxt);
539        assertEquals(e.eval("accessor1 = 2;", newCtxt), 2);
540        assertEquals(e.eval(sharedScript, newCtxt), 2);
541
542
543        final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, 1, 1000));
544        final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, 2, 1000));
545
546        t1.start();
547        t2.start();
548        t1.join();
549        t2.join();
550
551        assertEquals(e.eval(sharedScript), 1);
552        assertEquals(e.eval(sharedScript, newCtxt), 2);
553        assertEquals(e.eval("v"), 1);
554        assertEquals(e.eval("v", newCtxt), 2);
555    }
556
557    /**
558     * Test multi-threaded access to global getters and setters for shared script classes with multiple globals.
559     */
560    @Test
561    public static void getterSetter2Test() throws ScriptException, InterruptedException {
562        final ScriptEngineManager m = new ScriptEngineManager();
563        final ScriptEngine e = m.getEngineByName("nashorn");
564        final Bindings b = e.createBindings();
565        final ScriptContext origContext = e.getContext();
566        final ScriptContext newCtxt = new SimpleScriptContext();
567        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
568        final String sharedScript = "accessor2";
569
570        e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), origContext);
571        assertEquals(e.eval("accessor2 = 1;"), 1);
572        assertEquals(e.eval(sharedScript), 1);
573
574        e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), newCtxt);
575        assertEquals(e.eval("accessor2 = 2;", newCtxt), 2);
576        assertEquals(e.eval(sharedScript, newCtxt), 2);
577
578
579        final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, 1, 1000));
580        final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, 2, 1000));
581
582        t1.start();
583        t2.start();
584        t1.join();
585        t2.join();
586
587        assertEquals(e.eval(sharedScript), 1);
588        assertEquals(e.eval(sharedScript, newCtxt), 2);
589        assertEquals(e.eval("x"), 1);
590        assertEquals(e.eval("x", newCtxt), 2);
591    }
592
593    // @bug 8058422: Users should be able to overwrite "context" and "engine" variables
594    @Test
595    public static void contextOverwriteTest() throws ScriptException {
596        final ScriptEngineManager m = new ScriptEngineManager();
597        final ScriptEngine e = m.getEngineByName("nashorn");
598        final Bindings b = new SimpleBindings();
599        b.put("context", "hello");
600        b.put("foo", 32);
601        final ScriptContext newCtxt = new SimpleScriptContext();
602        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
603        e.setContext(newCtxt);
604        assertEquals(e.eval("context"), "hello");
605        assertEquals(((Number)e.eval("foo")).intValue(), 32);
606    }
607
608    // @bug 8058422: Users should be able to overwrite "context" and "engine" variables
609    @Test
610    public static void contextOverwriteInScriptTest() throws ScriptException {
611        final ScriptEngineManager m = new ScriptEngineManager();
612        final ScriptEngine e = m.getEngineByName("nashorn");
613        e.put("foo", 32);
614
615        assertEquals(((Number)e.eval("foo")).intValue(), 32);
616        assertEquals(e.eval("context = 'bar'"), "bar");
617        assertEquals(((Number)e.eval("foo")).intValue(), 32);
618    }
619
620    // @bug 8058422: Users should be able to overwrite "context" and "engine" variables
621    @Test
622    public static void engineOverwriteTest() throws ScriptException {
623        final ScriptEngineManager m = new ScriptEngineManager();
624        final ScriptEngine e = m.getEngineByName("nashorn");
625        final Bindings b = new SimpleBindings();
626        b.put("engine", "hello");
627        b.put("foo", 32);
628        final ScriptContext newCtxt = new SimpleScriptContext();
629        newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
630        e.setContext(newCtxt);
631        assertEquals(e.eval("engine"), "hello");
632        assertEquals(((Number)e.eval("foo")).intValue(), 32);
633    }
634
635    // @bug 8058422: Users should be able to overwrite "context" and "engine" variables
636    @Test
637    public static void engineOverwriteInScriptTest() throws ScriptException {
638        final ScriptEngineManager m = new ScriptEngineManager();
639        final ScriptEngine e = m.getEngineByName("nashorn");
640        e.put("foo", 32);
641
642        assertEquals(((Number)e.eval("foo")).intValue(), 32);
643        assertEquals(e.eval("engine = 'bar'"), "bar");
644        assertEquals(((Number)e.eval("foo")).intValue(), 32);
645    }
646
647    // @bug 8044750: megamorphic getter for scope objects does not call __noSuchProperty__ hook
648    @Test
649    public static void testMegamorphicGetInGlobal() throws Exception {
650        final ScriptEngineManager m = new ScriptEngineManager();
651        final ScriptEngine engine = m.getEngineByName("nashorn");
652        final String script = "foo";
653        // "foo" is megamorphic because of different global scopes.
654        // Make sure ScriptContext variable search works even after
655        // it becomes megamorphic.
656        for (int index = 0; index < 25; index++) {
657            final Bindings bindings = new SimpleBindings();
658            bindings.put("foo", index);
659            final Number value = (Number)engine.eval(script, bindings);
660            assertEquals(index, value.intValue());
661        }
662    }
663
664    /**
665     * Test "slow" scopes involving {@code with} and {@code eval} statements for shared script classes with multiple globals.
666     * @throws ScriptException
667     * @throws InterruptedException
668     */
669    @Test
670    public static void testSlowScope() throws ScriptException, InterruptedException {
671        final ScriptEngineManager m = new ScriptEngineManager();
672        final ScriptEngine e = m.getEngineByName("nashorn");
673
674        for (int i = 0; i < 100; i++) {
675            final Bindings b = e.createBindings();
676            final ScriptContext ctxt = new SimpleScriptContext();
677            ctxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
678
679            e.eval(new URLReader(ScopeTest.class.getResource("resources/witheval.js")), ctxt);
680            assertEquals(e.eval("a", ctxt), 1);
681            assertEquals(b.get("a"), 1);
682            assertEquals(e.eval("b", ctxt), 3);
683            assertEquals(b.get("b"), 3);
684            assertEquals(e.eval("c", ctxt), 10);
685            assertEquals(b.get("c"), 10);
686        }
687    }
688
689    private static class ScriptRunner implements Runnable {
690
691        final ScriptEngine engine;
692        final ScriptContext context;
693        final String source;
694        final Object expected;
695        final int iterations;
696
697        ScriptRunner(final ScriptEngine engine, final ScriptContext context, final String source, final Object expected, final int iterations) {
698            this.engine = engine;
699            this.context = context;
700            this.source = source;
701            this.expected = expected;
702            this.iterations = iterations;
703        }
704
705        @Override
706        public void run() {
707            try {
708                for (int i = 0; i < iterations; i++) {
709                    assertEquals(engine.eval(source, context), expected);
710                }
711            } catch (final ScriptException se) {
712                throw new RuntimeException(se);
713            }
714        }
715    }
716
717    // @bug 8071678: NashornScriptEngine returns javax.script.ScriptContext instance
718    // with get/setAttribute methods insonsistent for GLOBAL_SCOPE
719    @Test
720    public void testGlobalScopeSearch() throws Exception {
721        final ScriptEngineManager m = new ScriptEngineManager();
722        final ScriptEngine e = m.getEngineByName("nashorn");
723        final ScriptContext c = e.getContext();
724        c.setAttribute("name1234", "value", ScriptContext.GLOBAL_SCOPE);
725        assertEquals(c.getAttribute("name1234"), "value");
726        assertEquals(c.getAttributesScope("name1234"),
727            ScriptContext.GLOBAL_SCOPE);
728    }
729
730    // @bug 8071594: NashornScriptEngine returns javax.script.ScriptContext instance
731    // which doesn't completely conform to the spec regarding exceptions throwing
732    @Test
733    public void testScriptContext_NPE_IAE() throws Exception {
734        final ScriptEngineManager m = new ScriptEngineManager();
735        final ScriptEngine e = m.getEngineByName("nashorn");
736        final ScriptContext c = e.getContext();
737        try {
738            c.getAttribute("");
739            throw new AssertionError("should have thrown IAE");
740        } catch (final IllegalArgumentException iae1) {}
741
742        try {
743            c.getAttribute(null);
744            throw new AssertionError("should have thrown NPE");
745        } catch (final NullPointerException npe1) {}
746
747        try {
748            c.getAttribute("", ScriptContext.ENGINE_SCOPE);
749            throw new AssertionError("should have thrown IAE");
750        } catch (final IllegalArgumentException iae2) {}
751
752        try {
753            c.getAttribute(null, ScriptContext.ENGINE_SCOPE);
754            throw new AssertionError("should have thrown NPE");
755        } catch (final NullPointerException npe2) {}
756
757        try {
758            c.removeAttribute("", ScriptContext.ENGINE_SCOPE);
759            throw new AssertionError("should have thrown IAE");
760        } catch (final IllegalArgumentException iae3) {}
761
762        try {
763            c.removeAttribute(null, ScriptContext.ENGINE_SCOPE);
764            throw new AssertionError("should have thrown NPE");
765        } catch (final NullPointerException npe3) {}
766
767        try {
768            c.setAttribute("", "value", ScriptContext.ENGINE_SCOPE);
769            throw new AssertionError("should have thrown IAE");
770        } catch (final IllegalArgumentException iae4) {}
771
772        try {
773            c.setAttribute(null, "value", ScriptContext.ENGINE_SCOPE);
774            throw new AssertionError("should have thrown NPE");
775        } catch (final NullPointerException npe4) {}
776
777        try {
778            c.getAttributesScope("");
779            throw new AssertionError("should have thrown IAE");
780        } catch (final IllegalArgumentException iae5) {}
781
782        try {
783            c.getAttributesScope(null);
784            throw new AssertionError("should have thrown NPE");
785        } catch (final NullPointerException npe5) {}
786    }
787
788    public static class RecursiveEval {
789        private final ScriptEngineFactory factory = new NashornScriptEngineFactory();
790        private final ScriptEngine engine = factory.getScriptEngine();
791        private final Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
792
793        public void program() throws ScriptException {
794            final ScriptContext sc = new SimpleScriptContext();
795            final Bindings global = new SimpleBindings();
796            sc.setBindings(global, ScriptContext.GLOBAL_SCOPE);
797            sc.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
798            global.put("text", "programText");
799            String value = engine.eval("text", sc).toString();
800            Assert.assertEquals(value, "programText");
801            engine.put("program", this);
802            engine.eval("program.method()");
803            // eval again from here!
804            value = engine.eval("text", sc).toString();
805            Assert.assertEquals(value, "programText");
806        }
807
808        public void method() throws ScriptException {
809            // a context with a new global bindings, same engine bindings
810            final ScriptContext sc = new SimpleScriptContext();
811            final Bindings global = new SimpleBindings();
812            sc.setBindings(global, ScriptContext.GLOBAL_SCOPE);
813            sc.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE);
814            global.put("text", "methodText");
815            final String value = engine.eval("text", sc).toString();
816            Assert.assertEquals(value, "methodText");
817        }
818    }
819
820    // @bug 8081609: engine.eval call from a java method which
821    // was called from a previous engine.eval results in wrong
822    // ScriptContext being used.
823    @Test
824    public void recursiveEvalCallScriptContextTest() throws ScriptException {
825        new RecursiveEval().program();
826    }
827
828    private static final String VAR_NAME = "myvar";
829
830    private static boolean lookupVar(final ScriptEngine engine, final String varName) {
831        try {
832            engine.eval(varName);
833            return true;
834        } catch (final ScriptException se) {
835            return false;
836        }
837    }
838
839    // @bug 8136544: Call site switching to megamorphic causes incorrect property read
840    @Test
841    public void megamorphicPropertyReadTest() throws ScriptException {
842        final NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
843        final ScriptEngine engine = factory.getScriptEngine();
844        final Bindings scope = engine.getBindings(ScriptContext.ENGINE_SCOPE);
845        boolean ret;
846
847        // Why 16 is the upper limit of this loop? The default nashorn dynalink megamorphic threshold is 16.
848        // See jdk.nashorn.internal.runtime.linker.Bootstrap.NASHORN_DEFAULT_UNSTABLE_RELINK_THRESHOLD
849        // We do, 'eval' of the same in this loop twice. So, 16*2 = 32 times that callsite in the script
850        // is exercised - much beyond the default megamorphic threshold.
851
852        for (int i = 0; i < 16; i++) {
853            scope.remove(VAR_NAME);
854            ret = lookupVar(engine, VAR_NAME);
855            assertFalse(ret, "Expected false in iteration " + i);
856            scope.put(VAR_NAME, "foo");
857            ret = lookupVar(engine, VAR_NAME);
858            assertTrue(ret, "Expected true in iteration " + i);
859        }
860    }
861
862    // @bug 8138616: invokeFunction fails if function calls a function defined in GLOBAL_SCOPE
863    @Test
864    public void invokeFunctionInGlobalScopeTest() throws Exception {
865         final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
866         final ScriptContext ctxt = engine.getContext();
867
868         // define a function called "func"
869         engine.eval("func = function() { return 42 }");
870
871         // move ENGINE_SCOPE Bindings to GLOBAL_SCOPE
872         ctxt.setBindings(ctxt.getBindings(ScriptContext.ENGINE_SCOPE), ScriptContext.GLOBAL_SCOPE);
873
874         // create a new Bindings and set as ENGINE_SCOPE
875         ctxt.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
876
877         // define new function that calls "func" now in GLOBAL_SCOPE
878         engine.eval("newfunc = function() { return func() }");
879
880         // call "newfunc" and check the return value
881         final Object value = ((Invocable)engine).invokeFunction("newfunc");
882         assertTrue(((Number)value).intValue() == 42);
883    }
884
885
886    // @bug 8138616: invokeFunction fails if function calls a function defined in GLOBAL_SCOPE
887    // variant of above that replaces default ScriptContext of the engine with a fresh instance!
888    @Test
889    public void invokeFunctionInGlobalScopeTest2() throws Exception {
890         final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
891
892         // create a new ScriptContext instance
893         final ScriptContext ctxt = new SimpleScriptContext();
894         // set it as 'default' ScriptContext
895         engine.setContext(ctxt);
896
897         // create a new Bindings and set as ENGINE_SCOPE
898         ctxt.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
899
900         // define a function called "func"
901         engine.eval("func = function() { return 42 }");
902
903         // move ENGINE_SCOPE Bindings to GLOBAL_SCOPE
904         ctxt.setBindings(ctxt.getBindings(ScriptContext.ENGINE_SCOPE), ScriptContext.GLOBAL_SCOPE);
905
906         // create a new Bindings and set as ENGINE_SCOPE
907         ctxt.setBindings(engine.createBindings(), ScriptContext.ENGINE_SCOPE);
908
909         // define new function that calls "func" now in GLOBAL_SCOPE
910         engine.eval("newfunc = function() { return func() }");
911
912         // call "newfunc" and check the return value
913         final Object value = ((Invocable)engine).invokeFunction("newfunc");
914         assertTrue(((Number)value).intValue() == 42);
915    }
916
917    // @bug 8150219 ReferenceError in 1.8.0_72
918    // When we create a Global for a non-default ScriptContext that needs one keep the
919    // ScriptContext associated with the Global so that invoke methods work as expected.
920    @Test
921    public void invokeFunctionWithCustomScriptContextTest() throws Exception {
922        final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
923
924        // create an engine and a ScriptContext, but don't set it as default
925        final ScriptContext scriptContext = new SimpleScriptContext();
926
927        // Set some value in the context
928        scriptContext.setAttribute("myString", "foo", ScriptContext.ENGINE_SCOPE);
929
930        // Evaluate script with custom context and get back a function
931        final String script = "function (c) { return myString.indexOf(c); }";
932        final CompiledScript compiledScript = ((Compilable)engine).compile(script);
933        final Object func = compiledScript.eval(scriptContext);
934
935        // Invoked function should be able to see context it was evaluated with
936        final Object result = ((Invocable) engine).invokeMethod(func, "call", func, "o", null);
937        assertTrue(((Number)result).intValue() == 1);
938    }
939}
940