diff --git a/.github/build.sh b/.github/build.sh new file mode 100755 index 0000000..7da4262 --- /dev/null +++ b/.github/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh +curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/master/ci-build.sh +sh ci-build.sh diff --git a/.github/setup.sh b/.github/setup.sh new file mode 100755 index 0000000..f359bbe --- /dev/null +++ b/.github/setup.sh @@ -0,0 +1,3 @@ +#!/bin/sh +curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/master/ci-setup-github-actions.sh +sh ci-setup-github-actions.sh diff --git a/.github/workflows/build-main.yml b/.github/workflows/build-main.yml new file mode 100644 index 0000000..5ef5692 --- /dev/null +++ b/.github/workflows/build-main.yml @@ -0,0 +1,32 @@ +name: build + +on: + push: + branches: + - master + tags: + - "*-[0-9]+.*" + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Java + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'zulu' + cache: 'maven' + - name: Set up CI environment + run: .github/setup.sh + - name: Execute the build + run: .github/build.sh + env: + GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + MAVEN_USER: ${{ secrets.MAVEN_USER }} + MAVEN_PASS: ${{ secrets.MAVEN_PASS }} + OSSRH_PASS: ${{ secrets.OSSRH_PASS }} + SIGNING_ASC: ${{ secrets.SIGNING_ASC }} diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 0000000..925b576 --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,23 @@ +name: build PR + +on: + pull_request: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Java + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'zulu' + cache: 'maven' + - name: Set up CI environment + run: .github/setup.sh + - name: Execute the build + run: .github/build.sh diff --git a/LICENSE.txt b/LICENSE.txt index e896c7f..9356d7c 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,4 @@ -Copyright (c) 2008 - 2014, Board of Regents of the University of -Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck -Institute of Molecular Cell Biology and Genetics. +Copyright (c) 2008 - 2023, SciJava developers. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/README.md b/README.md index 9951263..c46d24b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![](https://github.com/scijava/scripting-jython/actions/workflows/build-main.yml/badge.svg)](https://github.com/scijava/scripting-jython/actions/workflows/build-main.yml) + # Jython Scripting This library provides a diff --git a/pom.xml b/pom.xml index 5727a41..8ef0f03 100644 --- a/pom.xml +++ b/pom.xml @@ -1,22 +1,25 @@ - + 4.0.0 org.scijava pom-scijava - 3.4 + 34.0.0 scripting-jython - 0.1.5-SNAPSHOT + 1.0.2-SNAPSHOT SciJava Scripting: Jython JSR-223-compliant Jython scripting language plugin. - http://scijava.org/ + https://github.com/scijava/scripting-jython 2008 - + + SciJava + https://scijava.org/ + Simplified BSD License @@ -25,33 +28,33 @@ - - dscho - Johannes Schindelin - schindelin@wisc.edu - http://loci.wisc.edu/people/johannes-schindelin - UW-Madison LOCI - http://loci.wisc.edu/ - - architect - developer - - -6 - ctrueden Curtis Rueden - ctrueden@wisc.edu - http://loci.wisc.edu/people/curtis-rueden - UW-Madison LOCI - http://loci.wisc.edu/ + https://imagej.net/people/ctrueden - architect + lead developer + debugger + reviewer + support + maintainer - -6 + + + Johannes Schindelin + https://imagej.net/people/dscho + founder + dscho + + + Mark Hiner + https://imagej.net/people/hinerm + hinerm + + @@ -64,22 +67,31 @@ - scm:git:git://github.com/scijava/scripting-jython + scm:git:https://github.com/scijava/scripting-jython scm:git:git@github.com:scijava/scripting-jython HEAD https://github.com/scijava/scripting-jython - GitHub Issues https://github.com/scijava/scripting-jython/issues - - Jenkins - https://jenkins.imagej.net/job/scripting-Jython/ + GitHub Actions + https://github.com/scijava/scripting-jython/actions + + org.scijava.plugins.scripting.jython + + bsd_2 + SciJava developers. + **/script_templates/** + + + sign,deploy-to-scijava + + @@ -89,9 +101,8 @@ - org.scijava - jython-shaded - 2.5.3 + org.python + jython-slim @@ -101,30 +112,4 @@ test - - - - - maven-jar-plugin - - - - org.scijava.plugins.scripting.jython - - - - - - org.codehaus.mojo - license-maven-plugin - - bsd_2 - Board of Regents of the University of -Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck -Institute of Molecular Cell Biology and Genetics. - - - - - diff --git a/src/main/java/org/scijava/plugins/scripting/jython/JythonBindings.java b/src/main/java/org/scijava/plugins/scripting/jython/JythonBindings.java deleted file mode 100644 index 595f85a..0000000 --- a/src/main/java/org/scijava/plugins/scripting/jython/JythonBindings.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * #%L - * JSR-223-compliant Jython scripting language plugin. - * %% - * Copyright (C) 2008 - 2014 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package org.scijava.plugins.scripting.jython; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.script.Bindings; - -import org.python.core.PyStringMap; -import org.python.util.PythonInterpreter; - -/** - * A {@link Bindings} wrapper around Jython's local variables. - * - * @author Johannes Schindelin - */ -public class JythonBindings implements Bindings { - - protected final PythonInterpreter interpreter; - - public JythonBindings(final PythonInterpreter interpreter) { - this.interpreter = interpreter; - } - - @Override - public int size() { - return interpreter.getLocals().__len__(); - } - - @Override - public boolean isEmpty() { - return size() == 0; - } - - @Override - public boolean containsKey(Object key) { - return get(key) != null; - } - - @Override - public boolean containsValue(Object value) { - for (final Object value2 : values()) { - if (value.equals(value2)) return true; - } - return false; - } - - @Override - public Object get(Object key) { - try { - return interpreter.get((String)key); - } catch (Error e) { - return null; - } - } - - @Override - public Object put(String key, Object value) { - final Object result = get(key); - try { - interpreter.set(key, value); - } catch (Error e) { - // ignore - } - return result; - } - - @Override - public Object remove(Object key) { - final Object result = get(key); - if (result != null) interpreter.getLocals().__delitem__((String)key); - return result; - } - - @Override - public void putAll(Map toMerge) { - for (final Entry entry : toMerge.entrySet()) { - put(entry.getKey(), entry.getValue()); - } - } - - private PyStringMap dict() { - return (PyStringMap)interpreter.getLocals(); - } - - @Override - public void clear() { - dict().clear(); - } - - @Override - public Set keySet() { - final Set result = new HashSet(); - for (final Object name : dict().keys()) { - result.add(name.toString()); - } - return result; - } - - @Override - public Collection values() { - final List result = new ArrayList(); - for (final Object name : dict().keys()) try { - result.add(get(name)); - } catch (Error exc) { - // ignore for now - } - return result; - } - - @Override - public Set> entrySet() { - final Set> result = new HashSet>(); - for (final Object name : dict().keys()) { - result.add(new Entry() { - - @Override - public String getKey() { - return name.toString(); - } - - @Override - public Object getValue() { - return get(name); - } - - @Override - public Object setValue(Object value) { - throw new UnsupportedOperationException(); - } - }); - } - return result; - } - -} diff --git a/src/main/java/org/scijava/plugins/scripting/jython/JythonScriptEngine.java b/src/main/java/org/scijava/plugins/scripting/jython/JythonScriptEngine.java deleted file mode 100644 index 8fdee6c..0000000 --- a/src/main/java/org/scijava/plugins/scripting/jython/JythonScriptEngine.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * #%L - * JSR-223-compliant Jython scripting language plugin. - * %% - * Copyright (C) 2008 - 2014 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -package org.scijava.plugins.scripting.jython; - -import java.io.Reader; -import java.io.Writer; - -import javax.script.ScriptContext; -import javax.script.ScriptEngine; -import javax.script.ScriptException; - -import org.python.core.Py; -import org.python.util.PythonInterpreter; -import org.scijava.script.AbstractScriptEngine; - -/** - * A Python interpreter based on Jython. - * - * @author Johannes Schindelin - */ -public class JythonScriptEngine extends AbstractScriptEngine -{ - - protected final PythonInterpreter interpreter; - - public JythonScriptEngine() { - interpreter = new PythonInterpreter(); - engineScopeBindings = new JythonBindings(interpreter); - } - - @Override - public Object eval(final String script) throws ScriptException { - setup(); - try { - return interpreter.eval(script); - } - catch (final Exception e) { - throw new ScriptException(e); - } - } - - @Override - public Object eval(final Reader reader) throws ScriptException { - setup(); - try { - final String filename = getString(ScriptEngine.FILENAME); - return Py.runCode(interpreter.compile(reader, filename), null, interpreter.getLocals()); - } - catch (final Exception e) { - throw new ScriptException(e); - } - } - - protected void setup() { - final ScriptContext context = getContext(); - final Reader reader = context.getReader(); - if (reader != null) { - interpreter.setIn(reader); - } - final Writer writer = context.getWriter(); - if (writer != null) { - interpreter.setOut(writer); - } - final Writer errorWriter = context.getErrorWriter(); - if (errorWriter != null) { - interpreter.setErr(errorWriter); - } - } - - private String getString(final String key) { - Object result = get(key); - return result == null ? null : result.toString(); - } -} diff --git a/src/main/java/org/scijava/plugins/scripting/jython/JythonScriptLanguage.java b/src/main/java/org/scijava/plugins/scripting/jython/JythonScriptLanguage.java index 0cf9e24..8fe493a 100644 --- a/src/main/java/org/scijava/plugins/scripting/jython/JythonScriptLanguage.java +++ b/src/main/java/org/scijava/plugins/scripting/jython/JythonScriptLanguage.java @@ -2,9 +2,7 @@ * #%L * JSR-223-compliant Jython scripting language plugin. * %% - * Copyright (C) 2008 - 2014 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2008 - 2023 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,33 +31,57 @@ import javax.script.ScriptEngine; +import org.python.core.PyBoolean; +import org.python.core.PyFloat; +import org.python.core.PyInteger; import org.python.core.PyNone; +import org.python.core.PyObject; +import org.python.core.PyString; +import org.scijava.Context; +import org.scijava.plugin.Parameter; import org.scijava.plugin.Plugin; import org.scijava.script.AdaptedScriptLanguage; import org.scijava.script.ScriptLanguage; /** * An adapter of the Jython interpreter to the SciJava scripting interface. - * + * * @author Johannes Schindelin + * @author Mark Hiner * @see ScriptEngine */ -@Plugin(type = ScriptLanguage.class, name = "Python") +@Plugin(type = ScriptLanguage.class, name = "Jython") public class JythonScriptLanguage extends AdaptedScriptLanguage { + @Parameter + private Context context; + public JythonScriptLanguage() { super("jython"); } - @Override - public ScriptEngine getScriptEngine() { - // TODO: Consider adapting the wrapped ScriptEngineFactory's ScriptEngine. - return new JythonScriptEngine(); - } - @Override public Object decode(final Object object) { - return object instanceof PyNone ? null : object; + if (object instanceof PyNone) return null; + if (object instanceof PyBoolean) { + return ((PyBoolean) object).getBooleanValue(); + } + if (object instanceof PyInteger) { + return ((PyInteger) object).getValue(); + } + if (object instanceof PyFloat) { + return ((PyFloat) object).getValue(); + } + if (object instanceof PyString) { + return ((PyString) object).getString(); + } + if (object instanceof PyObject) { + // Unwrap Jython objects when they wrap Java ones. + final PyObject pyObj = (PyObject) object; + final Class javaType = pyObj.getType().getProxyType(); + if (javaType != null) return pyObj.__tojava__(javaType); + } + return object; } } diff --git a/src/main/resources/script-templates/Python/Greeting.py b/src/main/resources/script_templates/Intro/Greeting.py similarity index 70% rename from src/main/resources/script-templates/Python/Greeting.py rename to src/main/resources/script_templates/Intro/Greeting.py index a08cce7..00bae41 100644 --- a/src/main/resources/script-templates/Python/Greeting.py +++ b/src/main/resources/script_templates/Intro/Greeting.py @@ -1,5 +1,5 @@ -# @String name -# @OUTPUT String greeting +#@ String (label="Please enter your name", description="Name field") name +#@output String greeting # A Jython script with parameters. # It is the duty of the scripting framework to harvest diff --git a/src/main/resources/script-templates/Python/Hello_World.py b/src/main/resources/script_templates/Intro/Hello_World.py similarity index 100% rename from src/main/resources/script-templates/Python/Hello_World.py rename to src/main/resources/script_templates/Intro/Hello_World.py diff --git a/src/test/java/org/scijava/plugins/scripting/jython/JythonTest.java b/src/test/java/org/scijava/plugins/scripting/jython/JythonTest.java index b86143a..60b5877 100644 --- a/src/test/java/org/scijava/plugins/scripting/jython/JythonTest.java +++ b/src/test/java/org/scijava/plugins/scripting/jython/JythonTest.java @@ -2,9 +2,7 @@ * #%L * JSR-223-compliant Jython scripting language plugin. * %% - * Copyright (C) 2008 - 2014 Board of Regents of the University of - * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck - * Institute of Molecular Cell Biology and Genetics. + * Copyright (C) 2008 - 2023 SciJava developers. * %% * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -33,8 +31,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; -import java.io.IOException; +import java.math.BigInteger; import java.util.concurrent.ExecutionException; import javax.script.Bindings; @@ -42,7 +42,10 @@ import javax.script.ScriptEngine; import javax.script.ScriptException; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import org.python.jsr223.PyScriptEngine; import org.scijava.Context; import org.scijava.script.ScriptLanguage; import org.scijava.script.ScriptModule; @@ -50,35 +53,42 @@ /** * Jython unit tests. - * + * * @author Johannes Schindelin */ public class JythonTest { + private Context context; + private ScriptService scriptService; + + @Before + public void setUp() { + context = new Context(); + scriptService = context.getService(ScriptService.class); + } + + @After + public void tearDown() { + context.dispose(); + } + @Test - public void testBasic() throws InterruptedException, ExecutionException, - IOException, ScriptException - { - final Context context = new Context(ScriptService.class); - final ScriptService scriptService = context.getService(ScriptService.class); + public void testBasic() throws InterruptedException, ExecutionException { final String script = "1 + 2"; final ScriptModule m = scriptService.run("add.py", script, true).get(); - final Object result = m.getReturnValue(); - // NB: Result is of type org.python.core.PyInteger. - assertEquals("3", result.toString()); + final Object result = m.getInfo().getLanguage().decode(m.getReturnValue()); + assertSame(Integer.class, result.getClass()); + assertEquals(3, result); } @Test public void testLocals() throws ScriptException { - final Context context = new Context(ScriptService.class); - final ScriptService scriptService = context.getService(ScriptService.class); - final ScriptLanguage language = scriptService.getLanguageByExtension("py"); final ScriptEngine engine = language.getScriptEngine(); - assertEquals(JythonScriptEngine.class, engine.getClass()); + assertEquals(PyScriptEngine.class, engine.getClass()); engine.put("hello", 17); - assertEquals("17", engine.eval("hello").toString()); - assertEquals("17", engine.get("hello").toString()); + assertEquals(17, language.decode(engine.eval("hello"))); + assertEquals(17, language.decode(engine.get("hello"))); final Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); bindings.clear(); @@ -86,20 +96,15 @@ public void testLocals() throws ScriptException { } @Test - public void testParameters() throws InterruptedException, ExecutionException, - IOException, ScriptException - { - final Context context = new Context(ScriptService.class); - final ScriptService scriptService = context.getService(ScriptService.class); - + public void testParameters() throws InterruptedException, ExecutionException { final String script = "" + // - "# @ScriptService ss\n" + // - "# @OUTPUT String language\n" + // + "#@ ScriptService ss\n" + // + "#@output String language\n" + // "language = ss.getLanguageByName('jython').getLanguageName()\n"; final ScriptModule m = scriptService.run("hello.py", script, true).get(); final Object actual = m.getOutput("language"); - final String expected = + final String expected = // scriptService.getLanguageByName("jython").getLanguageName(); assertEquals(expected, actual); } @@ -108,31 +113,26 @@ public void testParameters() throws InterruptedException, ExecutionException, * Tests that variables assigned a primitive long value have the expected * type. *

- * There is a crazy bug in {@link org.python.jsr223.PyScriptEngine}, which - * results in variables assigned a long primitive to somehow end up as (or + * There was a crazy bug in {@link PyScriptEngine} version 2.5.3, which + * resulted in variables assigned a long primitive to somehow end up as (or * appearing to end up as) {@link java.math.BigInteger} instances instead. See * this thread on the jython-users mailing list for discussion. *

*

- * This test ensures that that specific problem gets flagged if it occurs. As - * long as we keep using our own Jython {@code ScriptEngine} implementation - * (i.e.: {@link org.scijava.plugins.scripting.jython.JythonScriptEngine}), - * the problem does not occur. But if we switch to the stock JSR-223 Jython - * {@code ScriptEngine} (i.e.: {@link org.python.jsr223.PyScriptEngine}), the - * problem manifests. See {@link JythonScriptLanguage#getScriptEngine()}. + * This test ensures that that specific problem gets flagged if it recurs. + * Previously, to avoid it, we used our own Jython {@code ScriptEngine} + * implementation + * ({@code org.scijava.plugins.scripting.jython.JythonScriptEngine}). But + * since Jython 2.7.0, the stock JSR-223 Jython {@code ScriptEngine} (i.e.: + * {@link org.python.jsr223.PyScriptEngine}) no longer has this issue. *

*/ @Test - public void testLongType() throws InterruptedException, ExecutionException, - IOException, ScriptException - { - final Context context = new Context(ScriptService.class); - final ScriptService scriptService = context.getService(ScriptService.class); - + public void testLongType() throws InterruptedException, ExecutionException { final String script = "" + // - "# @OUTPUT String varType\n" + // + "#@output String varType\n" + // "a = 10L\n" + // "varType = type(a)\n"; final ScriptModule m = scriptService.run("longType.py", script, true).get(); @@ -141,4 +141,35 @@ public void testLongType() throws InterruptedException, ExecutionException, final String expected = ""; assertEquals(expected, actual); } + + @Test + public void testEval() throws ScriptException { + final ScriptLanguage language = scriptService.getLanguageByExtension("py"); + final ScriptEngine engine = language.getScriptEngine(); + assertEquals(PyScriptEngine.class, engine.getClass()); + + final Object sum = engine.eval("2 + 3"); + assertEquals(5, sum); + + final String n1 = "112233445566778899"; + final String n2 = "998877665544332211"; + final Object bigNum = engine.eval(n1 + "*" + n2); + assertEquals(new BigInteger(n1).multiply(new BigInteger(n2)), bigNum); + + final Object varAssign = engine.eval("a = 4 + 5"); + assertNull(varAssign); + } + + @Test + public void testGetPID() throws InterruptedException, ExecutionException { + final String script = "" + // + "#@output Object pid\n" + // + "import os\n" + // + "pid = os.getpid()\n"; + final ScriptModule m = scriptService.run("getpid.py", script, true).get(); + + final Object pid = m.getOutput("pid"); + assertTrue(pid instanceof Number); + assertTrue(((Number) pid).longValue() > 0); + } }