move global variables initialized to init (#54)

* move global variables initialized to init

* modify two test cases to initialize the return status

* modifiy python3Action Dockerfile and Tests
diff --git a/core/python3AiAction/Dockerfile b/core/python3AiAction/Dockerfile
index b815b9e..be7e7f7 100644
--- a/core/python3AiAction/Dockerfile
+++ b/core/python3AiAction/Dockerfile
@@ -56,6 +56,6 @@
 ADD https://raw.githubusercontent.com/apache/incubator-openwhisk-runtime-docker/dockerskeleton%401.3.3/core/actionProxy/actionproxy.py /actionProxy/actionproxy.py
 
 RUN mkdir -p /pythonAction
-ADD https://raw.githubusercontent.com/apache/incubator-openwhisk-runtime-python/3%401.0.3/core/pythonAction/pythonrunner.py /pythonAction/pythonrunner.py
+COPY pythonrunner.py /pythonAction/pythonrunner.py
 
 CMD ["/bin/bash", "-c", "cd /pythonAction && python -u pythonrunner.py"]
diff --git a/core/python3AiAction/pythonrunner.py b/core/python3AiAction/pythonrunner.py
new file mode 100644
index 0000000..70df3fc
--- /dev/null
+++ b/core/python3AiAction/pythonrunner.py
@@ -0,0 +1,100 @@
+"""Executable Python script for running Python actions.
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+"""
+
+import os
+import sys
+import codecs
+import traceback
+sys.path.append('../actionProxy')
+from actionproxy import ActionRunner, main, setRunner
+
+
+class PythonRunner(ActionRunner):
+
+    def __init__(self):
+        ActionRunner.__init__(self, '/action/__main__.py')
+        self.fn = None
+        self.mainFn = 'main'
+        self.global_context = {}
+
+    def initCodeFromString(self, message):
+        # do nothing, defer to build step
+        return True
+
+    def build(self, message):
+        binary = message['binary'] if 'binary' in message else False
+        if not binary:
+            code = message['code']
+            filename = 'action'
+        elif os.path.isfile(self.source):
+            with codecs.open(self.source, 'r', 'utf-8') as m:
+                code = m.read()
+            workdir = os.path.dirname(self.source)
+            sys.path.insert(0, workdir)
+            os.chdir(workdir)
+        else:
+            sys.stderr.write('Zip file does not include ' + os.path.basename(self.source) + '\n')
+            return False
+
+        try:
+            filename = os.path.basename(self.source)
+            self.fn = compile(code, filename=filename, mode='exec')
+            if 'main' in message:
+                self.mainFn = message['main']
+
+            # if the directory 'virtualenv' is extracted out of a zip file
+            path_to_virtualenv = os.path.dirname(self.source) + '/virtualenv'
+            if os.path.isdir(path_to_virtualenv):
+                # activate the virtualenv using activate_this.py contained in the virtualenv
+                activate_this_file = path_to_virtualenv + '/bin/activate_this.py'
+                if os.path.exists(activate_this_file):
+                    with open(activate_this_file) as f:
+                        code = compile(f.read(), activate_this_file, 'exec')
+                        exec(code, dict(__file__=activate_this_file))
+                else:
+                    sys.stderr.write('Invalid virtualenv. Zip file does not include /virtualenv/bin/' + os.path.basename(activate_this_file) + '\n')
+                    return False
+            exec(self.fn, self.global_context)
+            return True
+        except Exception:
+            traceback.print_exc(file=sys.stderr, limit=0)
+            return False
+
+    def verify(self):
+        return self.fn is not None
+
+    def run(self, args, env):
+        result = None
+        try:
+            os.environ = env
+            self.global_context['param'] = args
+            exec('fun = %s(param)' % self.mainFn, self.global_context)
+            result = self.global_context['fun']
+        except Exception:
+            traceback.print_exc(file=sys.stderr)
+
+        if result and isinstance(result, dict):
+            return (200, result)
+        else:
+            return (502, {'error': 'The action did not return a dictionary.'})
+
+if __name__ == '__main__':
+    setRunner(PythonRunner())
+    main()
diff --git a/core/pythonAction/pythonrunner.py b/core/pythonAction/pythonrunner.py
index b6cc3d7..70df3fc 100644
--- a/core/pythonAction/pythonrunner.py
+++ b/core/pythonAction/pythonrunner.py
@@ -71,6 +71,7 @@
                 else:
                     sys.stderr.write('Invalid virtualenv. Zip file does not include /virtualenv/bin/' + os.path.basename(activate_this_file) + '\n')
                     return False
+            exec(self.fn, self.global_context)
             return True
         except Exception:
             traceback.print_exc(file=sys.stderr, limit=0)
@@ -84,7 +85,6 @@
         try:
             os.environ = env
             self.global_context['param'] = args
-            exec(self.fn, self.global_context)
             exec('fun = %s(param)' % self.mainFn, self.global_context)
             result = self.global_context['fun']
         except Exception:
diff --git a/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala b/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
index f36b8d9..132811b 100644
--- a/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
+++ b/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
@@ -267,10 +267,7 @@
       val (out, err) = withActionContainer() { c =>
         val (initCode, initRes) = c.init(initPayload(code, main = "main"))
         if (initErrorsAreLogged) {
-          initCode should be(200)
-          val args = JsObject("msg" -> JsString("any"))
-          val (runCode, runRes) = c.run(runPayload(args))
-          runCode should be(502)
+          initCode should be(502)
         } else {
           // it actually means it is actionloop
           // it checks the error at init time
@@ -420,10 +417,7 @@
 
       if (initErrorsAreLogged) {
         val (initCode, res) = c.init(initPayload(code))
-        initCode should be(200)
-
-        val (runCode, runRes) = c.run(runPayload(JsObject()))
-        runCode should be(502)
+        initCode should be(502)
       } else {
         // action loop detects those errors at init time
         val (initCode, initRes) = c.init(initPayload(code))