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