/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.scripting; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.io.Writer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.script.ScriptException; import org.junit.Test; import org.python.jsr223.PyScriptEngine; import org.python.jsr223.PyScriptEngineFactory; import de.rcenvironment.core.scripting.python.PythonOutputWriter; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.testing.CommonTestOptions; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncTaskService; /** * Tests the execution behavior of the Python script engine. Especially, during parallel script execution. * * @author Doreen Seider * @author Robert Mischke (added test size switching) */ public class PythonScriptEngineTest { private static final int ITERATION_COUNT_STANDARD_TESTING = 20; // arbitrary; adjust as needed private static final int TIMEOUT_SEC_STANDARD_TESTING = 10; private static final int ITERATION_COUNT_EXTENDED_TESTING = 800; private static final int TIMEOUT_SEC_EXTENDED_TESTING = 90; private static final int FALLBACK_TEST_TIMEOUT = 120000; private static final String SCRIPT = "import sys\nfor i in range(0, 5000):\n print '%s%s'\nfor i in range(0, 10000):\n " + "sys.stderr.write('%s%s\\n')\n sys.stderr.flush()\nfor i in range(0, 5000):\n print '%s%s'"; private static final String OUTPUT_PREFIX_STDOUT = "stdout "; private static final String OUTPUT_PREFIX_STDERR = "stderr "; private final AtomicInteger stdoutCloseCount = new AtomicInteger(0); private final AtomicInteger stderrCloseCount = new AtomicInteger(0); private final AtomicInteger wrongOutputCount = new AtomicInteger(0); // /** // * Sets JVM properties required for Jython script execution. // */ // @BeforeClass // public static void setUp() { // ScriptingUtils.setJVMPropertiesForJython270Support(); // } /** * Tests correct output handling of multiple scripts are executed in parallel threads but synchronized. * * @throws InterruptedException on error */ @Test(timeout = FALLBACK_TEST_TIMEOUT) public void testFetchingStdoutErrDuringSynchronizedParallelExecution() throws InterruptedException { testFetchingStdoutErrDuringParallelExecution(true); assertEquals(0, wrongOutputCount.get()); } private void testFetchingStdoutErrDuringParallelExecution(final boolean isSynchronized) throws InterruptedException { final int waitInterval = CommonTestOptions.selectStandardOrExtendedValue(TIMEOUT_SEC_STANDARD_TESTING, TIMEOUT_SEC_EXTENDED_TESTING); final int scriptEvalCount = CommonTestOptions.selectStandardOrExtendedValue(ITERATION_COUNT_STANDARD_TESTING, ITERATION_COUNT_EXTENDED_TESTING); final CountDownLatch iterationFinishedLatch = new CountDownLatch(scriptEvalCount); final AsyncTaskService threadPool = ConcurrencyUtils.getAsyncTaskService(); for (int i = 0; i < scriptEvalCount; i++) { final String suffix = String.valueOf(i); threadPool.execute(new Runnable() { @Override public void run() { if (isSynchronized) { synchronized (PythonScriptEngineTest.this) { executePythonScript(suffix, iterationFinishedLatch); } } else { executePythonScript(suffix, iterationFinishedLatch); } } }); } assertTrue(iterationFinishedLatch.await(waitInterval, TimeUnit.SECONDS)); assertEquals(scriptEvalCount, stdoutCloseCount.get()); assertEquals(scriptEvalCount, stderrCloseCount.get()); } private void executePythonScript(String suffix, CountDownLatch iterationFinishedLatch) { PyScriptEngineFactory factory = new PyScriptEngineFactory(); PyScriptEngine engine = (PyScriptEngine) factory.getScriptEngine(); try { Writer outWriter = new PythonOutputWriterStub(new Object(), OUTPUT_PREFIX_STDOUT, suffix); engine.getContext().setWriter(outWriter); Writer errWriter = new PythonOutputWriterStub(new Object(), OUTPUT_PREFIX_STDERR, suffix); engine.getContext().setErrorWriter(errWriter); engine.eval(StringUtils.format(SCRIPT, OUTPUT_PREFIX_STDOUT, suffix, OUTPUT_PREFIX_STDERR, suffix, OUTPUT_PREFIX_STDOUT, suffix)); engine.eval(StringUtils.format( "sys.stdout.write('%s')\nsys.stderr.write('%s')\nsys.stdout.flush()\nsys.stderr.flush()", PythonOutputWriter.CONSOLE_END, PythonOutputWriter.CONSOLE_END)); outWriter.close(); errWriter.close(); iterationFinishedLatch.countDown(); } catch (ScriptException | IOException e) { throw new RuntimeException(e); } } /** * Stub implementation of {@link PythonOutputWriter} used by the Jython script engine for stdout and stderr. * * @author Doreen Seider */ class PythonOutputWriterStub extends PythonOutputWriter { private final String outPrefix; private final String outSuffix; PythonOutputWriterStub(Object lock, String outPrefix, String outSuffix) { super(lock, null); this.outPrefix = outPrefix; this.outSuffix = outSuffix; } @Override public void close() throws IOException { super.close(); if (outPrefix.equals(OUTPUT_PREFIX_STDOUT)) { stdoutCloseCount.incrementAndGet(); } else { stderrCloseCount.incrementAndGet(); } } @Override protected void onNewLineToForward(String line) { // line is null if end of output is reached; not relevant for this test case if (line != null && !line.equals(outPrefix + outSuffix + "\n")) { wrongOutputCount.incrementAndGet(); } } } }