InvocableTest.java revision 1239:77609e069f9f
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@SuppressWarnings("javadoc")
45public 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}
540