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 package jdk.nashorn.api.scripting;
  26 
  27 import javax.script.Bindings;
  28 import javax.script.ScriptContext;
  29 import javax.script.ScriptEngine;
  30 import javax.script.ScriptEngineManager;
  31 import javax.script.ScriptException;
  32 import javax.script.SimpleBindings;
  33 import javax.script.SimpleScriptContext;
  34 import org.testng.Assert;
  35 import static org.testng.Assert.assertEquals;
  36 import static org.testng.Assert.assertNotNull;
  37 import static org.testng.Assert.assertTrue;
  38 import static org.testng.Assert.fail;
  39 import org.testng.annotations.Test;
  40 
  41 /**
  42  * Tests for jsr223 Bindings "scope" (engine, global scopes)
  43  */
  44 public class ScopeTest {
  45 
  46     @Test
  47     public void createBindingsTest() {
  48         final ScriptEngineManager m = new ScriptEngineManager();
  49         final ScriptEngine e = m.getEngineByName("nashorn");
  50         Bindings b = e.createBindings();
  51         b.put("foo", 42.0);
  52         Object res = null;
  53         try {
  54             res = e.eval("foo == 42.0", b);
  55         } catch (final ScriptException | NullPointerException se) {
  56             se.printStackTrace();
  57             fail(se.getMessage());
  58         }
  59 
  60         assertEquals(res, Boolean.TRUE);
  61     }
  62 
  63     @Test
  64     public void engineScopeTest() {
  65         final ScriptEngineManager m = new ScriptEngineManager();
  66         final ScriptEngine e = m.getEngineByName("nashorn");
  67         Bindings engineScope = e.getBindings(ScriptContext.ENGINE_SCOPE);
  68 
  69         // check few ECMA standard built-in global properties
  70         assertNotNull(engineScope.get("Object"));
  71         assertNotNull(engineScope.get("TypeError"));
  72         assertNotNull(engineScope.get("eval"));
  73 
  74         // can access via ScriptEngine.get as well
  75         assertNotNull(e.get("Object"));
  76         assertNotNull(e.get("TypeError"));
  77         assertNotNull(e.get("eval"));
  78 
  79         // Access by either way should return same object
  80         assertEquals(engineScope.get("Array"), e.get("Array"));
  81         assertEquals(engineScope.get("EvalError"), e.get("EvalError"));
  82         assertEquals(engineScope.get("undefined"), e.get("undefined"));
  83 
  84         // try exposing a new variable from scope
  85         engineScope.put("myVar", "foo");
  86         try {
  87             assertEquals(e.eval("myVar"), "foo");
  88         } catch (final ScriptException se) {
  89             se.printStackTrace();
  90             fail(se.getMessage());
  91         }
  92 
  93         // update "myVar" in script an check the value from scope
  94         try {
  95             e.eval("myVar = 'nashorn';");
  96         } catch (final ScriptException se) {
  97             se.printStackTrace();
  98             fail(se.getMessage());
  99         }
 100 
 101         // now check modified value from scope and engine
 102         assertEquals(engineScope.get("myVar"), "nashorn");
 103         assertEquals(e.get("myVar"), "nashorn");
 104     }
 105 
 106     @Test
 107     public void multiGlobalTest() {
 108         final ScriptEngineManager m = new ScriptEngineManager();
 109         final ScriptEngine e = m.getEngineByName("nashorn");
 110         final Bindings b = e.createBindings();
 111         final ScriptContext newCtxt = new SimpleScriptContext();
 112         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 113 
 114         try {
 115             Object obj1 = e.eval("Object");
 116             Object obj2 = e.eval("Object", newCtxt);
 117             Assert.assertNotEquals(obj1, obj2);
 118             Assert.assertNotNull(obj1);
 119             Assert.assertNotNull(obj2);
 120             Assert.assertEquals(obj1.toString(), obj2.toString());
 121 
 122             e.eval("x = 'hello'");
 123             e.eval("x = 'world'", newCtxt);
 124             Object x1 = e.getContext().getAttribute("x");
 125             Object x2 = newCtxt.getAttribute("x");
 126             Assert.assertNotEquals(x1, x2);
 127             Assert.assertEquals(x1, "hello");
 128             Assert.assertEquals(x2, "world");
 129 
 130             x1 = e.eval("x");
 131             x2 = e.eval("x", newCtxt);
 132             Assert.assertNotEquals(x1, x2);
 133             Assert.assertEquals(x1, "hello");
 134             Assert.assertEquals(x2, "world");
 135 
 136             final ScriptContext origCtxt = e.getContext();
 137             e.setContext(newCtxt);
 138             e.eval("y = new Object()");
 139             e.eval("y = new Object()", origCtxt);
 140 
 141             Object y1 = origCtxt.getAttribute("y");
 142             Object y2 = newCtxt.getAttribute("y");
 143             Assert.assertNotEquals(y1, y2);
 144             Assert.assertNotEquals(e.eval("y"), e.eval("y", origCtxt));
 145             Assert.assertEquals("[object Object]", y1.toString());
 146             Assert.assertEquals("[object Object]", y2.toString());
 147         } catch (final ScriptException se) {
 148             se.printStackTrace();
 149             fail(se.getMessage());
 150         }
 151     }
 152 
 153     @Test
 154     public void userEngineScopeBindingsTest() throws ScriptException {
 155         final ScriptEngineManager m = new ScriptEngineManager();
 156         final ScriptEngine e = m.getEngineByName("nashorn");
 157         e.eval("function func() {}");
 158 
 159         final ScriptContext newContext = new SimpleScriptContext();
 160         newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
 161         // we are using a new bindings - so it should have 'func' defined
 162         Object value = e.eval("typeof func", newContext);
 163         assertTrue(value.equals("undefined"));
 164     }
 165 
 166     @Test
 167     public void userEngineScopeBindingsNoLeakTest() throws ScriptException {
 168         final ScriptEngineManager m = new ScriptEngineManager();
 169         final ScriptEngine e = m.getEngineByName("nashorn");
 170         final ScriptContext newContext = new SimpleScriptContext();
 171         newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
 172         e.eval("function foo() {}", newContext);
 173 
 174         // in the default context's ENGINE_SCOPE, 'foo' shouldn't exist
 175         assertTrue(e.eval("typeof foo").equals("undefined"));
 176     }
 177 
 178     @Test
 179     public void userEngineScopeBindingsRetentionTest() throws ScriptException {
 180         final ScriptEngineManager m = new ScriptEngineManager();
 181         final ScriptEngine e = m.getEngineByName("nashorn");
 182         final ScriptContext newContext = new SimpleScriptContext();
 183         newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
 184         e.eval("function foo() {}", newContext);
 185 
 186         // definition retained with user's ENGINE_SCOPE Binding
 187         assertTrue(e.eval("typeof foo", newContext).equals("function"));
 188 
 189         final Bindings oldBindings = newContext.getBindings(ScriptContext.ENGINE_SCOPE);
 190         // but not in another ENGINE_SCOPE binding
 191         newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
 192         assertTrue(e.eval("typeof foo", newContext).equals("undefined"));
 193 
 194         // restore ENGINE_SCOPE and check again
 195         newContext.setBindings(oldBindings, ScriptContext.ENGINE_SCOPE);
 196         assertTrue(e.eval("typeof foo", newContext).equals("function"));
 197     }
 198 
 199     @Test
 200     // check that engine.js definitions are visible in all new global instances
 201     public void checkBuiltinsInNewBindingsTest() throws ScriptException {
 202         final ScriptEngineManager m = new ScriptEngineManager();
 203         final ScriptEngine e = m.getEngineByName("nashorn");
 204 
 205         // check default global instance has engine.js definitions
 206         final Bindings g = (Bindings) e.eval("this");
 207         Object value = g.get("__noSuchProperty__");
 208         assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
 209         value = g.get("print");
 210         assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
 211 
 212         // check new global instance created has engine.js definitions
 213         Bindings b = e.createBindings();
 214         value = b.get("__noSuchProperty__");
 215         assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
 216         value = b.get("print");
 217         assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
 218 
 219         // put a mapping into GLOBAL_SCOPE
 220         final Bindings globalScope = e.getContext().getBindings(ScriptContext.GLOBAL_SCOPE);
 221         globalScope.put("x", "hello");
 222 
 223         // GLOBAL_SCOPE mapping should be visible from default ScriptContext eval
 224         assertTrue(e.eval("x").equals("hello"));
 225 
 226         final ScriptContext ctx = new SimpleScriptContext();
 227         ctx.setBindings(globalScope, ScriptContext.GLOBAL_SCOPE);
 228         ctx.setBindings(b, ScriptContext.ENGINE_SCOPE);
 229 
 230         // GLOBAL_SCOPE mapping should be visible from non-default ScriptContext eval
 231         assertTrue(e.eval("x", ctx).equals("hello"));
 232 
 233         // try some arbitray Bindings for ENGINE_SCOPE
 234         Bindings sb = new SimpleBindings();
 235         ctx.setBindings(sb, ScriptContext.ENGINE_SCOPE);
 236 
 237         // GLOBAL_SCOPE mapping should be visible from non-default ScriptContext eval
 238         assertTrue(e.eval("x", ctx).equals("hello"));
 239 
 240         // engine.js builtins are still defined even with arbitrary Bindings
 241         assertTrue(e.eval("typeof print", ctx).equals("function"));
 242         assertTrue(e.eval("typeof __noSuchProperty__", ctx).equals("function"));
 243 
 244         // ENGINE_SCOPE definition should 'hide' GLOBAL_SCOPE definition
 245         sb.put("x", "newX");
 246         assertTrue(e.eval("x", ctx).equals("newX"));
 247     }
 248 
 249     /**
 250      * Test multi-threaded access to defined global variables for shared script classes with multiple globals.
 251      */
 252     @Test
 253     public static void multiThreadedVarTest() throws ScriptException, InterruptedException {
 254         final ScriptEngineManager m = new ScriptEngineManager();
 255         final ScriptEngine e = m.getEngineByName("nashorn");
 256         final Bindings b = e.createBindings();
 257         final ScriptContext origContext = e.getContext();
 258         final ScriptContext newCtxt = new SimpleScriptContext();
 259         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 260         final String sharedScript = "foo";
 261 
 262         assertEquals(e.eval("var foo = 'original context';", origContext), null);
 263         assertEquals(e.eval("var foo = 'new context';", newCtxt), null);
 264 
 265         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 266         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
 267         t1.start();
 268         t2.start();
 269         t1.join();
 270         t2.join();
 271 
 272         assertEquals(e.eval("var foo = 'newer context';", newCtxt), null);
 273         final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 274         final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
 275 
 276         t3.start();
 277         t4.start();
 278         t3.join();
 279         t4.join();
 280 
 281         assertEquals(e.eval(sharedScript), "original context");
 282         assertEquals(e.eval(sharedScript, newCtxt), "newer context");
 283     }
 284 
 285     /**
 286      * Test multi-threaded access to undefined global variables for shared script classes with multiple globals.
 287      */
 288     @Test
 289     public static void multiThreadedGlobalTest() throws ScriptException, InterruptedException {
 290         final ScriptEngineManager m = new ScriptEngineManager();
 291         final ScriptEngine e = m.getEngineByName("nashorn");
 292         final Bindings b = e.createBindings();
 293         final ScriptContext origContext = e.getContext();
 294         final ScriptContext newCtxt = new SimpleScriptContext();
 295         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 296 
 297         assertEquals(e.eval("foo = 'original context';", origContext), "original context");
 298         assertEquals(e.eval("foo = 'new context';", newCtxt), "new context");
 299         final String sharedScript = "foo";
 300 
 301         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 302         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
 303         t1.start();
 304         t2.start();
 305         t1.join();
 306         t2.join();
 307 
 308         Object obj3 = e.eval("delete foo; foo = 'newer context';", newCtxt);
 309         assertEquals(obj3, "newer context");
 310         final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 311         final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
 312 
 313         t3.start();
 314         t4.start();
 315         t3.join();
 316         t4.join();
 317 
 318         Assert.assertEquals(e.eval(sharedScript), "original context");
 319         Assert.assertEquals(e.eval(sharedScript, newCtxt), "newer context");
 320     }
 321 
 322     /**
 323      * Test multi-threaded access using the postfix ++ operator for shared script classes with multiple globals.
 324      */
 325     @Test
 326     public static void multiThreadedIncTest() throws ScriptException, InterruptedException {
 327         final ScriptEngineManager m = new ScriptEngineManager();
 328         final ScriptEngine e = m.getEngineByName("nashorn");
 329         final Bindings b = e.createBindings();
 330         final ScriptContext origContext = e.getContext();
 331         final ScriptContext newCtxt = new SimpleScriptContext();
 332         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 333 
 334         assertEquals(e.eval("var x = 0;", origContext), null);
 335         assertEquals(e.eval("var x = 2;", newCtxt), null);
 336         final String sharedScript = "x++;";
 337 
 338         final Thread t1 = new Thread(new Runnable() {
 339             @Override
 340             public void run() {
 341                 try {
 342                     for (int i = 0; i < 1000; i++) {
 343                         assertEquals(e.eval(sharedScript, origContext), (double)i);
 344                     }
 345                 } catch (ScriptException se) {
 346                     fail(se.toString());
 347                 }
 348             }
 349         });
 350         final Thread t2 = new Thread(new Runnable() {
 351             @Override
 352             public void run() {
 353                 try {
 354                     for (int i = 2; i < 1000; i++) {
 355                         assertEquals(e.eval(sharedScript, newCtxt), (double)i);
 356                     }
 357                 } catch (ScriptException se) {
 358                     fail(se.toString());
 359                 }
 360             }
 361         });
 362         t1.start();
 363         t2.start();
 364         t1.join();
 365         t2.join();
 366     }
 367 
 368     /**
 369      * Test multi-threaded access to primitive prototype properties for shared script classes with multiple globals.
 370      */
 371     @Test
 372     public static void multiThreadedPrimitiveTest() throws ScriptException, InterruptedException {
 373         final ScriptEngineManager m = new ScriptEngineManager();
 374         final ScriptEngine e = m.getEngineByName("nashorn");
 375         final Bindings b = e.createBindings();
 376         final ScriptContext origContext = e.getContext();
 377         final ScriptContext newCtxt = new SimpleScriptContext();
 378         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 379 
 380         Object obj1 = e.eval("String.prototype.foo = 'original context';", origContext);
 381         Object obj2 = e.eval("String.prototype.foo = 'new context';", newCtxt);
 382         assertEquals(obj1, "original context");
 383         assertEquals(obj2, "new context");
 384         final String sharedScript = "''.foo";
 385 
 386         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 387         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
 388         t1.start();
 389         t2.start();
 390         t1.join();
 391         t2.join();
 392 
 393         Object obj3 = e.eval("delete String.prototype.foo; Object.prototype.foo = 'newer context';", newCtxt);
 394         assertEquals(obj3, "newer context");
 395         final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 396         final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
 397 
 398         t3.start();
 399         t4.start();
 400         t3.join();
 401         t4.join();
 402 
 403         Assert.assertEquals(e.eval(sharedScript), "original context");
 404         Assert.assertEquals(e.eval(sharedScript, newCtxt), "newer context");
 405     }
 406 
 407     /**
 408      * Test multi-threaded scope function invocation for shared script classes with multiple globals.
 409      */
 410     @Test
 411     public static void multiThreadedFunctionTest() throws ScriptException, InterruptedException {
 412         final ScriptEngineManager m = new ScriptEngineManager();
 413         final ScriptEngine e = m.getEngineByName("nashorn");
 414         final Bindings b = e.createBindings();
 415         final ScriptContext origContext = e.getContext();
 416         final ScriptContext newCtxt = new SimpleScriptContext();
 417         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 418 
 419         e.eval(new URLReader(ScopeTest.class.getResource("resources/func.js")), origContext);
 420         assertEquals(origContext.getAttribute("scopeVar"), 1);
 421         assertEquals(e.eval("scopeTest()"), 1);
 422 
 423         e.eval(new URLReader(ScopeTest.class.getResource("resources/func.js")), newCtxt);
 424         assertEquals(newCtxt.getAttribute("scopeVar"), 1);
 425         assertEquals(e.eval("scopeTest();", newCtxt), 1);
 426 
 427         assertEquals(e.eval("scopeVar = 3;", newCtxt), 3);
 428         assertEquals(newCtxt.getAttribute("scopeVar"), 3);
 429 
 430 
 431         final Thread t1 = new Thread(new ScriptRunner(e, origContext, "scopeTest()", 1, 1000));
 432         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, "scopeTest()", 3, 1000));
 433 
 434         t1.start();
 435         t2.start();
 436         t1.join();
 437         t2.join();
 438 
 439     }
 440 
 441     /**
 442      * Test multi-threaded access to global getters and setters for shared script classes with multiple globals.
 443      */
 444     @Test
 445     public static void getterSetterTest() throws ScriptException, InterruptedException {
 446         final ScriptEngineManager m = new ScriptEngineManager();
 447         final ScriptEngine e = m.getEngineByName("nashorn");
 448         final Bindings b = e.createBindings();
 449         final ScriptContext origContext = e.getContext();
 450         final ScriptContext newCtxt = new SimpleScriptContext();
 451         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 452         final String sharedScript = "accessor1";
 453 
 454         e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), origContext);
 455         assertEquals(e.eval("accessor1 = 1;"), 1);
 456         assertEquals(e.eval(sharedScript), 1);
 457 
 458         e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), newCtxt);
 459         assertEquals(e.eval("accessor1 = 2;", newCtxt), 2);
 460         assertEquals(e.eval(sharedScript, newCtxt), 2);
 461 
 462 
 463         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, 1, 1000));
 464         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, 2, 1000));
 465 
 466         t1.start();
 467         t2.start();
 468         t1.join();
 469         t2.join();
 470 
 471         assertEquals(e.eval(sharedScript), 1);
 472         assertEquals(e.eval(sharedScript, newCtxt), 2);
 473         assertEquals(e.eval("v"), 1);
 474         assertEquals(e.eval("v", newCtxt), 2);
 475     }
 476 
 477     /**
 478      * Test multi-threaded access to global getters and setters for shared script classes with multiple globals.
 479      */
 480     @Test
 481     public static void getterSetter2Test() throws ScriptException, InterruptedException {
 482         final ScriptEngineManager m = new ScriptEngineManager();
 483         final ScriptEngine e = m.getEngineByName("nashorn");
 484         final Bindings b = e.createBindings();
 485         final ScriptContext origContext = e.getContext();
 486         final ScriptContext newCtxt = new SimpleScriptContext();
 487         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 488         final String sharedScript = "accessor2";
 489 
 490         e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), origContext);
 491         assertEquals(e.eval("accessor2 = 1;"), 1);
 492         assertEquals(e.eval(sharedScript), 1);
 493 
 494         e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), newCtxt);
 495         assertEquals(e.eval("accessor2 = 2;", newCtxt), 2);
 496         assertEquals(e.eval(sharedScript, newCtxt), 2);
 497 
 498 
 499         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, 1, 1000));
 500         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, 2, 1000));
 501 
 502         t1.start();
 503         t2.start();
 504         t1.join();
 505         t2.join();
 506 
 507         assertEquals(e.eval(sharedScript), 1);
 508         assertEquals(e.eval(sharedScript, newCtxt), 2);
 509         assertEquals(e.eval("x"), 1);
 510         assertEquals(e.eval("x", newCtxt), 2);
 511     }
 512 
 513     /**
 514      * Test "slow" scopes involving {@code with} and {@code eval} statements for shared script classes with multiple globals.
 515      */
 516     @Test
 517     public static void testSlowScope() throws ScriptException, InterruptedException {
 518         final ScriptEngineManager m = new ScriptEngineManager();
 519         final ScriptEngine e = m.getEngineByName("nashorn");
 520 
 521         for (int i = 0; i < 100; i++) {
 522             final Bindings b = e.createBindings();
 523             final ScriptContext ctxt = new SimpleScriptContext();
 524             ctxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 525 
 526             e.eval(new URLReader(ScopeTest.class.getResource("resources/witheval.js")), ctxt);
 527             assertEquals(e.eval("a", ctxt), 1);
 528             assertEquals(b.get("a"), 1);
 529             assertEquals(e.eval("b", ctxt), 3);
 530             assertEquals(b.get("b"), 3);
 531             assertEquals(e.eval("c", ctxt), 10);
 532             assertEquals(b.get("c"), 10);
 533         }
 534     }
 535 
 536     private static class ScriptRunner implements Runnable {
 537 
 538         final ScriptEngine engine;
 539         final ScriptContext context;
 540         final String source;
 541         final Object expected;
 542         final int iterations;
 543 
 544         ScriptRunner(final ScriptEngine engine, final ScriptContext context, final String source, final Object expected, final int iterations) {
 545             this.engine = engine;
 546             this.context = context;
 547             this.source = source;
 548             this.expected = expected;
 549             this.iterations = iterations;
 550         }
 551 
 552         @Override
 553         public void run() {
 554             try {
 555                 for (int i = 0; i < iterations; i++) {
 556                     assertEquals(engine.eval(source, context), expected);
 557                 }
 558             } catch (ScriptException se) {
 559                 throw new RuntimeException(se);
 560             }
 561         }
 562     }
 563 
 564 }