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         final 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         final 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             final Object obj1 = e.eval("Object");
 116             final 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             final Object y1 = origCtxt.getAttribute("y");
 142             final Object y2 = newCtxt.getAttribute("y");
 143             Assert.assertNotEquals(y1, y2);
 144             final Object yeval1 = e.eval("y");
 145             final Object yeval2 = e.eval("y", origCtxt);
 146             Assert.assertNotEquals(yeval1, yeval2);
 147             Assert.assertEquals("[object Object]", y1.toString());
 148             Assert.assertEquals("[object Object]", y2.toString());
 149         } catch (final ScriptException se) {
 150             se.printStackTrace();
 151             fail(se.getMessage());
 152         }
 153     }
 154 
 155     @Test
 156     public void userEngineScopeBindingsTest() throws ScriptException {
 157         final ScriptEngineManager m = new ScriptEngineManager();
 158         final ScriptEngine e = m.getEngineByName("nashorn");
 159         e.eval("function func() {}");
 160 
 161         final ScriptContext newContext = new SimpleScriptContext();
 162         newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
 163         // we are using a new bindings - so it should have 'func' defined
 164         final Object value = e.eval("typeof func", newContext);
 165         assertTrue(value.equals("undefined"));
 166     }
 167 
 168     @Test
 169     public void userEngineScopeBindingsNoLeakTest() throws ScriptException {
 170         final ScriptEngineManager m = new ScriptEngineManager();
 171         final ScriptEngine e = m.getEngineByName("nashorn");
 172         final ScriptContext newContext = new SimpleScriptContext();
 173         newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
 174         e.eval("function foo() {}", newContext);
 175 
 176         // in the default context's ENGINE_SCOPE, 'foo' shouldn't exist
 177         assertTrue(e.eval("typeof foo").equals("undefined"));
 178     }
 179 
 180     @Test
 181     public void userEngineScopeBindingsRetentionTest() throws ScriptException {
 182         final ScriptEngineManager m = new ScriptEngineManager();
 183         final ScriptEngine e = m.getEngineByName("nashorn");
 184         final ScriptContext newContext = new SimpleScriptContext();
 185         newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
 186         e.eval("function foo() {}", newContext);
 187 
 188         // definition retained with user's ENGINE_SCOPE Binding
 189         assertTrue(e.eval("typeof foo", newContext).equals("function"));
 190 
 191         final Bindings oldBindings = newContext.getBindings(ScriptContext.ENGINE_SCOPE);
 192         // but not in another ENGINE_SCOPE binding
 193         newContext.setBindings(new SimpleBindings(), ScriptContext.ENGINE_SCOPE);
 194         assertTrue(e.eval("typeof foo", newContext).equals("undefined"));
 195 
 196         // restore ENGINE_SCOPE and check again
 197         newContext.setBindings(oldBindings, ScriptContext.ENGINE_SCOPE);
 198         assertTrue(e.eval("typeof foo", newContext).equals("function"));
 199     }
 200 
 201     @Test
 202     // check that engine.js definitions are visible in all new global instances
 203     public void checkBuiltinsInNewBindingsTest() throws ScriptException {
 204         final ScriptEngineManager m = new ScriptEngineManager();
 205         final ScriptEngine e = m.getEngineByName("nashorn");
 206 
 207         // check default global instance has engine.js definitions
 208         final Bindings g = (Bindings) e.eval("this");
 209         Object value = g.get("__noSuchProperty__");
 210         assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
 211         value = g.get("print");
 212         assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
 213 
 214         // check new global instance created has engine.js definitions
 215         final Bindings b = e.createBindings();
 216         value = b.get("__noSuchProperty__");
 217         assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
 218         value = b.get("print");
 219         assertTrue(value instanceof ScriptObjectMirror && ((ScriptObjectMirror)value).isFunction());
 220 
 221         // put a mapping into GLOBAL_SCOPE
 222         final Bindings globalScope = e.getContext().getBindings(ScriptContext.GLOBAL_SCOPE);
 223         globalScope.put("x", "hello");
 224 
 225         // GLOBAL_SCOPE mapping should be visible from default ScriptContext eval
 226         assertTrue(e.eval("x").equals("hello"));
 227 
 228         final ScriptContext ctx = new SimpleScriptContext();
 229         ctx.setBindings(globalScope, ScriptContext.GLOBAL_SCOPE);
 230         ctx.setBindings(b, ScriptContext.ENGINE_SCOPE);
 231 
 232         // GLOBAL_SCOPE mapping should be visible from non-default ScriptContext eval
 233         assertTrue(e.eval("x", ctx).equals("hello"));
 234 
 235         // try some arbitray Bindings for ENGINE_SCOPE
 236         final Bindings sb = new SimpleBindings();
 237         ctx.setBindings(sb, ScriptContext.ENGINE_SCOPE);
 238 
 239         // GLOBAL_SCOPE mapping should be visible from non-default ScriptContext eval
 240         assertTrue(e.eval("x", ctx).equals("hello"));
 241 
 242         // engine.js builtins are still defined even with arbitrary Bindings
 243         assertTrue(e.eval("typeof print", ctx).equals("function"));
 244         assertTrue(e.eval("typeof __noSuchProperty__", ctx).equals("function"));
 245 
 246         // ENGINE_SCOPE definition should 'hide' GLOBAL_SCOPE definition
 247         sb.put("x", "newX");
 248         assertTrue(e.eval("x", ctx).equals("newX"));
 249     }
 250 
 251     /**
 252      * Test multi-threaded access to defined global variables for shared script classes with multiple globals.
 253      */
 254     @Test
 255     public static void multiThreadedVarTest() throws ScriptException, InterruptedException {
 256         final ScriptEngineManager m = new ScriptEngineManager();
 257         final ScriptEngine e = m.getEngineByName("nashorn");
 258         final Bindings b = e.createBindings();
 259         final ScriptContext origContext = e.getContext();
 260         final ScriptContext newCtxt = new SimpleScriptContext();
 261         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 262         final String sharedScript = "foo";
 263 
 264         assertEquals(e.eval("var foo = 'original context';", origContext), null);
 265         assertEquals(e.eval("var foo = 'new context';", newCtxt), null);
 266 
 267         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 268         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
 269         t1.start();
 270         t2.start();
 271         t1.join();
 272         t2.join();
 273 
 274         assertEquals(e.eval("var foo = 'newer context';", newCtxt), null);
 275         final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 276         final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
 277 
 278         t3.start();
 279         t4.start();
 280         t3.join();
 281         t4.join();
 282 
 283         assertEquals(e.eval(sharedScript), "original context");
 284         assertEquals(e.eval(sharedScript, newCtxt), "newer context");
 285     }
 286 
 287     /**
 288      * Test multi-threaded access to undefined global variables for shared script classes with multiple globals.
 289      */
 290     @Test
 291     public static void multiThreadedGlobalTest() throws ScriptException, InterruptedException {
 292         final ScriptEngineManager m = new ScriptEngineManager();
 293         final ScriptEngine e = m.getEngineByName("nashorn");
 294         final Bindings b = e.createBindings();
 295         final ScriptContext origContext = e.getContext();
 296         final ScriptContext newCtxt = new SimpleScriptContext();
 297         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 298 
 299         assertEquals(e.eval("foo = 'original context';", origContext), "original context");
 300         assertEquals(e.eval("foo = 'new context';", newCtxt), "new context");
 301         final String sharedScript = "foo";
 302 
 303         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 304         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
 305         t1.start();
 306         t2.start();
 307         t1.join();
 308         t2.join();
 309 
 310         final Object obj3 = e.eval("delete foo; foo = 'newer context';", newCtxt);
 311         assertEquals(obj3, "newer context");
 312         final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 313         final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
 314 
 315         t3.start();
 316         t4.start();
 317         t3.join();
 318         t4.join();
 319 
 320         Assert.assertEquals(e.eval(sharedScript), "original context");
 321         Assert.assertEquals(e.eval(sharedScript, newCtxt), "newer context");
 322     }
 323 
 324     /**
 325      * Test multi-threaded access using the postfix ++ operator for shared script classes with multiple globals.
 326      */
 327     @Test
 328     public static void multiThreadedIncTest() throws ScriptException, InterruptedException {
 329         final ScriptEngineManager m = new ScriptEngineManager();
 330         final ScriptEngine e = m.getEngineByName("nashorn");
 331         final Bindings b = e.createBindings();
 332         final ScriptContext origContext = e.getContext();
 333         final ScriptContext newCtxt = new SimpleScriptContext();
 334         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 335 
 336         assertEquals(e.eval("var x = 0;", origContext), null);
 337         assertEquals(e.eval("var x = 2;", newCtxt), null);
 338         final String sharedScript = "x++;";
 339 
 340         final Thread t1 = new Thread(new Runnable() {
 341             @Override
 342             public void run() {
 343                 try {
 344                     for (int i = 0; i < 1000; i++) {
 345                         assertEquals(e.eval(sharedScript, origContext), (double)i);
 346                     }
 347                 } catch (final ScriptException se) {
 348                     fail(se.toString());
 349                 }
 350             }
 351         });
 352         final Thread t2 = new Thread(new Runnable() {
 353             @Override
 354             public void run() {
 355                 try {
 356                     for (int i = 2; i < 1000; i++) {
 357                         assertEquals(e.eval(sharedScript, newCtxt), (double)i);
 358                     }
 359                 } catch (final ScriptException se) {
 360                     fail(se.toString());
 361                 }
 362             }
 363         });
 364         t1.start();
 365         t2.start();
 366         t1.join();
 367         t2.join();
 368     }
 369 
 370     /**
 371      * Test multi-threaded access to primitive prototype properties for shared script classes with multiple globals.
 372      */
 373     @Test
 374     public static void multiThreadedPrimitiveTest() throws ScriptException, InterruptedException {
 375         final ScriptEngineManager m = new ScriptEngineManager();
 376         final ScriptEngine e = m.getEngineByName("nashorn");
 377         final Bindings b = e.createBindings();
 378         final ScriptContext origContext = e.getContext();
 379         final ScriptContext newCtxt = new SimpleScriptContext();
 380         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 381 
 382         final Object obj1 = e.eval("String.prototype.foo = 'original context';", origContext);
 383         final Object obj2 = e.eval("String.prototype.foo = 'new context';", newCtxt);
 384         assertEquals(obj1, "original context");
 385         assertEquals(obj2, "new context");
 386         final String sharedScript = "''.foo";
 387 
 388         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 389         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "new context", 1000));
 390         t1.start();
 391         t2.start();
 392         t1.join();
 393         t2.join();
 394 
 395         final Object obj3 = e.eval("delete String.prototype.foo; Object.prototype.foo = 'newer context';", newCtxt);
 396         assertEquals(obj3, "newer context");
 397         final Thread t3 = new Thread(new ScriptRunner(e, origContext, sharedScript, "original context", 1000));
 398         final Thread t4 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, "newer context", 1000));
 399 
 400         t3.start();
 401         t4.start();
 402         t3.join();
 403         t4.join();
 404 
 405         Assert.assertEquals(e.eval(sharedScript), "original context");
 406         Assert.assertEquals(e.eval(sharedScript, newCtxt), "newer context");
 407     }
 408 
 409     /**
 410      * Test multi-threaded scope function invocation for shared script classes with multiple globals.
 411      */
 412     @Test
 413     public static void multiThreadedFunctionTest() throws ScriptException, InterruptedException {
 414         final ScriptEngineManager m = new ScriptEngineManager();
 415         final ScriptEngine e = m.getEngineByName("nashorn");
 416         final Bindings b = e.createBindings();
 417         final ScriptContext origContext = e.getContext();
 418         final ScriptContext newCtxt = new SimpleScriptContext();
 419         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 420 
 421         e.eval(new URLReader(ScopeTest.class.getResource("resources/func.js")), origContext);
 422         assertEquals(origContext.getAttribute("scopeVar"), 1);
 423         assertEquals(e.eval("scopeTest()"), 1);
 424 
 425         e.eval(new URLReader(ScopeTest.class.getResource("resources/func.js")), newCtxt);
 426         assertEquals(newCtxt.getAttribute("scopeVar"), 1);
 427         assertEquals(e.eval("scopeTest();", newCtxt), 1);
 428 
 429         assertEquals(e.eval("scopeVar = 3;", newCtxt), 3);
 430         assertEquals(newCtxt.getAttribute("scopeVar"), 3);
 431 
 432 
 433         final Thread t1 = new Thread(new ScriptRunner(e, origContext, "scopeTest()", 1, 1000));
 434         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, "scopeTest()", 3, 1000));
 435 
 436         t1.start();
 437         t2.start();
 438         t1.join();
 439         t2.join();
 440 
 441     }
 442 
 443     /**
 444      * Test multi-threaded access to global getters and setters for shared script classes with multiple globals.
 445      */
 446     @Test
 447     public static void getterSetterTest() throws ScriptException, InterruptedException {
 448         final ScriptEngineManager m = new ScriptEngineManager();
 449         final ScriptEngine e = m.getEngineByName("nashorn");
 450         final Bindings b = e.createBindings();
 451         final ScriptContext origContext = e.getContext();
 452         final ScriptContext newCtxt = new SimpleScriptContext();
 453         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 454         final String sharedScript = "accessor1";
 455 
 456         e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), origContext);
 457         assertEquals(e.eval("accessor1 = 1;"), 1);
 458         assertEquals(e.eval(sharedScript), 1);
 459 
 460         e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), newCtxt);
 461         assertEquals(e.eval("accessor1 = 2;", newCtxt), 2);
 462         assertEquals(e.eval(sharedScript, newCtxt), 2);
 463 
 464 
 465         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, 1, 1000));
 466         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, 2, 1000));
 467 
 468         t1.start();
 469         t2.start();
 470         t1.join();
 471         t2.join();
 472 
 473         assertEquals(e.eval(sharedScript), 1);
 474         assertEquals(e.eval(sharedScript, newCtxt), 2);
 475         assertEquals(e.eval("v"), 1);
 476         assertEquals(e.eval("v", newCtxt), 2);
 477     }
 478 
 479     /**
 480      * Test multi-threaded access to global getters and setters for shared script classes with multiple globals.
 481      */
 482     @Test
 483     public static void getterSetter2Test() throws ScriptException, InterruptedException {
 484         final ScriptEngineManager m = new ScriptEngineManager();
 485         final ScriptEngine e = m.getEngineByName("nashorn");
 486         final Bindings b = e.createBindings();
 487         final ScriptContext origContext = e.getContext();
 488         final ScriptContext newCtxt = new SimpleScriptContext();
 489         newCtxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 490         final String sharedScript = "accessor2";
 491 
 492         e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), origContext);
 493         assertEquals(e.eval("accessor2 = 1;"), 1);
 494         assertEquals(e.eval(sharedScript), 1);
 495 
 496         e.eval(new URLReader(ScopeTest.class.getResource("resources/gettersetter.js")), newCtxt);
 497         assertEquals(e.eval("accessor2 = 2;", newCtxt), 2);
 498         assertEquals(e.eval(sharedScript, newCtxt), 2);
 499 
 500 
 501         final Thread t1 = new Thread(new ScriptRunner(e, origContext, sharedScript, 1, 1000));
 502         final Thread t2 = new Thread(new ScriptRunner(e, newCtxt, sharedScript, 2, 1000));
 503 
 504         t1.start();
 505         t2.start();
 506         t1.join();
 507         t2.join();
 508 
 509         assertEquals(e.eval(sharedScript), 1);
 510         assertEquals(e.eval(sharedScript, newCtxt), 2);
 511         assertEquals(e.eval("x"), 1);
 512         assertEquals(e.eval("x", newCtxt), 2);
 513     }
 514 
 515     /**
 516      * Test "slow" scopes involving {@code with} and {@code eval} statements for shared script classes with multiple globals.
 517      */
 518     @Test
 519     public static void testSlowScope() throws ScriptException, InterruptedException {
 520         final ScriptEngineManager m = new ScriptEngineManager();
 521         final ScriptEngine e = m.getEngineByName("nashorn");
 522 
 523         for (int i = 0; i < 100; i++) {
 524             final Bindings b = e.createBindings();
 525             final ScriptContext ctxt = new SimpleScriptContext();
 526             ctxt.setBindings(b, ScriptContext.ENGINE_SCOPE);
 527 
 528             e.eval(new URLReader(ScopeTest.class.getResource("resources/witheval.js")), ctxt);
 529             assertEquals(e.eval("a", ctxt), 1);
 530             assertEquals(b.get("a"), 1);
 531             assertEquals(e.eval("b", ctxt), 3);
 532             assertEquals(b.get("b"), 3);
 533             assertEquals(e.eval("c", ctxt), 10);
 534             assertEquals(b.get("c"), 10);
 535         }
 536     }
 537 
 538     private static class ScriptRunner implements Runnable {
 539 
 540         final ScriptEngine engine;
 541         final ScriptContext context;
 542         final String source;
 543         final Object expected;
 544         final int iterations;
 545 
 546         ScriptRunner(final ScriptEngine engine, final ScriptContext context, final String source, final Object expected, final int iterations) {
 547             this.engine = engine;
 548             this.context = context;
 549             this.source = source;
 550             this.expected = expected;
 551             this.iterations = iterations;
 552         }
 553 
 554         @Override
 555         public void run() {
 556             try {
 557                 for (int i = 0; i < iterations; i++) {
 558                     assertEquals(engine.eval(source, context), expected);
 559                 }
 560             } catch (final ScriptException se) {
 561                 throw new RuntimeException(se);
 562             }
 563         }
 564     }
 565 
 566 }