diff --git a/src/main/c/Include/Jep.h b/src/main/c/Include/Jep.h index 367f1839..44c215c3 100644 --- a/src/main/c/Include/Jep.h +++ b/src/main/c/Include/Jep.h @@ -63,6 +63,7 @@ #include "java_access/Collections.h" #include "java_access/Comparable.h" #include "java_access/Constructor.h" +#include "java_access/Entry.h" #include "java_access/Field.h" #include "java_access/Iterable.h" #include "java_access/Iterator.h" diff --git a/src/main/c/Include/java_access/Entry.h b/src/main/c/Include/java_access/Entry.h new file mode 100644 index 00000000..5bcd2569 --- /dev/null +++ b/src/main/c/Include/java_access/Entry.h @@ -0,0 +1,34 @@ +/* + jep - Java Embedded Python + + Copyright (c) 2017 JEP AUTHORS. + + This file is licensed under the the zlib/libpng License. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _Included_java_util_Map_Entry +#define _Included_java_util_Map_Entry + +jobject java_util_Map_Entry_getKey(JNIEnv*, jobject); +jobject java_util_Map_Entry_getValue(JNIEnv*, jobject); + +#endif // ndef java_util_Map_Entry diff --git a/src/main/c/Include/java_access/Map.h b/src/main/c/Include/java_access/Map.h index 13f75d2e..31d726ab 100644 --- a/src/main/c/Include/java_access/Map.h +++ b/src/main/c/Include/java_access/Map.h @@ -34,5 +34,6 @@ jobject java_util_Map_keySet(JNIEnv*, jobject); jobject java_util_Map_put(JNIEnv*, jobject, jobject, jobject); jobject java_util_Map_remove(JNIEnv*, jobject, jobject); jint java_util_Map_size(JNIEnv*, jobject); +jobject java_util_Map_entrySet(JNIEnv*, jobject); #endif // ndef java_util_Map diff --git a/src/main/c/Include/jep_util.h b/src/main/c/Include/jep_util.h index 238e81fe..a7b6e4b1 100644 --- a/src/main/c/Include/jep_util.h +++ b/src/main/c/Include/jep_util.h @@ -122,6 +122,7 @@ extern jclass JDOUBLE_ARRAY_TYPE; F(JCLASS_TYPE, "java/lang/Class") \ F(JLIST_TYPE, "java/util/List") \ F(JMAP_TYPE, "java/util/Map") \ + F(JENTRY_TYPE, "java/util/Map$Entry") \ F(JITERABLE_TYPE, "java/lang/Iterable") \ F(JITERATOR_TYPE, "java/util/Iterator") \ F(JCOLLECTION_TYPE, "java/util/Collection") \ diff --git a/src/main/c/Include/pyembed.h b/src/main/c/Include/pyembed.h index 4cd5e8d2..1008706e 100644 --- a/src/main/c/Include/pyembed.h +++ b/src/main/c/Include/pyembed.h @@ -61,8 +61,8 @@ void pyembed_thread_close(JNIEnv*, intptr_t); void pyembed_close(void); void pyembed_run(JNIEnv*, intptr_t, char*); jobject pyembed_invoke_method(JNIEnv*, intptr_t, const char*, jobjectArray, - jintArray); -jobject pyembed_invoke(JNIEnv*, PyObject*, jobjectArray, jintArray); + jobject); +jobject pyembed_invoke(JNIEnv*, PyObject*, jobjectArray, jobject); void pyembed_eval(JNIEnv*, intptr_t, char*); int pyembed_compile_string(JNIEnv*, intptr_t, char*); void pyembed_setloader(JNIEnv*, intptr_t, jobject); diff --git a/src/main/c/Jep/invocationhandler.c b/src/main/c/Jep/invocationhandler.c index 7aa043bc..e708fc54 100644 --- a/src/main/c/Jep/invocationhandler.c +++ b/src/main/c/Jep/invocationhandler.c @@ -82,7 +82,7 @@ JNIEXPORT jobject JNICALL Java_jep_InvocationHandler_invoke goto EXIT; } - ret = pyembed_invoke(env, callable, args, types); + ret = pyembed_invoke(env, callable, args, NULL); EXIT: PyEval_ReleaseThread(jepThread->tstate); diff --git a/src/main/c/Jep/java_access/Entry.c b/src/main/c/Jep/java_access/Entry.c new file mode 100644 index 00000000..117df11f --- /dev/null +++ b/src/main/c/Jep/java_access/Entry.c @@ -0,0 +1,58 @@ +/* + jep - Java Embedded Python + + Copyright (c) 2017 JEP AUTHORS. + + This file is licensed under the the zlib/libpng License. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you use + this software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "Jep.h" + +static jmethodID getKey = 0; +static jmethodID getValue = 0; + + +jobject java_util_Map_Entry_getKey(JNIEnv* env, jobject this) +{ + jobject result = NULL; + Py_BEGIN_ALLOW_THREADS + if (JNI_METHOD(getKey, env, JENTRY_TYPE, "getKey", + "()Ljava/lang/Object;")) { + result = (*env)->CallObjectMethod(env, this, getKey); + } + Py_END_ALLOW_THREADS + return result; +} + + +jobject java_util_Map_Entry_getValue(JNIEnv* env, jobject this) +{ + jobject result = NULL; + Py_BEGIN_ALLOW_THREADS + if (JNI_METHOD(getValue, env, JENTRY_TYPE, "getValue", + "()Ljava/lang/Object;")) { + result = (*env)->CallObjectMethod(env, this, getValue); + } + Py_END_ALLOW_THREADS + return result; +} + diff --git a/src/main/c/Jep/java_access/Map.c b/src/main/c/Jep/java_access/Map.c index 88005321..cc882cd8 100644 --- a/src/main/c/Jep/java_access/Map.c +++ b/src/main/c/Jep/java_access/Map.c @@ -33,6 +33,7 @@ static jmethodID keySet = 0; static jmethodID put = 0; static jmethodID map_remove = 0; static jmethodID size = 0; +static jmethodID entrySet = 0; jboolean java_util_Map_containsKey(JNIEnv* env, jobject this, jobject key) { @@ -103,3 +104,14 @@ jint java_util_Map_size(JNIEnv* env, jobject this) Py_END_ALLOW_THREADS return result; } + +jobject java_util_Map_entrySet(JNIEnv* env, jobject this) +{ + jobject result = NULL; + Py_BEGIN_ALLOW_THREADS + if (JNI_METHOD(entrySet, env, JMAP_TYPE, "entrySet", "()Ljava/util/Set;")) { + result = (*env)->CallObjectMethod(env, this, entrySet); + } + Py_END_ALLOW_THREADS + return result; +} diff --git a/src/main/c/Jep/java_access/Modifier.c b/src/main/c/Jep/java_access/Modifier.c index 07a25431..b6704e4a 100644 --- a/src/main/c/Jep/java_access/Modifier.c +++ b/src/main/c/Jep/java_access/Modifier.c @@ -58,7 +58,7 @@ jboolean java_lang_reflect_Modifier_isAbstract(JNIEnv* env, jint mod) jboolean result = JNI_FALSE; if (isAbstract || (isAbstract = (*env)->GetStaticMethodID(env, JMODIFIER_TYPE, "isAbstract", - "(I)Z"))) { + "(I)Z"))) { result = (*env)->CallStaticBooleanMethod(env, JMODIFIER_TYPE, isAbstract, mod); } return result; diff --git a/src/main/c/Jep/jep.c b/src/main/c/Jep/jep.c index 3332fee2..19dc0fb1 100644 --- a/src/main/c/Jep/jep.c +++ b/src/main/c/Jep/jep.c @@ -131,7 +131,7 @@ JNIEXPORT void JNICALL Java_jep_Jep_run /* * Class: jep_Jep * Method: invoke - * Signature: (JLjava/lang/String;[Ljava/lang/Object;[II)Ljava/lang/Object; + * Signature: (JLjava/lang/String;[Ljava/lang/Object;Ljava/util/Map;)Ljava/lang/Object; */ JNIEXPORT jobject JNICALL Java_jep_Jep_invoke (JNIEnv *env, @@ -139,13 +139,13 @@ JNIEXPORT jobject JNICALL Java_jep_Jep_invoke jlong tstate, jstring name, jobjectArray args, - jintArray types) + jobject kwargs) { const char *cname; jobject ret; cname = jstring2char(env, name); - ret = pyembed_invoke_method(env, (intptr_t) tstate, cname, args, types); + ret = pyembed_invoke_method(env, (intptr_t) tstate, cname, args, kwargs); release_utf_char(env, name, cname); return ret; diff --git a/src/main/c/Jep/pyembed.c b/src/main/c/Jep/pyembed.c index 0a16c12b..8c2a287e 100644 --- a/src/main/c/Jep/pyembed.c +++ b/src/main/c/Jep/pyembed.c @@ -919,98 +919,134 @@ static PyObject* pyembed_findclass(PyObject *self, PyObject *args) } -jobject pyembed_invoke_method(JNIEnv *env, - intptr_t _jepThread, - const char *cname, - jobjectArray args, - jintArray types) -{ - PyObject *callable; - JepThread *jepThread; - jobject ret; - - ret = NULL; - - jepThread = (JepThread *) _jepThread; - if (!jepThread) { - THROW_JEP(env, "Couldn't get thread objects."); - return ret; - } - - PyEval_AcquireThread(jepThread->tstate); - - callable = PyDict_GetItemString(jepThread->globals, (char *) cname); - if (!callable) { - THROW_JEP(env, "Object was not found in the global dictionary."); - goto EXIT; - } - if (process_py_exception(env)) { - goto EXIT; - } - - ret = pyembed_invoke(env, callable, args, types); - -EXIT: - PyEval_ReleaseThread(jepThread->tstate); - - return ret; -} - - -// invoke object callable -// **** hold lock before calling **** +/* + * Invoke callable object. Hold the thread state lock before calling. + */ jobject pyembed_invoke(JNIEnv *env, PyObject *callable, jobjectArray args, - jintArray _types) + jobject kwargs) { - jobject ret; - int iarg, arglen; - jint *types; /* pinned primitive array */ - jboolean isCopy; - PyObject *pyargs; /* a tuple */ - PyObject *pyret; - - types = NULL; - ret = NULL; - pyret = NULL; + jobject ret = NULL; + PyObject *pyargs = NULL; /* a tuple */ + PyObject *pykwargs = NULL; /* a dictionary */ + PyObject *pyret = NULL; + int arglen = 0; + Py_ssize_t i = 0; if (!PyCallable_Check(callable)) { THROW_JEP(env, "pyembed:invoke Invalid callable."); return NULL; } - // pin primitive array so we can get to it - types = (*env)->GetIntArrayElements(env, _types, &isCopy); + if (args != NULL) { + arglen = (*env)->GetArrayLength(env, args); + pyargs = PyTuple_New(arglen); + } else { + // pyargs should be a Tuple of size 0 if no args + pyargs = PyTuple_New(arglen); + } - // first thing to do, convert java arguments to a python tuple - arglen = (*env)->GetArrayLength(env, args); - pyargs = PyTuple_New(arglen); - for (iarg = 0; iarg < arglen; iarg++) { + // convert Java arguments to a Python tuple + for (i = 0; i < arglen; i++) { jobject val; - int typeid; PyObject *pyval; - val = (*env)->GetObjectArrayElement(env, args, iarg); + val = (*env)->GetObjectArrayElement(env, args, i); if ((*env)->ExceptionCheck(env)) { /* careful, NULL is okay */ goto EXIT; } - typeid = (int) types[iarg]; - - // now we know the type, convert and add to pyargs. we know - pyval = convert_jobject(env, val, typeid); - if ((*env)->ExceptionOccurred(env)) { + pyval = convert_jobject_pyobject(env, val); + if ((*env)->ExceptionCheck(env)) { goto EXIT; } - PyTuple_SET_ITEM(pyargs, iarg, pyval); /* steals */ + PyTuple_SET_ITEM(pyargs, i, pyval); /* steals */ if (val) { (*env)->DeleteLocalRef(env, val); } - } // for(iarg = 0; iarg < arglen; iarg++) + } + + // convert Java arguments to a Python dictionary + if (kwargs != NULL) { + jobject entrySet; + jobject itr; + + pykwargs = PyDict_New(); + entrySet = java_util_Map_entrySet(env, kwargs); + if ((*env)->ExceptionCheck(env)) { + goto EXIT; + } + itr = java_lang_Iterable_iterator(env, entrySet); + if ((*env)->ExceptionCheck(env)) { + goto EXIT; + } + + while (java_util_Iterator_hasNext(env, itr)) { + jobject next; + jobject key; + jobject value; + PyObject *pykey; + PyObject *pyval; + + next = java_util_Iterator_next(env, itr); + if (!next) { + if (!(*env)->ExceptionCheck(env)) { + THROW_JEP(env, "Map.entrySet().iterator().next() returned null"); + } + goto EXIT; + } + + // convert Map.Entry's key to a PyObject* + key = java_util_Map_Entry_getKey(env, next); + if ((*env)->ExceptionCheck(env)) { + goto EXIT; + } + pykey = convert_jobject_pyobject(env, key); + if (!pykey || (*env)->ExceptionCheck(env)) { + Py_XDECREF(pykey); + goto EXIT; + } + + // convert Map.Entry's value to a PyObject* + value = java_util_Map_Entry_getValue(env, next); + if ((*env)->ExceptionCheck(env)) { + Py_XDECREF(pykey); + goto EXIT; + } + pyval = convert_jobject_pyobject(env, value); + if (!pyval || (*env)->ExceptionCheck(env)) { + Py_XDECREF(pykey); + Py_XDECREF(pyval); + goto EXIT; + } + + if (PyDict_SetItem(pykwargs, pykey, pyval)) { + process_py_exception(env); + Py_DECREF(pykey); + Py_DECREF(pyval); + goto EXIT; + } + Py_DECREF(pykey); + Py_DECREF(pyval); + + (*env)->DeleteLocalRef(env, next); + if (key) { + (*env)->DeleteLocalRef(env, key); + } + if (value) { + (*env)->DeleteLocalRef(env, value); + } + } + } // end of while loop + + // if hasNext() threw an exception + if ((*env)->ExceptionCheck(env)) { + goto EXIT; + } - pyret = PyObject_CallObject(callable, pyargs); + pyret = PyObject_Call(callable, pyargs, pykwargs); if (process_py_exception(env) || !pyret) { goto EXIT; } @@ -1022,18 +1058,48 @@ jobject pyembed_invoke(JNIEnv *env, } EXIT: - Py_XDECREF(pyargs); + Py_CLEAR(pyargs); + Py_CLEAR(pykwargs); Py_XDECREF(pyret); - if (types) { - (*env)->ReleaseIntArrayElements(env, - _types, - types, - JNI_ABORT); + return ret; +} + - (*env)->DeleteLocalRef(env, _types); +jobject pyembed_invoke_method(JNIEnv *env, + intptr_t _jepThread, + const char *cname, + jobjectArray args, + jobject kwargs) +{ + PyObject *callable; + JepThread *jepThread; + jobject ret = NULL; + + jepThread = (JepThread *) _jepThread; + if (!jepThread) { + THROW_JEP(env, "Couldn't get thread objects."); + return ret; + } + + PyEval_AcquireThread(jepThread->tstate); + + callable = PyDict_GetItemString(jepThread->globals, (char *) cname); + if (!callable) { + char strBuf[128]; + snprintf(strBuf, 128, "Unable to find object with name: %s", cname); + THROW_JEP(env, strBuf); + goto EXIT; + } + if (process_py_exception(env)) { + goto EXIT; } + ret = pyembed_invoke(env, callable, args, kwargs); + +EXIT: + PyEval_ReleaseThread(jepThread->tstate); + return ret; } diff --git a/src/main/java/jep/Jep.java b/src/main/java/jep/Jep.java index 1c87334b..8b885c76 100644 --- a/src/main/java/jep/Jep.java +++ b/src/main/java/jep/Jep.java @@ -28,6 +28,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; @@ -224,7 +225,8 @@ public void sharedImport(String module) throws JepException { * @param config * the python configuration to use. * - * @throws JepException if an error occurs + * @throws JepException + * if an error occurs * @since 3.6 */ public static void setInitParams(PyConfig config) throws JepException { @@ -245,7 +247,8 @@ public static void setInitParams(PyConfig config) throws JepException { * @param argv * the arguments to be set on Python's sys.argv for the top/main * interpreter - * @throws JepException if an error occurs + * @throws JepException + * if an error occurs * * @since 3.7 */ @@ -521,19 +524,57 @@ public void runScript(String script, ClassLoader cl) throws JepException { * if an error occurs */ public Object invoke(String name, Object... args) throws JepException { - if (name == null || name.trim().equals("")) + if (name == null || name.trim().equals("")) { throw new JepException("Invalid function name."); + } - int[] types = new int[args.length]; + return invoke(this.tstate, name, args, null); + } - for (int i = 0; i < args.length; i++) - types[i] = Util.getTypeId(args[i]); + /** + * Invokes a Python function. + * + * @param name + * must be a valid Python function name in globals dict + * @param kwargs + * a Map of keyword args + * @return an value + * @throws JepException + * @since 3.8 + */ + public Object invoke(String name, Map kwargs) + throws JepException { + if (name == null || name.trim().equals("")) { + throw new JepException("Invalid function name."); + } + + return invoke(this.tstate, name, null, kwargs); + } + + /** + * Invokes a Python function. + * + * @param name + * must be a valid Python function name in globals dict + * @param args + * args to pass to the function in order + * @param kwargs + * a Map of keyword args + * @return an value + * @throws JepException + * @since 3.8 + */ + public Object invoke(String name, Object[] args, Map kwargs) + throws JepException { + if (name == null || name.trim().equals("")) { + throw new JepException("Invalid function name."); + } - return invoke(this.tstate, name, args, types); + return invoke(this.tstate, name, args, kwargs); } private native Object invoke(long tstate, String name, Object[] args, - int[] types); + Map kwargs); /** *

diff --git a/src/test/java/jep/test/TestInvoke.java b/src/test/java/jep/test/TestInvoke.java new file mode 100644 index 00000000..e415a5d6 --- /dev/null +++ b/src/test/java/jep/test/TestInvoke.java @@ -0,0 +1,110 @@ +package jep.test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jep.Jep; +import jep.JepConfig; +import jep.JepException; + +/** + * Tests that the variations of the Jep.invoke(...) method work correctly. Also + * tests a few error possibilities. + * + * Created: September 2017 + * + * @author Nate Jensen + * @since 3.8 + */ +public class TestInvoke { + + public static void main(String[] args) throws JepException { + JepConfig config = new JepConfig(); + config.addIncludePaths("src/test/python/subprocess"); + + try (Jep jep = new Jep(config)) { + jep.eval("from invoke_args import *"); + + // test a basic invoke with no args + Object result = jep.invoke("invokeNoArgs"); + if (result != null) { + throw new IllegalStateException( + "Received " + result + " but expected null"); + } + + // test a basic invoke with arguments + result = jep.invoke("invokeArgs", "a", null, 5.4); + if (result == null || !result.equals(Boolean.TRUE)) { + throw new IllegalStateException( + "Received " + result + " but expected true"); + } + + // test that args are passed in order + result = jep.invoke("invokeVarArgsExplicit", true, null, 2, "xyz"); + if (result != null) { + throw new IllegalStateException( + "Received " + result + " but expected null"); + } + + // test *args + result = jep.invoke("invokeVarArgs", true, null, 2, "xyz"); + if (result == null || !result.equals("xyz")) { + throw new IllegalStateException( + "Received " + result + " but expected xyz"); + } + + Map kwMap = new HashMap<>(); + kwMap.put("arg4", "xyz"); + kwMap.put("arg5", new ArrayList<>()); + kwMap.put("argnull", null); + + // test that keys/values are mapped to correct arguments + result = jep.invoke("invokeKeywordArgsExplicit", kwMap); + if (result == null || !(result instanceof ArrayList)) { + throw new IllegalStateException( + "Received " + result + " but expected ArrayList"); + } + + // test **kwargs + result = jep.invoke("invokeKeywordArgs", kwMap); + if (result != null) { + throw new IllegalStateException( + "Received " + result + " but expected null"); + } + + // test a mixture of varargs and kwargs + result = jep.invoke("invokeArgsAndKeywordArgs", + new Object[] { 15, "add", false }, kwMap); + if (result == null || !(result instanceof List)) { + throw new IllegalStateException( + "Received " + result + " but expected List"); + } + + // keywords must be strings + try { + kwMap.put(null, "default"); + result = jep.invoke("invokeArgsAndKeywordArgs", + new Object[] { 15, "add", false }, kwMap); + } catch (JepException e) { + if (!e.getMessage().contains("TypeError")) { + throw new IllegalStateException( + "Bad error message, error did not include TypeError"); + } + } + + // test that you can't call an Object that doesn't exist + try { + result = jep.invoke("anything"); + } catch (JepException e) { + if (!e.getMessage().contains("anything")) { + throw new IllegalStateException( + "Bad error message, error did not include missing object name"); + } + } + + } + } + +} diff --git a/src/test/python/subprocess/invoke_args.py b/src/test/python/subprocess/invoke_args.py new file mode 100644 index 00000000..f717ee82 --- /dev/null +++ b/src/test/python/subprocess/invoke_args.py @@ -0,0 +1,68 @@ +from unittest import TestCase + + +class FakeTestCase(TestCase): + "This class exists to get the assert methods without having a real TestCase" + + def runTest(self): + pass + +def invokeNoArgs(): + x = FakeTestCase() + x.assertTrue(True) + +def invokeArgs(arg1, arg2, arg3): + x = FakeTestCase() + x.assertEqual(arg1, "a") + x.assertIsNone(arg2, None) + x.assertEqual(arg3, 5.4) + return True + +def invokeVarArgsExplicit(arg1, arg2, arg3, arg4): + x = FakeTestCase() + x.assertEqual(arg1, True) + x.assertIsNone(arg2) + x.assertEqual(arg3, 2) + x.assertEqual(arg4, "xyz") + return None + +def invokeVarArgs(*args): + x = FakeTestCase() + x.assertEqual(len(args), 4) + x.assertEqual(args[0], True) + x.assertIsNone(args[1]) + x.assertEqual(args[2], 2) + x.assertEqual(args[3], "xyz") + return args[3] + +def invokeKeywordArgsExplicit(argnull, arg4, arg5, arg6=None): + x = FakeTestCase() + x.assertIsNone(argnull) + x.assertEqual(arg4, "xyz") + x.assertIn("PyJList", str(type(arg5))) + x.assertIsNone(arg6) + return arg5 + +def invokeKeywordArgs(**kwargs): + x = FakeTestCase() + x.assertEqual(len(kwargs), 3) + x.assertEqual(kwargs['arg4'], "xyz") + x.assertIsNone(kwargs['argnull']) + arg5 = kwargs['arg5'] + x.assertIn("PyJList", str(type(arg5))) + x.assertNotIn("arg1", kwargs) + x.assertNotIn("arg2", kwargs) + x.assertNotIn("arg3", kwargs) + x.assertNotIn("arg6", kwargs) + return kwargs['argnull'] + +def invokeArgsAndKeywordArgs(arg1, arg2, arg3=True, arg4=False, arg5=None, arg6=10, argnull=5.1): + x = FakeTestCase() + x.assertEqual(arg1, 15) + x.assertEqual(arg2, "add") + x.assertEqual(arg3, False) + x.assertEqual(arg4, "xyz") + x.assertIsNotNone(arg5) + x.assertEqual(arg6, 10) + x.assertIsNone(argnull) + return [arg1, arg2, arg3, arg4, arg5, arg6, argnull] diff --git a/src/test/python/test_invoke.py b/src/test/python/test_invoke.py new file mode 100644 index 00000000..193cafd8 --- /dev/null +++ b/src/test/python/test_invoke.py @@ -0,0 +1,11 @@ +import unittest +import sys +from jep_pipe import jep_pipe +from jep_pipe import build_java_process_cmd + + +class TestInvokes(unittest.TestCase): + + @unittest.skipIf(sys.platform.startswith("win"), "subprocess complications on Windows") + def test_inits(self): + jep_pipe(build_java_process_cmd('jep.test.TestInvoke'))