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 }