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 }