You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2018/04/22 18:05:58 UTC

[GitHub] sciabarracom closed pull request #7: Source and Main support, refactored test suite

sciabarracom closed pull request #7: Source and Main support, refactored test suite
URL: https://github.com/apache/incubator-openwhisk-runtime-go/pull/7
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/.gitignore b/.gitignore
index 6e7acee..5871750 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,17 @@
 .gradle/
 .gogradle/
+*.log
 vendor/
 core/proxy
+core/go19action/proxy
+core/go19action/gobuild
+core/goproxy/proxy
 openwhisk/_test/exec
-openwhisk/_test/exec.zip
+openwhisk/_test/hi
+openwhisk/_test/hello_greeting
+openwhisk/_test/hello_message
+openwhisk/_test/*.zip
+openwhisk/_test/compile/
 openwhisk/action/
+openwhisk/compile/
+openwhisk/debug.test
diff --git a/README.md b/README.md
index 8eacd6c..75f2727 100644
--- a/README.md
+++ b/README.md
@@ -2,5 +2,14 @@
 
 This is an  OpenWhisk runtime for  Golang.
 
-Check [test documentation](./test/README.md) for testing.
+Build a proxy in ./core/proxy with ./gradlew build
+
+Run a the test suite with ./gradlew test
+
+For running tests you need:
+
+- jq
+- go (1.9) in the path
+- GOPATH set
+
 
diff --git a/core/gobuild b/core/gobuild
new file mode 100755
index 0000000..fcb3fe5
--- /dev/null
+++ b/core/gobuild
@@ -0,0 +1,40 @@
+#!/bin/bash
+if test -z "$1"
+then echo "usage: file-or-dir [main]" ; exit 1
+fi
+exec="${2:-main}"
+# absolute path of taget dir or file
+target="$(readlink -f $1)"
+# prepare a compilation dir
+compiledir="$(mktemp -d)"
+mkdir -p "$compiledir/src/action" "$compiledir/src/main" 
+# capitalized main function name
+main="$(tr '[:lower:]' '[:upper:]' <<< ${exec:0:1})${exec:1}"
+# preparing for compilation
+if test -d "$target"
+# copy all the files unzipped
+then cp -rf "$target"/* "$compiledir/src/"
+     dest="$target/$exec"
+# if we have a single file action, copy it
+else cp "$target" "$compiledir/src/action/action.go"
+     dest="$target"
+fi 
+# prepare the main
+cat <<EOF >$compiledir/src/main/main.go
+package main
+
+import (
+	"os"
+	"action"
+
+	"github.com/apache/incubator-openwhisk-client-go/whisk"
+)
+
+func main() {
+	whisk.StartWithArgs(action.$main, os.Args[1:])
+}
+EOF
+# build it
+cd "$compiledir"
+GOPATH="$GOPATH:$compiledir" go build -i action
+GOPATH="$GOPATH:$compiledir" go build -o "$dest" main
diff --git a/main/proxy.go b/main/proxy.go
index fe8c0bc..0f4c05e 100644
--- a/main/proxy.go
+++ b/main/proxy.go
@@ -20,6 +20,7 @@ import (
 	"flag"
 	"io/ioutil"
 	"log"
+	"os"
 
 	"github.com/sciabarracom/incubator-openwhisk-runtime-go/openwhisk"
 )
@@ -27,10 +28,16 @@ import (
 // disable stderr except when debugging
 var debug = flag.Bool("debug", false, "enable debug output")
 
+var compiler = flag.String("compiler", os.Getenv("COMPILER"), "define the compiler on the command line")
+
 func main() {
 	flag.Parse()
+
 	if !*debug {
+		// hide log unless you are debugging
 		log.SetOutput(ioutil.Discard)
 	}
-	openwhisk.Start()
+	// start the balls rolling
+	ap := openwhisk.NewActionProxy("./action", *compiler, os.Stdout, !*debug)
+	ap.Start(8080)
 }
diff --git a/openwhisk/_test/action/.gitignore b/openwhisk/_test/action/.gitignore
new file mode 100644
index 0000000..2d71be7
--- /dev/null
+++ b/openwhisk/_test/action/.gitignore
@@ -0,0 +1,8 @@
+.DS_Store
+/.build
+/Packages
+/*.xcodeproj
+Package.resolved
+*.json
+.*_history
+.viminfo
diff --git a/test/src/hello/hello.go b/openwhisk/_test/action/hello.go
similarity index 91%
rename from test/src/hello/hello.go
rename to openwhisk/_test/action/hello.go
index b0592d0..b5f0ce6 100644
--- a/test/src/hello/hello.go
+++ b/openwhisk/_test/action/hello.go
@@ -1,9 +1,9 @@
-package hello
+package action
 
 import (
 	"encoding/json"
 	"fmt"
-	"log"
+	//"log"
 )
 
 // Hello receive an event in format
@@ -23,7 +23,7 @@ func Hello(event json.RawMessage) (json.RawMessage, error) {
 	if input.Name != "" {
 		// handle the event
 		output.Greetings = "Hello, " + input.Name
-		log.Println(output.Greetings)
+		fmt.Println(output.Greetings)
 		return json.Marshal(output)
 	}
 	return nil, fmt.Errorf("no name specified")
diff --git a/test/src/hello/hello_test.go b/openwhisk/_test/action/hello_test.go
similarity index 93%
rename from test/src/hello/hello_test.go
rename to openwhisk/_test/action/hello_test.go
index 0624cad..fd1d67e 100644
--- a/test/src/hello/hello_test.go
+++ b/openwhisk/_test/action/hello_test.go
@@ -1,4 +1,4 @@
-package hello
+package action
 
 import (
 	"fmt"
@@ -9,6 +9,7 @@ func ExampleHello() {
 	data, _ := Hello(name)
 	fmt.Printf("%s", data)
 	// Output:
+	// Hello, Mike
 	// {"greetings":"Hello, Mike"}
 }
 
diff --git a/openwhisk/_test/action/main.go b/openwhisk/_test/action/main.go
new file mode 100644
index 0000000..2dec4da
--- /dev/null
+++ b/openwhisk/_test/action/main.go
@@ -0,0 +1,12 @@
+package action
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+// Main forwading to Hello
+func Main(event json.RawMessage) (json.RawMessage, error) {
+	fmt.Println("Main:")
+	return Hello(event)
+}
diff --git a/openwhisk/_test/build.sh b/openwhisk/_test/build.sh
index 82f3378..81699d3 100755
--- a/openwhisk/_test/build.sh
+++ b/openwhisk/_test/build.sh
@@ -1,5 +1,33 @@
 #!/bin/bash
 cd "$(dirname $0)"
-test -e exec || GOARCH=amd64 GOOS=linux go build -o exec exec.go
+
+function build {
+  if ! test -e $1
+  then cp $1.src $1.go
+       GOARCH=amd64 GOOS=linux go build -a -o $1 $1.go
+       rm $1.go
+  fi
+}
+
+function zipit {
+  if ! test -e $1
+  then
+    mkdir $$
+    cp $2 $$/$3
+    zip -q -j $1 $$/$3
+    rm -rf $$
+  fi 
+}
+
+build exec
 test -e exec.zip || zip -q -r exec.zip exec etc dir
-cd -
+build hi
+zipit hi.zip hi main
+build hello_message
+zipit hello_message.zip hello_message main
+zipit hello_message1.zip hello_message message
+build hello_greeting
+zipit hello_greeting.zip hello_greeting main
+zipit hello_greeting1.zip hello_greeting greeting
+
+
diff --git a/openwhisk/_test/dir/etc b/openwhisk/_test/dir/etc
index e69de29..d00491f 100644
--- a/openwhisk/_test/dir/etc
+++ b/openwhisk/_test/dir/etc
@@ -0,0 +1 @@
+1
diff --git a/openwhisk/_test/etc b/openwhisk/_test/etc
index e69de29..d00491f 100644
--- a/openwhisk/_test/etc
+++ b/openwhisk/_test/etc
@@ -0,0 +1 @@
+1
diff --git a/test/src/empty.go b/openwhisk/_test/exec.src
similarity index 100%
rename from test/src/empty.go
rename to openwhisk/_test/exec.src
diff --git a/openwhisk/_test/hello.src b/openwhisk/_test/hello.src
new file mode 100644
index 0000000..2bfd8c1
--- /dev/null
+++ b/openwhisk/_test/hello.src
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+package action
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+func Main(event json.RawMessage) (json.RawMessage, error) {
+	var obj map[string]interface{}
+	json.Unmarshal(event, &obj)
+	name, ok := obj["name"].(string)
+	if !ok {
+		name = "Stranger"
+	}
+	fmt.Printf("name=%s\n", name)
+	msg := map[string]string{"message": ("Hello, " + name + "!")}
+	return json.Marshal(msg)
+}
diff --git a/openwhisk/_test/hello1.src b/openwhisk/_test/hello1.src
new file mode 100644
index 0000000..95e1586
--- /dev/null
+++ b/openwhisk/_test/hello1.src
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package action
+
+import (
+	"encoding/json"
+	"fmt"
+)
+
+func Hello(event json.RawMessage) (json.RawMessage, error) {
+	var obj struct {
+		Name string `json:",omitempty"`
+	}
+	err := json.Unmarshal(event, &obj)
+	if err != nil {
+		return nil, err
+	}
+	name := obj.Name
+	if name == "" {
+		name = "Stranger"
+	}
+	fmt.Printf("name=%s\n", name)
+	msg := map[string]string{"hello": ("Hello, " + name + "!")}
+	return json.Marshal(msg)
+}
diff --git a/test/src/hello_greeting.go b/openwhisk/_test/hello_greeting.src
similarity index 83%
rename from test/src/hello_greeting.go
rename to openwhisk/_test/hello_greeting.src
index f70bd2c..24d9f2f 100644
--- a/test/src/hello_greeting.go
+++ b/openwhisk/_test/hello_greeting.src
@@ -17,14 +17,14 @@
 package main
 
 import (
-	"log"
+	//"log"
 	"os"
 
 	"github.com/apache/incubator-openwhisk-client-go/whisk"
-	"github.com/sciabarracom/incubator-openwhisk-runtime-go/test/src/hello"
+	"github.com/sciabarracom/incubator-openwhisk-runtime-go/openwhisk/_test/action"
 )
 
 func main() {
-	log.SetPrefix("hello_greeting: ")
-	whisk.StartWithArgs(hello.Hello, os.Args[1:])
+	//log.SetPrefix("hello_greeting: ")
+	whisk.StartWithArgs(action.Hello, os.Args[1:])
 }
diff --git a/test/src/hello_message.go b/openwhisk/_test/hello_message.src
similarity index 94%
rename from test/src/hello_message.go
rename to openwhisk/_test/hello_message.src
index c29170d..3c89484 100644
--- a/test/src/hello_message.go
+++ b/openwhisk/_test/hello_message.src
@@ -18,7 +18,7 @@ package main
 
 import (
 	"encoding/json"
-	"log"
+	"fmt"
 	"os"
 
 	"github.com/apache/incubator-openwhisk-client-go/whisk"
@@ -31,12 +31,12 @@ func sayHello(event json.RawMessage) (json.RawMessage, error) {
 	if !ok {
 		name = "Stranger"
 	}
-	log.Printf("name=%s\n", name)
+	fmt.Printf("name=%s\n", name)
 	msg := map[string]string{"message": ("Hello, " + name + "!")}
 	return json.Marshal(msg)
 }
 
 func main() {
-	log.SetPrefix("hello_message: ")
+	//log.SetPrefix("hello_message: ")
 	whisk.StartWithArgs(sayHello, os.Args[1:])
 }
diff --git a/test/src/hi.go b/openwhisk/_test/hi.src
similarity index 100%
rename from test/src/hi.go
rename to openwhisk/_test/hi.src
diff --git a/openwhisk/_test/postcompile.sh b/openwhisk/_test/postcompile.sh
new file mode 100755
index 0000000..00415d0
--- /dev/null
+++ b/openwhisk/_test/postcompile.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+FILE=${1:?compiled file}
+file -i "$FILE"
+echo '{"name":"Mike"}' | $FILE 3>/tmp/$$
+cat /tmp/$$
+rm /tmp/$$
diff --git a/openwhisk/_test/precompile.sh b/openwhisk/_test/precompile.sh
new file mode 100755
index 0000000..8f7ba2f
--- /dev/null
+++ b/openwhisk/_test/precompile.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+cd "$(dirname $0)"
+SRC=${1:?source}
+ID=${2:?numbe}
+go get github.com/apache/incubator-openwhisk-client-go/whisk
+rm -Rvf compile/$ID >/dev/null
+mkdir -p compile/$ID
+if test -d "$SRC"
+then cp -r "$SRC" compile/$ID
+else cp $SRC compile/$ID/exec
+fi
+
diff --git a/openwhisk/_test/zips.sh b/openwhisk/_test/zips.sh
new file mode 100755
index 0000000..d212614
--- /dev/null
+++ b/openwhisk/_test/zips.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+cd "$(dirname $0)"
+rm action.zip 2>/dev/null
+zip -r -q action.zip action
diff --git a/openwhisk/actionProxy.go b/openwhisk/actionProxy.go
index 5889b01..886573a 100644
--- a/openwhisk/actionProxy.go
+++ b/openwhisk/actionProxy.go
@@ -20,40 +20,69 @@ package openwhisk
 import (
 	"fmt"
 	"log"
+	"net"
 	"net/http"
 	"os"
 )
 
-// theServer is the current server
-var theServer http.Server
+// ActionProxy is the container of the data specific to a server
+type ActionProxy struct {
+	// current directory
+	baseDir string
 
-// theChannel is the channel communicating with the action
-var theExecutor *Executor
+	// Compiler is the script to use to compile your code when action are source code
+	compiler string
+
+	// RemoveFailedAction perform  cleanup of failed actions (disabled when debugging)
+	removeFailedAction bool
+
+	// index current dir
+	currentDir int
+
+	// theChannel is the channel communicating with the action
+	theExecutor *Executor
+
+	// log file
+	logFile *os.File
+}
+
+// NewActionProxy creates a new action proxy that can handle http requests
+func NewActionProxy(baseDir string, compiler string, logFile *os.File, removeFailedAction bool) *ActionProxy {
+	os.Mkdir(baseDir, 0755)
+	return &ActionProxy{
+		baseDir,
+		compiler,
+		removeFailedAction,
+		highestDir(baseDir),
+		nil,
+		logFile,
+	}
+}
 
 // StartLatestAction tries to start
 // the more recently uplodaded
 // action if valid, otherwise remove it
 // and fallback to the previous, if any
-func StartLatestAction() error {
+func (ap *ActionProxy) StartLatestAction(main string) error {
 
 	// find the action if any
-	highestDir := highestDir("./action")
+	highestDir := highestDir(ap.baseDir)
 	if highestDir == 0 {
 		log.Println("no action found")
-		theExecutor = nil
+		ap.theExecutor = nil
 		return fmt.Errorf("no valid actions available")
 	}
 
 	// save the current executor
-	curExecutor := theExecutor
+	curExecutor := ap.theExecutor
 
 	// try to launch the action
-	executable := fmt.Sprintf("./action/%d/exec", highestDir)
-	newExecutor := NewExecutor(executable)
+	executable := fmt.Sprintf("%s/%d/%s", ap.baseDir, highestDir, main)
+	newExecutor := NewExecutor(ap.logFile, executable)
 	log.Printf("starting %s", executable)
 	err := newExecutor.Start()
 	if err == nil {
-		theExecutor = newExecutor
+		ap.theExecutor = newExecutor
 		if curExecutor != nil {
 			log.Println("stopping old executor")
 			curExecutor.Stop()
@@ -63,22 +92,34 @@ func StartLatestAction() error {
 
 	// cannot start, removing the action
 	// and leaving the current executor running
+	if ap.removeFailedAction {
+		exeDir := fmt.Sprintf("./action/%d/", highestDir)
+		log.Printf("removing the failed action in %s", exeDir)
+		os.RemoveAll(exeDir)
+	}
 
-	exeDir := fmt.Sprintf("./action/%d/", highestDir)
-	log.Printf("removing the failed action in %s", exeDir)
-	os.RemoveAll(exeDir)
 	return err
 }
 
+func (ap *ActionProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	//fmt.Println(r.URL.Path)
+	switch r.URL.Path {
+	case "/init":
+		ap.initHandler(w, r)
+	case "/run":
+		ap.runHandler(w, r)
+	}
+}
+
 // Start creates a proxy to execute actions
-func Start() {
-	// handle initialization
-	http.HandleFunc("/init", initHandler)
-	// handle execution
-	http.HandleFunc("/run", runHandler)
-
-	// start
-	log.Println("Started!")
-	theServer.Addr = ":8080"
-	log.Fatal(theServer.ListenAndServe())
+func (ap *ActionProxy) Start(port int) {
+
+	// listen and start
+	listener, err := net.Listen("tcp", ":"+string(port))
+	if err != nil {
+		log.Println(err)
+		return
+	}
+	log.Printf("Started server in port %d", port)
+	http.Serve(listener, ap)
 }
diff --git a/openwhisk/actionProxy_test.go b/openwhisk/actionProxy_test.go
index b46ca3d..661c0b0 100644
--- a/openwhisk/actionProxy_test.go
+++ b/openwhisk/actionProxy_test.go
@@ -1,44 +1,65 @@
 package openwhisk
 
 import (
+	"fmt"
+	"io/ioutil"
 	"os"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
+func Example_startTestServer() {
+	ts, cur, log := startTestServer("")
+	res, _, _ := doPost(ts.URL+"/init", "{}")
+	fmt.Print(res)
+	res, _, _ = doPost(ts.URL+"/init", "XXX")
+	fmt.Print(res)
+	res, _, _ = doPost(ts.URL+"/run", "{}")
+	fmt.Print(res)
+	res, _, _ = doPost(ts.URL+"/run", "XXX")
+	fmt.Print(res)
+	stopTestServer(ts, cur, log)
+	// Output:
+	// {"ok":true}
+	// {"error":"Error unmarshaling request: invalid character 'X' looking for beginning of value"}
+	// {"error":"no action defined yet"}
+	// {"error":"Error unmarshaling request: invalid character 'X' looking for beginning of value"}
+}
+
 func TestStartLatestAction(t *testing.T) {
 
 	// cleanup
 	os.RemoveAll("./action")
-	theExecutor = nil
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action", "", log, true)
 
 	// start an action that terminate immediately
 	buf := []byte("#!/bin/sh\ntrue\n")
-	extractAction(&buf, true)
-	StartLatestAction()
-	assert.Nil(t, theExecutor)
+	ap.ExtractAction(&buf, "main")
+	ap.StartLatestAction("main")
+	assert.Nil(t, ap.theExecutor)
 
 	// start the action that emits 1
 	buf = []byte("#!/bin/sh\nwhile read a; do echo 1 >&3 ; done\n")
-	extractAction(&buf, true)
-	StartLatestAction()
-	theExecutor.io <- "x"
-	assert.Equal(t, <-theExecutor.io, "1")
+	ap.ExtractAction(&buf, "main")
+	ap.StartLatestAction("main")
+	ap.theExecutor.io <- "x"
+	assert.Equal(t, <-ap.theExecutor.io, "1")
 
 	// now start an action that terminate immediately
 	buf = []byte("#!/bin/sh\ntrue\n")
-	extractAction(&buf, true)
-	StartLatestAction()
-	theExecutor.io <- "y"
-	assert.Equal(t, <-theExecutor.io, "1")
+	ap.ExtractAction(&buf, "main")
+	ap.StartLatestAction("main")
+	ap.theExecutor.io <- "y"
+	assert.Equal(t, <-ap.theExecutor.io, "1")
 
 	// start the action that emits 2
 	buf = []byte("#!/bin/sh\nwhile read a; do echo 2 >&3 ; done\n")
-	extractAction(&buf, true)
-	StartLatestAction()
-	theExecutor.io <- "z"
-	assert.Equal(t, <-theExecutor.io, "2")
+	ap.ExtractAction(&buf, "main")
+	ap.StartLatestAction("main")
+	ap.theExecutor.io <- "z"
+	assert.Equal(t, <-ap.theExecutor.io, "2")
 	/**/
-	theExecutor.Stop()
+	ap.theExecutor.Stop()
 }
diff --git a/openwhisk/compiler.go b/openwhisk/compiler.go
new file mode 100644
index 0000000..0cec8d8
--- /dev/null
+++ b/openwhisk/compiler.go
@@ -0,0 +1,40 @@
+package openwhisk
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os/exec"
+
+	"github.com/h2non/filetype"
+)
+
+// check if the code is already compiled
+func isCompiled(file string) bool {
+	buf, err := ioutil.ReadFile(file)
+	if err != nil {
+		log.Println(err)
+		return false
+	}
+	kind, err := filetype.Match(buf)
+	if err != nil {
+		log.Println(err)
+		return false
+	}
+	if kind.Extension == "elf" {
+		return true
+	}
+	return false
+}
+
+// CompileAction will compile an anction in source format invoking a compiler
+func (ap *ActionProxy) CompileAction(file string, main string) error {
+	if ap.compiler == "" {
+		return fmt.Errorf("No compiler defined")
+	}
+	log.Printf("compiling: %s %s %s", ap.compiler, file, main)
+	cmd := exec.Command(ap.compiler, file, main)
+	out, err := cmd.CombinedOutput()
+	log.Print(string(out))
+	return err
+}
diff --git a/openwhisk/compiler_test.go b/openwhisk/compiler_test.go
new file mode 100644
index 0000000..42f226e
--- /dev/null
+++ b/openwhisk/compiler_test.go
@@ -0,0 +1,71 @@
+package openwhisk
+
+import (
+	"fmt"
+	"io/ioutil"
+)
+
+/* this test confuses gogradle
+func Example_compileAction_wrong() {
+	sys("_test/precompile.sh", "hello.sh", "0")
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action/0", "../core/gobuild", log, true)
+	fmt.Println(ap.CompileAction("_test/compile/0/exec", "exec"))
+	// Output:
+	// exit status 1
+}*/
+
+func Example_compileAction_singlefile_main() {
+	sys("_test/precompile.sh", "hello.src", "1")
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action/1", "../core/gobuild", log, true)
+	fmt.Println(ap.CompileAction("_test/compile/1/exec", "main"))
+	sys("_test/postcompile.sh", "_test/compile/1/exec")
+	// Output:
+	// <nil>
+	// _test/compile/1/exec: application/x-executable; charset=binary
+	// name=Mike
+	// {"message":"Hello, Mike!"}
+
+}
+
+func Example_compileAction_singlefile_hello() {
+	sys("_test/precompile.sh", "hello1.src", "2")
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action/2", "../core/gobuild", log, true)
+	fmt.Println(ap.CompileAction("_test/compile/2/exec", "hello"))
+	sys("_test/postcompile.sh", "_test/compile/2/exec")
+	// Output:
+	// <nil>
+	// _test/compile/2/exec: application/x-executable; charset=binary
+	// name=Mike
+	// {"hello":"Hello, Mike!"}
+}
+
+func Example_compileAction_multifile_main() {
+	sys("_test/precompile.sh", "action", "3")
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action/3", "../core/gobuild", log, true)
+	fmt.Println(ap.CompileAction("_test/compile/3/", "main"))
+	sys("_test/postcompile.sh", "_test/compile/3/main")
+	// Output:
+	// <nil>
+	// _test/compile/3/main: application/x-executable; charset=binary
+	// Main:
+	// Hello, Mike
+	// {"greetings":"Hello, Mike"}
+}
+
+func Example_compileAction_multifile_hello() {
+	sys("_test/precompile.sh", "action", "4")
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action/4", "../core/gobuild", log, true)
+	fmt.Println(ap.CompileAction("_test/compile/4/", "hello"))
+	sys("_test/postcompile.sh", "_test/compile/4/hello")
+	// Output:
+	// <nil>
+	// _test/compile/4/hello: application/x-executable; charset=binary
+	// Hello, Mike
+	// {"greetings":"Hello, Mike"}
+
+}
diff --git a/openwhisk/executor.go b/openwhisk/executor.go
index 6a48f4b..160aea3 100644
--- a/openwhisk/executor.go
+++ b/openwhisk/executor.go
@@ -42,11 +42,13 @@ type Executor struct {
 	_output *bufio.Scanner
 	_logout *bufio.Scanner
 	_logerr *bufio.Scanner
+	_logbuf *os.File
 }
 
-// NewExecutor creates a child subprocess using the provided command line.
+// NewExecutor creates a child subprocess using the provided command line,
+// writing the logs in the given file.
 // You can then start it getting a communication channel
-func NewExecutor(command string, args ...string) (proc *Executor) {
+func NewExecutor(logbuf *os.File, command string, args ...string) (proc *Executor) {
 	cmd := exec.Command(command, args...)
 
 	stdin, err := cmd.StdinPipe()
@@ -79,6 +81,7 @@ func NewExecutor(command string, args ...string) (proc *Executor) {
 		bufio.NewScanner(pipeOut),
 		bufio.NewScanner(stdout),
 		bufio.NewScanner(stderr),
+		logbuf,
 	}
 }
 
@@ -106,6 +109,19 @@ func (proc *Executor) run() {
 	log.Println("run: end")
 }
 
+func (proc *Executor) drain(ch chan string) {
+	runtime.Gosched()
+	for loop := true; loop; {
+		select {
+		case buf := <-ch:
+			fmt.Fprintln(proc._logbuf, buf)
+		case <-time.After(TIMEOUT):
+			loop = false
+		}
+	}
+	fmt.Fprintln(proc._logbuf, "XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX")
+}
+
 // manage copying stdout and stder in output
 // with log guards
 func (proc *Executor) logger() {
@@ -119,28 +135,9 @@ func (proc *Executor) logger() {
 	// wait for the signal
 	for <-proc.log {
 		// flush stdout
-		runtime.Gosched()
-		for loop := true; loop; {
-			select {
-			case buf := <-chOut:
-				fmt.Println(buf)
-			case <-time.After(TIMEOUT):
-				loop = false
-			}
-		}
-		fmt.Println("XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX")
-
+		proc.drain(chOut)
 		// flush stderr
-		runtime.Gosched()
-		for loop := true; loop; {
-			select {
-			case buf := <-chErr:
-				fmt.Println(buf)
-			case <-time.After(TIMEOUT):
-				loop = false
-			}
-		}
-		fmt.Println("XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX")
+		proc.drain(chErr)
 	}
 	log.Printf("logger: end")
 }
diff --git a/openwhisk/executor_test.go b/openwhisk/executor_test.go
index b904cb3..db8247b 100644
--- a/openwhisk/executor_test.go
+++ b/openwhisk/executor_test.go
@@ -18,23 +18,25 @@ package openwhisk
 
 import (
 	"fmt"
+	"io/ioutil"
 	"time"
 )
 
 func ExampleNewExecutor_failed() {
-	proc := NewExecutor("true")
+	log, _ := ioutil.TempFile("", "log")
+	proc := NewExecutor(log, "true")
 	err := proc.Start()
 	fmt.Println(err)
 	proc.Stop()
-	proc = NewExecutor("/bin/pwd")
+	proc = NewExecutor(log, "/bin/pwd")
 	err = proc.Start()
 	fmt.Println(err)
 	proc.Stop()
-	proc = NewExecutor("donotexist")
+	proc = NewExecutor(log, "donotexist")
 	err = proc.Start()
 	fmt.Println(err)
 	proc.Stop()
-	proc = NewExecutor("/etc/passwd")
+	proc = NewExecutor(log, "/etc/passwd")
 	err = proc.Start()
 	fmt.Println(err)
 	proc.Stop()
@@ -46,7 +48,8 @@ func ExampleNewExecutor_failed() {
 }
 
 func ExampleNewExecutor_bc() {
-	proc := NewExecutor("_test/bc.sh")
+	log, _ := ioutil.TempFile("", "log")
+	proc := NewExecutor(log, "_test/bc.sh")
 	err := proc.Start()
 	fmt.Println(err)
 	//proc.log <- true
@@ -63,6 +66,7 @@ func ExampleNewExecutor_bc() {
 	}
 	time.Sleep(100 * time.Millisecond)
 	proc.Stop()
+	dump(log)
 	// Output:
 	// <nil>
 	// 4
@@ -72,7 +76,8 @@ func ExampleNewExecutor_bc() {
 }
 
 func ExampleNewExecutor_hello() {
-	proc := NewExecutor("_test/hello.sh")
+	log, _ := ioutil.TempFile("", "log")
+	proc := NewExecutor(log, "_test/hello.sh")
 	err := proc.Start()
 	fmt.Println(err)
 	proc.io <- `{"name":"Mike"}`
@@ -83,6 +88,7 @@ func ExampleNewExecutor_hello() {
 	time.Sleep(100 * time.Millisecond)
 	_, ok := <-proc.io
 	fmt.Printf("io %v\n", ok)
+	dump(log)
 	// Unordered output:
 	// <nil>
 	// {"hello": "Mike"}
@@ -93,7 +99,8 @@ func ExampleNewExecutor_hello() {
 }
 
 func ExampleNewExecutor_term() {
-	proc := NewExecutor("_test/hello.sh")
+	log, _ := ioutil.TempFile("", "log")
+	proc := NewExecutor(log, "_test/hello.sh")
 	err := proc.Start()
 	fmt.Println(err)
 	proc.io <- `{"name":"*"}`
@@ -111,6 +118,7 @@ func ExampleNewExecutor_term() {
 	time.Sleep(100 * time.Millisecond)
 	_, ok := <-proc.io
 	fmt.Printf("io %v\n", ok)
+	dump(log)
 	// Unordered output:
 	// <nil>
 	// exit true
diff --git a/openwhisk/extractor.go b/openwhisk/extractor.go
index 17965bb..e496b07 100644
--- a/openwhisk/extractor.go
+++ b/openwhisk/extractor.go
@@ -100,32 +100,25 @@ func highestDir(dir string) int {
 	return max
 }
 
-var currentDir = highestDir("./action")
-
-// extractAction accept a byte array write it to a file
-func extractAction(buf *[]byte, isScript bool) error {
+// ExtractAction accept a byte array and write it to a file
+// it handles zip files extracting the content
+// it stores in a new directory under ./action/XXX whee x is incremented every time
+// it returns the file if a file or the directory if it was a zip file
+func (ap *ActionProxy) ExtractAction(buf *[]byte, main string) (string, error) {
 	if buf == nil || len(*buf) == 0 {
-		return fmt.Errorf("no file")
+		return "", fmt.Errorf("no file")
 	}
-	currentDir++
-	newDir := fmt.Sprintf("./action/%d", currentDir)
+	ap.currentDir++
+	newDir := fmt.Sprintf("%s/%d", ap.baseDir, ap.currentDir)
 	os.MkdirAll(newDir, 0755)
 	kind, err := filetype.Match(*buf)
 	if err != nil {
-		return err
+		return "", err
 	}
 	if kind.Extension == "zip" {
 		log.Println("Extract Action, assuming a zip")
-		return unzip(*buf, newDir)
-	}
-	if kind.Extension == "elf" || isScript {
-		if isScript {
-			log.Println("Extract Action, assuming a script")
-		} else {
-			log.Println("Extract Action, assuming a binary")
-		}
-		return ioutil.WriteFile(newDir+"/exec", *buf, 0755)
+		return newDir, unzip(*buf, newDir)
 	}
-	log.Println("No valid action found")
-	return fmt.Errorf("unknown filetype %s", kind)
+	file := newDir + "/" + main
+	return file, ioutil.WriteFile(file, *buf, 0755)
 }
diff --git a/openwhisk/extractor_test.go b/openwhisk/extractor_test.go
index ddc6522..469651b 100644
--- a/openwhisk/extractor_test.go
+++ b/openwhisk/extractor_test.go
@@ -29,8 +29,9 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func sys(cli string) {
-	cmd := exec.Command(cli)
+func sys(cli string, args ...string) {
+
+	cmd := exec.Command(cli, args...)
 	out, err := cmd.CombinedOutput()
 	if err != nil {
 		log.Print(err)
@@ -39,15 +40,6 @@ func sys(cli string) {
 	}
 }
 
-func TestExtractActionTest_exec(t *testing.T) {
-	sys("_test/build.sh")
-	// cleanup
-	assert.Nil(t, os.RemoveAll("./action"))
-	file, _ := ioutil.ReadFile("_test/exec")
-	extractAction(&file, false)
-	assert.Nil(t, exists("./action", "exec"))
-}
-
 func exists(dir, filename string) error {
 	path := fmt.Sprintf("%s/%d/%s", dir, highestDir(dir), filename)
 	_, err := os.Stat(path)
@@ -60,32 +52,51 @@ func detect(dir, filename string) string {
 	kind, _ := filetype.Match(file)
 	return kind.Extension
 }
+
+func TestExtractActionTest_exec(t *testing.T) {
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action/x1", "", log, true)
+	sys("_test/build.sh")
+	// cleanup
+	assert.Nil(t, os.RemoveAll("./action/x1"))
+	file, _ := ioutil.ReadFile("_test/exec")
+	ap.ExtractAction(&file, "exec")
+	assert.Nil(t, exists("./action/x1", "exec"))
+}
+
 func TestExtractActionTest_exe(t *testing.T) {
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action/x2", "", log, true)
 	sys("_test/build.sh")
 	// cleanup
-	assert.Nil(t, os.RemoveAll("./action"))
+	assert.Nil(t, os.RemoveAll("./action/x2"))
 	// match  exe
 	file, _ := ioutil.ReadFile("_test/exec")
-	extractAction(&file, false)
-	assert.Equal(t, detect("./action", "exec"), "elf")
+	ap.ExtractAction(&file, "exec")
+	assert.Equal(t, detect("./action/x2", "exec"), "elf")
 }
 
 func TestExtractActionTest_zip(t *testing.T) {
+	log, _ := ioutil.TempFile("", "log")
 	sys("_test/build.sh")
+	ap := NewActionProxy("./action/x3", "", log, true)
 	// cleanup
-	assert.Nil(t, os.RemoveAll("./action"))
+	assert.Nil(t, os.RemoveAll("./action/x3"))
 	// match  exe
 	file, _ := ioutil.ReadFile("_test/exec.zip")
-	extractAction(&file, false)
-	assert.Equal(t, detect("./action", "exec"), "elf")
-	assert.Nil(t, exists("./action", "etc"))
-	assert.Nil(t, exists("./action", "dir/etc"))
+	ap.ExtractAction(&file, "exec")
+	assert.Equal(t, detect("./action/x3", "exec"), "elf")
+	assert.Nil(t, exists("./action/x3", "etc"))
+	assert.Nil(t, exists("./action/x3", "dir/etc"))
 }
 
 func TestExtractAction_script(t *testing.T) {
+	log, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy("./action/x4", "", log, true)
 	buf := []byte("#!/bin/sh\necho ok")
-	assert.NotNil(t, extractAction(&buf, false))
-	assert.Nil(t, extractAction(&buf, true))
+	_, err := ap.ExtractAction(&buf, "exec")
+	//fmt.Print(err)
+	assert.Nil(t, err)
 }
 
 func TestHighestDir(t *testing.T) {
diff --git a/openwhisk/initHandler.go b/openwhisk/initHandler.go
index 59dce64..864b871 100644
--- a/openwhisk/initHandler.go
+++ b/openwhisk/initHandler.go
@@ -22,30 +22,38 @@ import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
+	"log"
 	"net/http"
 )
 
+type initBodyRequest struct {
+	Code   string `json:"code,omitempty"`
+	Binary bool   `json:"binary,omitempty"`
+	Main   string `json:"main,omitempty"`
+}
 type initRequest struct {
-	Value struct {
-		Code   string `json:",omitempty"`
-		Binary bool   `json:",omitempty"`
-	} `json:",omitempty"`
+	Value initBodyRequest `json:"value,omitempty"`
 }
 
 func sendOK(w http.ResponseWriter) {
 	// answer OK
 	w.Header().Set("Content-Type", "application/json")
-	w.Header().Set("Content-Length", "12")
-	w.Write([]byte("{\"ok\":true}\n"))
+	buf := []byte("{\"ok\":true}\n")
+	w.Header().Set("Content-Length", fmt.Sprintf("%d", len(buf)))
+	w.Write(buf)
 	if f, ok := w.(http.Flusher); ok {
 		f.Flush()
 	}
 }
 
-func initHandler(w http.ResponseWriter, r *http.Request) {
+func (ap *ActionProxy) initHandler(w http.ResponseWriter, r *http.Request) {
 
 	// read body of the request
 	// log.Println("init: reading")
+	if ap.compiler != "" {
+		log.Println("compiler: " + ap.compiler)
+	}
+
 	body, err := ioutil.ReadAll(r.Body)
 	defer r.Body.Close()
 	if err != nil {
@@ -53,7 +61,7 @@ func initHandler(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// decode request parameters
-	//log.Println("init: decoding")
+	//log.Printf("init: decoding %s\n", string(body))
 	var request initRequest
 	err = json.Unmarshal(body, &request)
 
@@ -62,32 +70,56 @@ func initHandler(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	// request with empty code - stop any executor but return ok
+	//log.Printf("request: %v\n", request)
 	if request.Value.Code == "" {
+		if ap.theExecutor != nil {
+			log.Printf("stop running action")
+			ap.theExecutor.Stop()
+			ap.theExecutor = nil
+		}
 		sendOK(w)
 		return
 	}
 
-	// check if it is a binary
+	main := request.Value.Main
+	if main == "" {
+		main = "main"
+	}
+
+	// extract code eventually decoding it
+	var buf []byte
 	if request.Value.Binary {
-		var decoded []byte
-		decoded, err = base64.StdEncoding.DecodeString(request.Value.Code)
+		log.Printf("binary")
+		buf, err = base64.StdEncoding.DecodeString(request.Value.Code)
 		if err != nil {
 			sendError(w, http.StatusBadRequest, "cannot decode the request: "+err.Error())
 			return
 		}
-		// extract the replacement, stopping and then starting the action
-		err = extractAction(&decoded, false)
 	} else {
-		buf := []byte(request.Value.Code)
-		err = extractAction(&buf, true)
+		log.Printf("plain text")
+		buf = []byte(request.Value.Code)
 	}
-	if err != nil {
+
+	// extract the action,
+	file, err := ap.ExtractAction(&buf, main)
+	if err != nil || file == "" {
 		sendError(w, http.StatusBadRequest, "invalid action: "+err.Error())
 		return
 	}
 
+	// compile it if a compiler is available
+	if ap.compiler != "" && !isCompiled(file) {
+		log.Printf("compiling: %s main: %s", file, main)
+		err = ap.CompileAction(file, main)
+		if err != nil {
+			sendError(w, http.StatusBadRequest, "cannot compile action: "+err.Error())
+			return
+		}
+	}
+
 	// stop and start
-	err = StartLatestAction()
+	err = ap.StartLatestAction(main)
 	if err != nil {
 		sendError(w, http.StatusBadRequest, "cannot start action: "+err.Error())
 		return
diff --git a/openwhisk/initHandler_test.go b/openwhisk/initHandler_test.go
new file mode 100644
index 0000000..16f88cb
--- /dev/null
+++ b/openwhisk/initHandler_test.go
@@ -0,0 +1,220 @@
+package openwhisk
+
+import "path/filepath"
+
+func Example_badinit_nocompiler() {
+	ts, cur, log := startTestServer("")
+	sys("_test/build.sh")
+	doRun(ts, "")
+	doInit(ts, "{}")
+	//sys("ls", "_test/exec")
+	doInit(ts, initBinary("_test/exec", ""))      // empty
+	doInit(ts, initBinary("_test/hi", ""))        // say hi
+	doInit(ts, initBinary("_test/hello.src", "")) // source not excutable
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 200 {"ok":true}
+	// 400 {"error":"cannot start action: command exited"}
+	// 400 {"error":"cannot start action: command exited"}
+	// 400 {"error":"cannot start action: command exited"}
+	// 400 {"error":"no action defined yet"}
+
+}
+
+func Example_bininit_nocompiler() {
+	ts, cur, log := startTestServer("")
+	sys("_test/build.sh")
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello_message", ""))
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello_greeting", ""))
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 200 {"ok":true}
+	// 200 {"message":"Hello, Mike!"}
+	// 200 {"ok":true}
+	// 200 {"greetings":"Hello, Mike"}
+	// name=Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// Hello, Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+func Example_zipinit_nocompiler() {
+	ts, cur, log := startTestServer("")
+	sys("_test/build.sh")
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello_greeting.zip", ""))
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello_message.zip", ""))
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 200 {"ok":true}
+	// 200 {"greetings":"Hello, Mike"}
+	// 200 {"ok":true}
+	// 200 {"message":"Hello, Mike!"}
+	// Hello, Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// name=Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+func Example_shell_nocompiler() {
+	ts, cur, log := startTestServer("")
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello.sh", ""))
+	doRun(ts, "")
+	doRun(ts, `{"name":"*"}`)
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 200 {"ok":true}
+	// 200 {"hello": "Mike"}
+	// 400 {"error":"command exited"}
+	// 400 {"error":"no action defined yet"}
+	// msg=hello Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// Goodbye!
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+func Example_main_nocompiler() {
+	ts, cur, log := startTestServer("")
+	sys("_test/build.sh")
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello_message", "message"))
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello_greeting", "greeting"))
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 200 {"ok":true}
+	// 200 {"message":"Hello, Mike!"}
+	// 200 {"ok":true}
+	// 200 {"greetings":"Hello, Mike"}
+	// name=Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// Hello, Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+func Example_main_zipinit_nocompiler() {
+	ts, cur, log := startTestServer("")
+	sys("_test/build.sh")
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello_greeting.zip", "greeting"))
+	doInit(ts, initBinary("_test/hello_greeting1.zip", "greeting"))
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/hello_message.zip", "message"))
+	doInit(ts, initBinary("_test/hello_message1.zip", "message"))
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 400 {"error":"cannot start action: command exited"}
+	// 200 {"ok":true}
+	// 200 {"greetings":"Hello, Mike"}
+	// 400 {"error":"cannot start action: command exited"}
+	// 200 {"ok":true}
+	// 200 {"message":"Hello, Mike!"}
+	// Hello, Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// name=Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+func Example_compile_simple() {
+	comp, _ := filepath.Abs("../core/gobuild")
+	ts, cur, log := startTestServer(comp)
+	sys("_test/build.sh")
+	doRun(ts, "")
+	doInit(ts, initCode("_test/hello.src", ""))
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 200 {"ok":true}
+	// 200 {"message":"Hello, Mike!"}
+	// name=Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+func Example_compile_withMain() {
+	comp, _ := filepath.Abs("../core/gobuild")
+	ts, cur, log := startTestServer(comp)
+	sys("_test/build.sh")
+	doRun(ts, "")
+	doInit(ts, initCode("_test/hello1.src", ""))
+	doInit(ts, initCode("_test/hello1.src", "hello"))
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 400 {"error":"cannot compile action: exit status 2"}
+	// 200 {"ok":true}
+	// 200 {"hello":"Hello, Mike!"}
+	// name=Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+func Example_compile_withZipSrc() {
+	sys("_test/zips.sh")
+	comp, _ := filepath.Abs("../core/gobuild")
+	ts, cur, log := startTestServer(comp)
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/action.zip", ""))
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/action.zip", "hello"))
+	doRun(ts, "")
+	stopTestServer(ts, cur, log)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 200 {"ok":true}
+	// 200 {"greetings":"Hello, Mike"}
+	// 200 {"ok":true}
+	// 200 {"greetings":"Hello, Mike"}
+	// Main:
+	// Hello, Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// Hello, Mike
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+
+/*
+func Example_compile_withZipSrcDefault() {
+	sys("_test/zips.sh")
+	comp, _ := filepath.Abs("../core/gobuild")
+	ts, cur := startTestServer(comp)
+	doRun(ts, "")
+	doInit(ts, initBinary("_test/action.zip", ""))
+	doRun(ts, "")
+	stopTestServer(ts, cur)
+	// Output:
+	// 400 {"error":"no action defined yet"}
+	// 200 {"ok":true}
+	// name=Mike
+	// 200 {"hello":"Hello, Mike!"}
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+	// XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
+}
+/**/
diff --git a/openwhisk/runHandler.go b/openwhisk/runHandler.go
index 027a9d8..4b1c5a6 100644
--- a/openwhisk/runHandler.go
+++ b/openwhisk/runHandler.go
@@ -49,7 +49,7 @@ func sendError(w http.ResponseWriter, code int, cause string) {
 	w.Write([]byte("\n"))
 }
 
-func runHandler(w http.ResponseWriter, r *http.Request) {
+func (ap *ActionProxy) runHandler(w http.ResponseWriter, r *http.Request) {
 
 	// parse the request
 	params := Params{}
@@ -68,32 +68,32 @@ func runHandler(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// check if you have an action
-	if theExecutor == nil {
+	if ap.theExecutor == nil {
 		sendError(w, http.StatusBadRequest, fmt.Sprintf("no action defined yet"))
 		return
 	}
 
 	// execute the action
 	// and check for early termination
-	theExecutor.io <- string(params.Value)
+	ap.theExecutor.io <- string(params.Value)
 	var response string
 	var exited bool
 	select {
-	case response = <-theExecutor.io:
+	case response = <-ap.theExecutor.io:
 		exited = false
-	case err = <-theExecutor.exit:
+	case err = <-ap.theExecutor.exit:
 		exited = true
 	}
 
 	// check for early termination
 	if exited {
-		theExecutor = nil
+		ap.theExecutor = nil
 		sendError(w, http.StatusBadRequest, fmt.Sprintf("command exited"))
 		return
 	}
 
 	// flush the logs sending the activation message at the end
-	theExecutor.log <- true
+	ap.theExecutor.log <- true
 
 	// check response
 	if response == "" {
diff --git a/openwhisk/util_test.go b/openwhisk/util_test.go
new file mode 100644
index 0000000..51ce8e8
--- /dev/null
+++ b/openwhisk/util_test.go
@@ -0,0 +1,119 @@
+package openwhisk
+
+import (
+	"bytes"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"path/filepath"
+	"runtime"
+	"time"
+)
+
+func startTestServer(compiler string) (*httptest.Server, string, *os.File) {
+	// temporary workdir
+	cur, _ := os.Getwd()
+	dir, _ := ioutil.TempDir("", "action")
+	file, _ := filepath.Abs("_test")
+	os.Symlink(file, dir+"/_test")
+	os.Chdir(dir)
+	log.Printf(dir)
+	// setup the server
+	buf, _ := ioutil.TempFile("", "log")
+	ap := NewActionProxy(dir, compiler, buf, false)
+	ts := httptest.NewServer(ap)
+	log.Printf(ts.URL)
+	doPost(ts.URL+"/init", `{value: {code: ""}}`)
+	return ts, cur, buf
+}
+
+func stopTestServer(ts *httptest.Server, cur string, buf *os.File) {
+	runtime.Gosched()
+	time.Sleep(1 * time.Second)
+	os.Chdir(cur)
+	ts.Close()
+	dump(buf)
+}
+
+func doPost(url string, message string) (string, int, error) {
+	buf := bytes.NewBufferString(message)
+	res, err := http.Post(url, "application/json", buf)
+	if err != nil {
+		return "", -1, err
+	}
+	defer res.Body.Close()
+	resp, err := ioutil.ReadAll(res.Body)
+	if err != nil {
+		return "", -1, err
+	}
+	return string(resp), res.StatusCode, nil
+}
+
+func doRun(ts *httptest.Server, message string) {
+	if message == "" {
+		message = `{"name":"Mike"}`
+	}
+	resp, status, err := doPost(ts.URL+"/run", `{ "value": `+message+`}`)
+	if err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Printf("%d %s", status, resp)
+	}
+}
+
+func doInit(ts *httptest.Server, message string) {
+	resp, status, err := doPost(ts.URL+"/init", message)
+	if err != nil {
+		fmt.Println(err)
+	} else {
+		fmt.Printf("%d %s", status, resp)
+	}
+}
+
+func initCode(file string, main string) string {
+	dat, _ := ioutil.ReadFile(file)
+	body := initBodyRequest{Code: string(dat)}
+	if main != "" {
+		body.Main = main
+	}
+	j, _ := json.Marshal(initRequest{Value: body})
+	return string(j)
+}
+
+func initBinary(file string, main string) string {
+	dat, _ := ioutil.ReadFile(file)
+	enc := base64.StdEncoding.EncodeToString(dat)
+	body := initBodyRequest{Binary: true, Code: enc}
+	if main != "" {
+		body.Main = main
+	}
+	j, _ := json.Marshal(initRequest{Value: body})
+	return string(j)
+}
+
+func Example_json_init() {
+	fmt.Println(initCode("", ""))
+	fmt.Println(initCode("_test/etc", ""))
+	fmt.Println(initCode("_test/etc", "world"))
+	fmt.Println(initBinary("_test/etc", ""))
+	fmt.Println(initBinary("_test/etc", "hello"))
+	// Output:
+	// {"value":{}}
+	// {"value":{"code":"1\n"}}
+	// {"value":{"code":"1\n","main":"world"}}
+	// {"value":{"code":"MQo=","binary":true}}
+	// {"value":{"code":"MQo=","binary":true,"main":"hello"}}
+}
+
+func dump(file *os.File) {
+	//file.Read()
+	buf, _ := ioutil.ReadFile(file.Name())
+	fmt.Print(string(buf))
+	//fmt.Print(file.ReadAll())
+	os.Remove(file.Name())
+}
diff --git a/test/.gitignore b/test/.gitignore
deleted file mode 100644
index 4499418..0000000
--- a/test/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-action/
-*.json
-*.err
-
diff --git a/test/README.md b/test/README.md
deleted file mode 100644
index 3a601c9..0000000
--- a/test/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Test
-
-How to run tests:
-
-- ensure you are running them under linux !
-- install `cram`: `sudo pip install cram`
-- start the tester: `cd test ; ./start.sh`
-- in another terminal `cd test ; cram test.t`
-
diff --git a/test/bin/.gitignore b/test/bin/.gitignore
deleted file mode 100644
index b374f99..0000000
--- a/test/bin/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-hello_*
-empty
-hi
diff --git a/test/bin/build.sh b/test/bin/build.sh
deleted file mode 100755
index 4ed5b0a..0000000
--- a/test/bin/build.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/sh
-FILE=${1:?go file}
-OUT=$(basename $FILE)
-BIN=${OUT%%.go}
-ZIP=${BIN}.zip
-go build -i -o bin/$BIN $FILE
-GOOS=linux GOARCH=amd64 go build -o exec $FILE
-zip zip/$ZIP exec
-rm exec
-echo "built bin/$BIN zip/$ZIP"
diff --git a/test/bin/init.sh b/test/bin/init.sh
deleted file mode 100755
index e700bb4..0000000
--- a/test/bin/init.sh
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/bin/bash
-FILE=${1:?file}
-JSON=${2:-/tmp/json$$}
-if file -i $FILE | grep text/ >/dev/null
-then echo '{"value":{"main":"main","code":'$(cat $FILE | jq -R -s .)'}}' >/tmp/json$$
-else echo '{"value":{"binary":true,"code":"'$(base64 -w 0 $FILE)'"}}' >/tmp/json$$
-fi
-#cat $JSON | jq .
-curl -H "Content-Type: application/json" -XPOST -w "%{http_code}\n" http://localhost:${PORT:-8080}/init -d @$JSON 2>/dev/null
-rm /tmp/json$$ 2>/dev/null
diff --git a/test/bin/post.sh b/test/bin/post.sh
deleted file mode 100755
index 6255f39..0000000
--- a/test/bin/post.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-FILE=${1:?file}
-curl -H "Content-Type: application/json" -XPOST -w "%{http_code}\n" http://localhost:${PORT:-8080}/init -d @$FILE 2>/dev/null
diff --git a/test/bin/run.sh b/test/bin/run.sh
deleted file mode 100755
index 0df821e..0000000
--- a/test/bin/run.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-DEFAULT='{"name": "Mike"}'
-JSON=${1:-$DEFAULT}
-DATA='{"value":'$JSON'}'
-curl -H "Content-Type: application/json" -w "%{http_code}\n" -XPOST http://localhost:${PORT:-8080}/run -d "$DATA" 2>/dev/null
-
diff --git a/test/etc/hello.js b/test/etc/hello.js
deleted file mode 100644
index a1bf768..0000000
--- a/test/etc/hello.js
+++ /dev/null
@@ -1,5 +0,0 @@
-function main(args) {
-  return {
-     "result": "Hello, "+args.name
-  }
-}
diff --git a/test/etc/hello.sh b/test/etc/hello.sh
deleted file mode 100755
index 9b7f318..0000000
--- a/test/etc/hello.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash
-while read line
-do
-   name="$(echo $line | jq -r .name)"
-   if test "$name" == ""
-   then exit
-   fi
-   echo "name=$name" 
-   hello="Hello, $name"
-   echo '{"hello":"'$hello'"}' >&3
-done
-
diff --git a/test/start.sh b/test/start.sh
deleted file mode 100755
index 5310d7f..0000000
--- a/test/start.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-cd "$(dirname $0)"
-bin/build.sh src/hello_greeting.go 
-bin/build.sh src/hello_message.go 
-bin/build.sh src/empty.go
-bin/build.sh src/hi.go
-rm -Rvf action
-go run ../main/proxy.go -debug
diff --git a/test/test.t b/test/test.t
deleted file mode 100644
index a85b86c..0000000
--- a/test/test.t
+++ /dev/null
@@ -1,93 +0,0 @@
-  $ export T=$TESTDIR
-
-  $ $T/bin/run.sh 
-  {"error":"no action defined yet"}
-  400
-
-  $ $T/bin/post.sh $T/etc/empty.json
-  {"ok":true}
-  200
-
-  $ $T/bin/init.sh $T/test.t
-  {"error":"cannot start action: command exited"}
-  400
-
-  $ $T/bin/init.sh $T/bin/empty
-  {"error":"cannot start action: command exited"}
-  400
-
-  $ $T/bin/init.sh $T/bin/hi
-  {"error":"cannot start action: command exited"}
-  400
-
-  $ $T/bin/run.sh 
-  {"error":"no action defined yet"}
-  400
-
-  $ $T/bin/init.sh $T/bin/hello_message
-  {"ok":true}
-  200
-
-  $ $T/bin/run.sh 
-  {"message":"Hello, Mike!"}
-  200
-
-  $ $T/bin/init.sh $T/bin/hello_greeting
-  {"ok":true}
-  200
-
-  $ $T/bin/run.sh 
-  {"greetings":"Hello, Mike"}
-  200
-
-  $ $T/bin/init.sh $T/zip/hello_message.zip
-  {"ok":true}
-  200
-
-  $ $T/bin/run.sh 
-  {"message":"Hello, Mike!"}
-  200
-
-  $ $T/bin/init.sh $T/zip/hello_greeting.zip
-  {"ok":true}
-  200
-
-  $ $T/bin/run.sh 
-  {"greetings":"Hello, Mike"}
-  200
-
-  $ $T/bin/init.sh $T/test.t
-  {"error":"cannot start action: command exited"}
-  400
-
-  $ $T/bin/run.sh 
-  {"greetings":"Hello, Mike"}
-  200
-
-  $ $T/bin/init.sh $T/bin/empty
-  {"error":"cannot start action: command exited"}
-  400
-
-  $ $T/bin/run.sh 
-  {"greetings":"Hello, Mike"}
-  200
-
-  $ $T/bin/init.sh $T/bin/hi
-  {"error":"cannot start action: command exited"}
-  400
-
-  $ $T/bin/init.sh $T/etc/hello.sh
-  {"ok":true}
-  200
-
-  $ $T/bin/run.sh
-  {"hello":"Hello, Mike"}
-  200
-
-  $ $T/bin/run.sh '{"name": ""}'
-  {"error":"command exited"}
-  400
-
-  $ $T/bin/run.sh
-  {"error":"no action defined yet"}
-  400
\ No newline at end of file
diff --git a/test/zip/.gitignore b/test/zip/.gitignore
deleted file mode 100644
index c4c4ffc..0000000
--- a/test/zip/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.zip


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services