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
26package jdk.nashorn.api.scripting.test;
27
28import static org.testng.Assert.assertEquals;
29import static org.testng.Assert.fail;
30import java.util.Objects;
31import java.util.function.Function;
32import javax.script.Invocable;
33import javax.script.ScriptContext;
34import javax.script.ScriptEngine;
35import javax.script.ScriptEngineManager;
36import javax.script.ScriptException;
37import javax.script.SimpleScriptContext;
38import org.testng.Assert;
39import 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")
49public 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}
544