Repository: cloudslang/cloud-slang
Issue: read_from_file operation context serialization error

If score tries to create execution message during this operation execution (for example if isRunningTooLong), it fails because  PyFile object, which is serializable  itself ,has field file:TextIOWrapper which is not serializable.

``` bash
(SimpleExecutionRunnable.java:126) ERROR - Error during execution!!!
java.lang.RuntimeException: Failed to serialize execution plan. Error: 
    at io.cloudslang.engine.queue.entities.ExecutionMessageConverter.objToBytes(ExecutionMessageConverter.java:100)
    at io.cloudslang.engine.queue.entities.ExecutionMessageConverter.createPayload(ExecutionMessageConverter.java:45)
    at io.cloudslang.engine.queue.entities.ExecutionMessageConverter.createPayload(ExecutionMessageConverter.java:41)
    at io.cloudslang.worker.management.services.SimpleExecutionRunnable.createInProgressExecutionMessage(SimpleExecutionRunnable.java:387)
    at io.cloudslang.worker.management.services.SimpleExecutionRunnable.isRunningTooLong(SimpleExecutionRunnable.java:330)
    at io.cloudslang.worker.management.services.SimpleExecutionRunnable.shouldStop(SimpleExecutionRunnable.java:175)
    at io.cloudslang.worker.management.services.SimpleExecutionRunnable.executeRegularStep(SimpleExecutionRunnable.java:161)
    at io.cloudslang.worker.management.services.SimpleExecutionRunnable.run(SimpleExecutionRunnable.java:119)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at io.cloudslang.worker.management.services.WorkerThreadFactory$1.run(WorkerThreadFactory.java:33)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.NotSerializableException: org.python.core.io.TextIOWrapper
```


Changed files:
cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/steps/ActionSteps.java
cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/steps/ActionStepsTest.java

diff --git a/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/steps/ActionSteps.java b/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/steps/ActionSteps.java
index cdad7ad78..bc2912ec8 100644
--- a/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/steps/ActionSteps.java
+++ b/cloudslang-runtime/src/main/java/io/cloudslang/lang/runtime/steps/ActionSteps.java
@@ -25,9 +25,13 @@ import io.cloudslang.score.lang.ExecutionRuntimeServices;
 import org.python.core.Py;
 import org.python.core.PyBoolean;
 import org.python.core.PyException;
+import org.python.core.PyFile;
+import org.python.core.PyFunction;
 import org.python.core.PyModule;
 import org.python.core.PyObject;
 import org.python.core.PyStringMap;
+import org.python.core.PySystemState;
+import org.python.core.PyType;
 import org.python.util.PythonInterpreter;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -36,6 +40,7 @@ import java.io.Serializable;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -258,7 +263,7 @@ public class ActionSteps extends AbstractSteps {
         while (localsIterator.hasNext()) {
             String key = localsIterator.next().asString();
             PyObject value = interpreter.get(key);
-            if ((key.startsWith("__") && key.endsWith("__")) || value instanceof PyModule) {
+            if (keyIsExcluded(key, value)) {
                 continue;
             }
             Serializable javaValue = resolveJythonObjectToJava(value);
@@ -268,6 +273,14 @@ public class ActionSteps extends AbstractSteps {
         return returnValue;
     }
 
+    private boolean keyIsExcluded(String key, PyObject value) {
+        return (key.startsWith("__") && key.endsWith("__")) ||
+                value instanceof PyFile ||
+                value instanceof PyModule ||
+                value instanceof PyFunction ||
+                value instanceof PySystemState;
+    }
+
     private Serializable resolveJythonObjectToJava(PyObject value) {
         if (value instanceof PyBoolean) {
             PyBoolean pyBoolean = (PyBoolean) value;
diff --git a/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/steps/ActionStepsTest.java b/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/steps/ActionStepsTest.java
index e18581a81..1caa89773 100644
--- a/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/steps/ActionStepsTest.java
+++ b/cloudslang-runtime/src/test/java/io/cloudslang/lang/runtime/steps/ActionStepsTest.java
@@ -19,7 +19,9 @@ import org.junit.Assert;
 import io.cloudslang.score.api.execution.ExecutionParametersConsts;
 import io.cloudslang.score.events.ScoreEvent;
 import io.cloudslang.score.lang.ExecutionRuntimeServices;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.python.util.PythonInterpreter;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -28,6 +30,8 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
+import java.io.File;
+import java.io.IOException;
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.HashMap;
@@ -48,6 +52,9 @@ public class ActionStepsTest {
 
     private static final long DEFAULT_TIMEOUT = 10000;
 
+    @Rule
+    public TemporaryFolder folder = new TemporaryFolder();
+
     @Autowired
     private ActionSteps actionSteps;
 	ExecutionRuntimeServices executionRuntimeServicesMock = mock(ExecutionRuntimeServices.class);
@@ -465,6 +472,32 @@ public class ActionStepsTest {
         Assert.assertEquals("Python action outputs are not as expected", expectedOutputs, actualOutputs);
     }
 
+    @Test
+    public void doActionInvalidReturnTypes() throws IOException {
+        //prepare doAction arguments
+        RunEnvironment runEnv = new RunEnvironment();
+        File file = folder.newFile();
+        String userPythonScript =
+                "valid = 1\\n" +
+                "with open('" + file.getAbsolutePath() + "', 'r') as f:\\n" +
+                "  f.close()\\n\\n" +
+                "import sys\\n" +
+                "import io\\n" +
+                "def a():\\n" +
+                "  print 'a'\\n\\n";
+
+        //invoke doAction
+        actionSteps.doAction(runEnv, new HashMap<String, Object>(), PYTHON, "", "", executionRuntimeServicesMock, userPythonScript, 2L);
+
+        //extract actual outputs
+        ReturnValues actualReturnValues = runEnv.removeReturnValues();
+        Map<String, Serializable> actualOutputs = actualReturnValues.getOutputs();
+
+        //verify matching
+        Assert.assertEquals("Invalid types passed exclusion",
+                1, actualOutputs.size());
+    }
+
     @Test (expected = RuntimeException.class, timeout = DEFAULT_TIMEOUT)
     public void doActionPythonMissingInputsTest() {
         //prepare doAction arguments