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 }