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