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 26 package jdk.nashorn.api.scripting; 27 28 import java.util.Objects; 29 import java.util.function.Function; 30 import javax.script.Invocable; 31 import javax.script.ScriptContext; 32 import javax.script.ScriptEngine; 33 import javax.script.ScriptEngineManager; 34 import javax.script.ScriptException; 35 import javax.script.SimpleScriptContext; 36 import org.testng.Assert; 37 import static org.testng.Assert.assertEquals; 38 import static org.testng.Assert.fail; 39 import org.testng.annotations.Test; 40 41 /** 42 * Tests for javax.script.Invocable implementation of nashorn. 43 */ 44 public class InvocableTest { 45 46 private void log(String msg) { 47 org.testng.Reporter.log(msg, true); 48 } 49 50 @Test 51 public void invokeMethodTest() { 52 final ScriptEngineManager m = new ScriptEngineManager(); 53 final ScriptEngine e = m.getEngineByName("nashorn"); 54 55 try { 56 e.eval("var Example = function() { this.hello = function() { return 'Hello World!'; };}; myExample = new Example();"); 57 final Object obj = e.get("myExample"); 58 final Object res = ((Invocable) e).invokeMethod(obj, "hello"); 59 assertEquals(res, "Hello World!"); 60 } catch (final Exception exp) { 61 exp.printStackTrace(); 62 fail(exp.getMessage()); 63 } 64 } 65 66 @Test 67 /** 68 * Check that we can call invokeMethod on an object that we got by 69 * evaluating script with different Context set. 70 */ 71 public void invokeMethodDifferentContextTest() { 72 ScriptEngineManager m = new ScriptEngineManager(); 73 ScriptEngine e = m.getEngineByName("nashorn"); 74 75 try { 76 // define an object with method on it 77 Object obj = e.eval("({ hello: function() { return 'Hello World!'; } })"); 78 79 final ScriptContext ctxt = new SimpleScriptContext(); 80 ctxt.setBindings(e.createBindings(), ScriptContext.ENGINE_SCOPE); 81 e.setContext(ctxt); 82 83 // invoke 'func' on obj - but with current script context changed 84 final Object res = ((Invocable) e).invokeMethod(obj, "hello"); 85 assertEquals(res, "Hello World!"); 86 } catch (final Exception exp) { 87 exp.printStackTrace(); 88 fail(exp.getMessage()); 89 } 90 } 91 92 @Test 93 /** 94 * Check that invokeMethod throws NPE on null method name. 95 */ 96 public void invokeMethodNullNameTest() { 97 final ScriptEngineManager m = new ScriptEngineManager(); 98 final ScriptEngine e = m.getEngineByName("nashorn"); 99 100 try { 101 final Object obj = e.eval("({})"); 102 final Object res = ((Invocable) e).invokeMethod(obj, null); 103 fail("should have thrown NPE"); 104 } catch (final Exception exp) { 105 if (!(exp instanceof NullPointerException)) { 106 exp.printStackTrace(); 107 fail(exp.getMessage()); 108 } 109 } 110 } 111 112 @Test 113 /** 114 * Check that invokeMethod throws NoSuchMethodException on missing method. 115 */ 116 public void invokeMethodMissingTest() { 117 final ScriptEngineManager m = new ScriptEngineManager(); 118 final ScriptEngine e = m.getEngineByName("nashorn"); 119 120 try { 121 final Object obj = e.eval("({})"); 122 final Object res = ((Invocable) e).invokeMethod(obj, "nonExistentMethod"); 123 fail("should have thrown NoSuchMethodException"); 124 } catch (final Exception exp) { 125 if (!(exp instanceof NoSuchMethodException)) { 126 exp.printStackTrace(); 127 fail(exp.getMessage()); 128 } 129 } 130 } 131 132 @Test 133 /** 134 * Check that calling method on non-script object 'thiz' results in 135 * IllegalArgumentException. 136 */ 137 public void invokeMethodNonScriptObjectThizTest() { 138 final ScriptEngineManager m = new ScriptEngineManager(); 139 final ScriptEngine e = m.getEngineByName("nashorn"); 140 141 try { 142 ((Invocable) e).invokeMethod(new Object(), "toString"); 143 fail("should have thrown IllegalArgumentException"); 144 } catch (final Exception exp) { 145 if (!(exp instanceof IllegalArgumentException)) { 146 exp.printStackTrace(); 147 fail(exp.getMessage()); 148 } 149 } 150 } 151 152 @Test 153 /** 154 * Check that calling method on null 'thiz' results in 155 * IllegalArgumentException. 156 */ 157 public void invokeMethodNullThizTest() { 158 final ScriptEngineManager m = new ScriptEngineManager(); 159 final ScriptEngine e = m.getEngineByName("nashorn"); 160 161 try { 162 ((Invocable) e).invokeMethod(null, "toString"); 163 fail("should have thrown IllegalArgumentException"); 164 } catch (final Exception exp) { 165 if (!(exp instanceof IllegalArgumentException)) { 166 exp.printStackTrace(); 167 fail(exp.getMessage()); 168 } 169 } 170 } 171 172 @Test 173 /** 174 * Check that calling method on mirror created by another engine results in 175 * IllegalArgumentException. 176 */ 177 public void invokeMethodMixEnginesTest() { 178 final ScriptEngineManager m = new ScriptEngineManager(); 179 final ScriptEngine engine1 = m.getEngineByName("nashorn"); 180 final ScriptEngine engine2 = m.getEngineByName("nashorn"); 181 182 try { 183 Object obj = engine1.eval("({ run: function() {} })"); 184 // pass object from engine1 to engine2 as 'thiz' for invokeMethod 185 ((Invocable) engine2).invokeMethod(obj, "run"); 186 fail("should have thrown IllegalArgumentException"); 187 } catch (final Exception exp) { 188 if (!(exp instanceof IllegalArgumentException)) { 189 exp.printStackTrace(); 190 fail(exp.getMessage()); 191 } 192 } 193 } 194 195 @Test 196 public void getInterfaceTest() { 197 final ScriptEngineManager m = new ScriptEngineManager(); 198 final ScriptEngine e = m.getEngineByName("nashorn"); 199 final Invocable inv = (Invocable) e; 200 201 // try to get interface from global functions 202 try { 203 e.eval("function run() { print('run'); };"); 204 final Runnable runnable = inv.getInterface(Runnable.class); 205 runnable.run(); 206 } catch (final Exception exp) { 207 exp.printStackTrace(); 208 fail(exp.getMessage()); 209 } 210 211 // try interface on specific script object 212 try { 213 e.eval("var obj = { run: function() { print('run from obj'); } };"); 214 Object obj = e.get("obj"); 215 final Runnable runnable = inv.getInterface(obj, Runnable.class); 216 runnable.run(); 217 } catch (final Exception exp) { 218 exp.printStackTrace(); 219 fail(exp.getMessage()); 220 } 221 } 222 223 public interface Foo { 224 225 public void bar(); 226 } 227 228 public interface Foo2 extends Foo { 229 230 public void bar2(); 231 } 232 233 @Test 234 public void getInterfaceMissingTest() { 235 final ScriptEngineManager manager = new ScriptEngineManager(); 236 final ScriptEngine engine = manager.getEngineByName("nashorn"); 237 238 // don't define any function. 239 try { 240 engine.eval(""); 241 } catch (final Exception exp) { 242 exp.printStackTrace(); 243 fail(exp.getMessage()); 244 } 245 246 Runnable runnable = ((Invocable) engine).getInterface(Runnable.class); 247 if (runnable != null) { 248 fail("runnable is not null!"); 249 } 250 251 // now define "run" 252 try { 253 engine.eval("function run() { print('this is run function'); }"); 254 } catch (final Exception exp) { 255 exp.printStackTrace(); 256 fail(exp.getMessage()); 257 } 258 runnable = ((Invocable) engine).getInterface(Runnable.class); 259 // should not return null now! 260 runnable.run(); 261 262 // define only one method of "Foo2" 263 try { 264 engine.eval("function bar() { print('bar function'); }"); 265 } catch (final Exception exp) { 266 exp.printStackTrace(); 267 fail(exp.getMessage()); 268 } 269 270 Foo2 foo2 = ((Invocable) engine).getInterface(Foo2.class); 271 if (foo2 != null) { 272 throw new RuntimeException("foo2 is not null!"); 273 } 274 275 // now define other method of "Foo2" 276 try { 277 engine.eval("function bar2() { print('bar2 function'); }"); 278 } catch (final Exception exp) { 279 exp.printStackTrace(); 280 fail(exp.getMessage()); 281 } 282 foo2 = ((Invocable) engine).getInterface(Foo2.class); 283 foo2.bar(); 284 foo2.bar2(); 285 } 286 287 @Test 288 /** 289 * Try passing non-interface Class object for interface implementation. 290 */ 291 public void getNonInterfaceGetInterfaceTest() { 292 final ScriptEngineManager manager = new ScriptEngineManager(); 293 final ScriptEngine engine = manager.getEngineByName("nashorn"); 294 try { 295 log(Objects.toString(((Invocable) engine).getInterface(Object.class))); 296 fail("Should have thrown IllegalArgumentException"); 297 } catch (final Exception exp) { 298 if (!(exp instanceof IllegalArgumentException)) { 299 fail("IllegalArgumentException expected, got " + exp); 300 } 301 } 302 } 303 304 @Test 305 /** 306 * Check that we can get interface out of a script object even after 307 * switching to use different ScriptContext. 308 */ 309 public void getInterfaceDifferentContext() { 310 ScriptEngineManager m = new ScriptEngineManager(); 311 ScriptEngine e = m.getEngineByName("nashorn"); 312 try { 313 Object obj = e.eval("({ run: function() { } })"); 314 315 // change script context 316 ScriptContext ctxt = new SimpleScriptContext(); 317 ctxt.setBindings(e.createBindings(), ScriptContext.ENGINE_SCOPE); 318 e.setContext(ctxt); 319 320 Runnable r = ((Invocable) e).getInterface(obj, Runnable.class); 321 r.run(); 322 } catch (final Exception exp) { 323 exp.printStackTrace(); 324 fail(exp.getMessage()); 325 } 326 } 327 328 @Test 329 /** 330 * Check that getInterface on non-script object 'thiz' results in 331 * IllegalArgumentException. 332 */ 333 public void getInterfaceNonScriptObjectThizTest() { 334 final ScriptEngineManager m = new ScriptEngineManager(); 335 final ScriptEngine e = m.getEngineByName("nashorn"); 336 337 try { 338 ((Invocable) e).getInterface(new Object(), Runnable.class); 339 fail("should have thrown IllegalArgumentException"); 340 } catch (final Exception exp) { 341 if (!(exp instanceof IllegalArgumentException)) { 342 exp.printStackTrace(); 343 fail(exp.getMessage()); 344 } 345 } 346 } 347 348 @Test 349 /** 350 * Check that getInterface on null 'thiz' results in 351 * IllegalArgumentException. 352 */ 353 public void getInterfaceNullThizTest() { 354 final ScriptEngineManager m = new ScriptEngineManager(); 355 final ScriptEngine e = m.getEngineByName("nashorn"); 356 357 try { 358 ((Invocable) e).getInterface(null, Runnable.class); 359 fail("should have thrown IllegalArgumentException"); 360 } catch (final Exception exp) { 361 if (!(exp instanceof IllegalArgumentException)) { 362 exp.printStackTrace(); 363 fail(exp.getMessage()); 364 } 365 } 366 } 367 368 @Test 369 /** 370 * Check that calling getInterface on mirror created by another engine 371 * results in IllegalArgumentException. 372 */ 373 public void getInterfaceMixEnginesTest() { 374 final ScriptEngineManager m = new ScriptEngineManager(); 375 final ScriptEngine engine1 = m.getEngineByName("nashorn"); 376 final ScriptEngine engine2 = m.getEngineByName("nashorn"); 377 378 try { 379 Object obj = engine1.eval("({ run: function() {} })"); 380 // pass object from engine1 to engine2 as 'thiz' for getInterface 381 ((Invocable) engine2).getInterface(obj, Runnable.class); 382 fail("should have thrown IllegalArgumentException"); 383 } catch (final Exception exp) { 384 if (!(exp instanceof IllegalArgumentException)) { 385 exp.printStackTrace(); 386 fail(exp.getMessage()); 387 } 388 } 389 } 390 391 @Test 392 /** 393 * check that null function name results in NPE. 394 */ 395 public void invokeFunctionNullNameTest() { 396 final ScriptEngineManager m = new ScriptEngineManager(); 397 final ScriptEngine e = m.getEngineByName("nashorn"); 398 399 try { 400 final Object res = ((Invocable) e).invokeFunction(null); 401 fail("should have thrown NPE"); 402 } catch (final Exception exp) { 403 if (!(exp instanceof NullPointerException)) { 404 exp.printStackTrace(); 405 fail(exp.getMessage()); 406 } 407 } 408 } 409 410 @Test 411 /** 412 * Check that attempt to call missing function results in 413 * NoSuchMethodException. 414 */ 415 public void invokeFunctionMissingTest() { 416 final ScriptEngineManager m = new ScriptEngineManager(); 417 final ScriptEngine e = m.getEngineByName("nashorn"); 418 419 try { 420 final Object res = ((Invocable) e).invokeFunction("NonExistentFunc"); 421 fail("should have thrown NoSuchMethodException"); 422 } catch (final Exception exp) { 423 if (!(exp instanceof NoSuchMethodException)) { 424 exp.printStackTrace(); 425 fail(exp.getMessage()); 426 } 427 } 428 } 429 430 @Test 431 /** 432 * Check that invokeFunction calls functions only from current context's 433 * Bindings. 434 */ 435 public void invokeFunctionDifferentContextTest() { 436 ScriptEngineManager m = new ScriptEngineManager(); 437 ScriptEngine e = m.getEngineByName("nashorn"); 438 439 try { 440 // define an object with method on it 441 Object obj = e.eval("function hello() { return 'Hello World!'; }"); 442 final ScriptContext ctxt = new SimpleScriptContext(); 443 ctxt.setBindings(e.createBindings(), ScriptContext.ENGINE_SCOPE); 444 // change engine's current context 445 e.setContext(ctxt); 446 447 ((Invocable) e).invokeFunction("hello"); // no 'hello' in new context! 448 fail("should have thrown NoSuchMethodException"); 449 } catch (final Exception exp) { 450 if (!(exp instanceof NoSuchMethodException)) { 451 exp.printStackTrace(); 452 fail(exp.getMessage()); 453 } 454 } 455 } 456 457 @Test 458 public void invokeFunctionExceptionTest() { 459 final ScriptEngineManager m = new ScriptEngineManager(); 460 final ScriptEngine e = m.getEngineByName("nashorn"); 461 try { 462 e.eval("function func() { throw new TypeError(); }"); 463 } catch (final Throwable t) { 464 t.printStackTrace(); 465 fail(t.getMessage()); 466 } 467 468 try { 469 ((Invocable) e).invokeFunction("func"); 470 fail("should have thrown exception"); 471 } catch (final ScriptException se) { 472 // ECMA TypeError property wrapped as a ScriptException 473 log("got " + se + " as expected"); 474 } catch (final Throwable t) { 475 t.printStackTrace(); 476 fail(t.getMessage()); 477 } 478 } 479 480 @Test 481 public void invokeMethodExceptionTest() { 482 final ScriptEngineManager m = new ScriptEngineManager(); 483 final ScriptEngine e = m.getEngineByName("nashorn"); 484 try { 485 e.eval("var sobj = {}; sobj.foo = function func() { throw new TypeError(); }"); 486 } catch (final Throwable t) { 487 t.printStackTrace(); 488 fail(t.getMessage()); 489 } 490 491 try { 492 final Object sobj = e.get("sobj"); 493 ((Invocable) e).invokeMethod(sobj, "foo"); 494 fail("should have thrown exception"); 495 } catch (final ScriptException se) { 496 // ECMA TypeError property wrapped as a ScriptException 497 log("got " + se + " as expected"); 498 } catch (final Throwable t) { 499 t.printStackTrace(); 500 fail(t.getMessage()); 501 } 502 } 503 504 @Test 505 /** 506 * Tests whether invocation of a JavaScript method through a variable arity 507 * Java method will pass the vararg array. Both non-vararg and vararg 508 * JavaScript methods are tested. 509 * 510 * @throws ScriptException 511 */ 512 public void variableArityInterfaceTest() throws ScriptException { 513 final ScriptEngineManager m = new ScriptEngineManager(); 514 final ScriptEngine e = m.getEngineByName("nashorn"); 515 e.eval( 516 "function test1(i, strings) {" 517 + " return 'i == ' + i + ', strings instanceof java.lang.String[] == ' + (strings instanceof Java.type('java.lang.String[]')) + ', strings == ' + java.util.Arrays.toString(strings)" 518 + "}" 519 + "function test2() {" 520 + " return 'arguments[0] == ' + arguments[0] + ', arguments[1] instanceof java.lang.String[] == ' + (arguments[1] instanceof Java.type('java.lang.String[]')) + ', arguments[1] == ' + java.util.Arrays.toString(arguments[1])" 521 + "}"); 522 final VariableArityTestInterface itf = ((Invocable) e).getInterface(VariableArityTestInterface.class); 523 Assert.assertEquals(itf.test1(42, "a", "b"), "i == 42, strings instanceof java.lang.String[] == true, strings == [a, b]"); 524 Assert.assertEquals(itf.test2(44, "c", "d", "e"), "arguments[0] == 44, arguments[1] instanceof java.lang.String[] == true, arguments[1] == [c, d, e]"); 525 } 526 527 @Test 528 @SuppressWarnings("unchecked") 529 public void defaultMethodTest() throws ScriptException { 530 final ScriptEngineManager m = new ScriptEngineManager(); 531 final ScriptEngine e = m.getEngineByName("nashorn"); 532 final Invocable inv = (Invocable) e; 533 534 Object obj = e.eval("({ apply: function(arg) { return arg.toUpperCase(); }})"); 535 Function<String, String> func = inv.getInterface(obj, Function.class); 536 assertEquals(func.apply("hello"), "HELLO"); 537 } 538 }