You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by dg...@apache.org on 2018/07/09 14:19:50 UTC

[incubator-openwhisk] branch master updated: Remove runtime tests, simplify Rest vs CLI test hierarchy, add sniff test for all runtimes (#3840)

This is an automated email from the ASF dual-hosted git repository.

dgrove pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git


The following commit(s) were added to refs/heads/master by this push:
     new 5b13c4c  Remove runtime tests, simplify Rest vs CLI test hierarchy, add sniff test for all runtimes (#3840)
5b13c4c is described below

commit 5b13c4c90e4f8d59156c862c2ffe57ef027e5aee
Author: rodric rabbah <ro...@gmail.com>
AuthorDate: Mon Jul 9 10:19:46 2018 -0400

    Remove runtime tests, simplify Rest vs CLI test hierarchy, add sniff test for all runtimes (#3840)
    
    Additional cleanup of tests:
        removes WskRest* where possible and makes the base test suite the Rest tests
        removes runtime tests should should be moved to their respective runtimes
        runs the unicode tests over all the non-deprecated kinds per the runtime manifest
---
 docs/actions-new.md                                |   2 +
 tests/dat/actions/issue-1562.js                    |   9 -
 .../{unicode.jar => unicode.tests/java.bin}        | Bin
 .../{unicode.js => unicode.tests/nodejs:6.txt}     |   0
 .../{unicode.js => unicode.tests/nodejs:8.txt}     |   0
 tests/dat/actions/unicode.tests/php:7.1.txt        |   9 +
 tests/dat/actions/unicode.tests/php:7.2.txt        |   9 +
 .../{unicode2.py => unicode.tests/python.txt}      |   0
 .../{unicode2.py => unicode.tests/python:2.txt}    |   0
 .../{unicode3.py => unicode.tests/python:3.txt}    |   0
 .../src/java}/unicode/build.gradle                 |   0
 .../src/java}/unicode/src/main/java/Unicode.java   |   0
 .../swift:3.1.1.txt}                               |   0
 .../{unicode.swift => unicode.tests/swift:4.1.txt} |   0
 .../actionContainers/BasicActionRunnerTests.scala  | 389 +++++++++++--------
 .../test/scala/common/rest/WskRestOperations.scala | 423 ++++++++-------------
 .../test/scala/system/basic/WskActionTests.scala   |  26 +-
 .../scala/system/basic/WskActivationTests.scala    |  12 +-
 .../scala/system/basic/WskBasicJavaTests.scala     | 104 -----
 .../scala/system/basic/WskBasicNode6Tests.scala    |  94 -----
 .../scala/system/basic/WskBasicNode8Tests.scala    |  26 --
 .../system/basic/WskBasicNodeDefaultTests.scala    | 125 ------
 .../scala/system/basic/WskBasicPythonTests.scala   | 142 -------
 .../scala/system/basic/WskBasicSwift3Tests.scala   |  65 ----
 .../scala/system/basic/WskConductorTests.scala     |  19 +-
 .../test/scala/system/basic/WskPackageTests.scala  |  14 +-
 .../scala/system/basic/WskRestActionTests.scala    |  38 --
 .../system/basic/WskRestActivationTests.scala      |  29 --
 .../scala/system/basic/WskRestBasicJavaTests.scala |  28 --
 .../system/basic/WskRestBasicNode6Tests.scala      |  29 --
 .../system/basic/WskRestBasicNode8Tests.scala      |  29 --
 .../basic/WskRestBasicNodeDefaultTests.scala       |  29 --
 .../system/basic/WskRestBasicPythonTests.scala     |  29 --
 .../system/basic/WskRestBasicSwift311Tests.scala   |  30 --
 .../system/basic/WskRestBasicSwift41Tests.scala    |  31 --
 .../scala/system/basic/WskRestBasicTests.scala     |  11 +-
 .../scala/system/basic/WskRestConductorTests.scala |  29 --
 .../scala/system/basic/WskRestPackageTests.scala   |  29 --
 .../scala/system/basic/WskRestSequenceTests.scala  |  35 --
 .../system/basic/WskRestUnicodeJavaTests.scala     |  35 --
 .../system/basic/WskRestUnicodeNode6Tests.scala    |  35 --
 .../system/basic/WskRestUnicodeNode8Tests.scala    |  35 --
 .../system/basic/WskRestUnicodePython2Tests.scala  |  32 --
 .../system/basic/WskRestUnicodePython3Tests.scala  |  32 --
 .../system/basic/WskRestUnicodeSwift311Tests.scala |  32 --
 .../system/basic/WskRestUnicodeSwift41Tests.scala  |  32 --
 .../test/scala/system/basic/WskSequenceTests.scala |  23 +-
 .../test/scala/system/basic/WskUnicodeTests.scala  |  97 +++--
 .../core/cli/test/WskRestBasicUsageTests.scala     |  75 ----
 .../core/cli/test/WskRestWebActionsTests.scala     |  28 --
 .../whisk/core/cli/test/WskWebActionsTests.scala   |  14 +-
 .../core/controller/test/ActionsApiTests.scala     |  19 +-
 .../core/controller/test/SequenceApiTests.scala    |  13 +
 53 files changed, 562 insertions(+), 1784 deletions(-)

diff --git a/docs/actions-new.md b/docs/actions-new.md
index 7ae6b16..c7945d1 100644
--- a/docs/actions-new.md
+++ b/docs/actions-new.md
@@ -236,8 +236,10 @@ for validating a new runtime. The harness will performing the following:
 * Test the proxy can properly handle functions with Unicode characters.
 * Test the proxy properly constructs the activation context.
 * Test the proxy can handle large payloads (more than 1MB).
+* Test the proxy can handle an entry point other than "main".
 * Test the proxy does not permit re-initialization.
 * Test the error handling for an action returning an invalid response.
+* Test the proxy when initialized with no content.
 
 The canonical test suite should be extended by the new runtime tests. Additional
 tests will be required depending on the feature set provided by the runtime.
diff --git a/tests/dat/actions/issue-1562.js b/tests/dat/actions/issue-1562.js
deleted file mode 100644
index 4467adc..0000000
--- a/tests/dat/actions/issue-1562.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one or more contributor
-// license agreements; and to You under the Apache License, Version 2.0.
-
-// We expect this action to always result in errored activations.
-function main(args) {
-    return new Promise((resolve, reject) => {
-        reject();
-    });
-}
diff --git a/tests/dat/actions/unicode.jar b/tests/dat/actions/unicode.tests/java.bin
similarity index 100%
rename from tests/dat/actions/unicode.jar
rename to tests/dat/actions/unicode.tests/java.bin
diff --git a/tests/dat/actions/unicode.js b/tests/dat/actions/unicode.tests/nodejs:6.txt
similarity index 100%
copy from tests/dat/actions/unicode.js
copy to tests/dat/actions/unicode.tests/nodejs:6.txt
diff --git a/tests/dat/actions/unicode.js b/tests/dat/actions/unicode.tests/nodejs:8.txt
similarity index 100%
rename from tests/dat/actions/unicode.js
rename to tests/dat/actions/unicode.tests/nodejs:8.txt
diff --git a/tests/dat/actions/unicode.tests/php:7.1.txt b/tests/dat/actions/unicode.tests/php:7.1.txt
new file mode 100644
index 0000000..bdd88cb
--- /dev/null
+++ b/tests/dat/actions/unicode.tests/php:7.1.txt
@@ -0,0 +1,9 @@
+<?php
+// Licensed to the Apache Software Foundation (ASF) under one or more contributor
+// license agreements; and to You under the Apache License, Version 2.0.
+
+function main(array $args) : array {
+    $str = $args['delimiter'] . " ☃ " . $args['delimiter'];
+    echo $str . "\n";
+    return  ["winter" => $str];
+}
diff --git a/tests/dat/actions/unicode.tests/php:7.2.txt b/tests/dat/actions/unicode.tests/php:7.2.txt
new file mode 100644
index 0000000..bdd88cb
--- /dev/null
+++ b/tests/dat/actions/unicode.tests/php:7.2.txt
@@ -0,0 +1,9 @@
+<?php
+// Licensed to the Apache Software Foundation (ASF) under one or more contributor
+// license agreements; and to You under the Apache License, Version 2.0.
+
+function main(array $args) : array {
+    $str = $args['delimiter'] . " ☃ " . $args['delimiter'];
+    echo $str . "\n";
+    return  ["winter" => $str];
+}
diff --git a/tests/dat/actions/unicode2.py b/tests/dat/actions/unicode.tests/python.txt
similarity index 100%
copy from tests/dat/actions/unicode2.py
copy to tests/dat/actions/unicode.tests/python.txt
diff --git a/tests/dat/actions/unicode2.py b/tests/dat/actions/unicode.tests/python:2.txt
similarity index 100%
rename from tests/dat/actions/unicode2.py
rename to tests/dat/actions/unicode.tests/python:2.txt
diff --git a/tests/dat/actions/unicode3.py b/tests/dat/actions/unicode.tests/python:3.txt
similarity index 100%
rename from tests/dat/actions/unicode3.py
rename to tests/dat/actions/unicode.tests/python:3.txt
diff --git a/tests/dat/actions/unicode/build.gradle b/tests/dat/actions/unicode.tests/src/java/unicode/build.gradle
similarity index 100%
rename from tests/dat/actions/unicode/build.gradle
rename to tests/dat/actions/unicode.tests/src/java/unicode/build.gradle
diff --git a/tests/dat/actions/unicode/src/main/java/Unicode.java b/tests/dat/actions/unicode.tests/src/java/unicode/src/main/java/Unicode.java
similarity index 100%
rename from tests/dat/actions/unicode/src/main/java/Unicode.java
rename to tests/dat/actions/unicode.tests/src/java/unicode/src/main/java/Unicode.java
diff --git a/tests/dat/actions/unicode.swift b/tests/dat/actions/unicode.tests/swift:3.1.1.txt
similarity index 100%
copy from tests/dat/actions/unicode.swift
copy to tests/dat/actions/unicode.tests/swift:3.1.1.txt
diff --git a/tests/dat/actions/unicode.swift b/tests/dat/actions/unicode.tests/swift:4.1.txt
similarity index 100%
rename from tests/dat/actions/unicode.swift
rename to tests/dat/actions/unicode.tests/swift:4.1.txt
diff --git a/tests/src/test/scala/actionContainers/BasicActionRunnerTests.scala b/tests/src/test/scala/actionContainers/BasicActionRunnerTests.scala
index 3bae049..a8eb90c 100644
--- a/tests/src/test/scala/actionContainers/BasicActionRunnerTests.scala
+++ b/tests/src/test/scala/actionContainers/BasicActionRunnerTests.scala
@@ -24,204 +24,287 @@ import spray.json._
 
 @RunWith(classOf[JUnitRunner])
 trait BasicActionRunnerTests extends ActionProxyContainerTestUtils {
+
+  /**
+   * Must be defined by the runtime test suites. A typical implementation looks like this:
+   *      withContainer("container-image-name", env)(code)
+   * See [[ActionContainer.withContainer]] for details.
+   *
+   * @param env the environment to pass to the container
+   * @param code the code to initialize with
+   */
   def withActionContainer(env: Map[String, String] = Map.empty)(code: ActionContainer => Unit): (String, String)
 
   /**
    * Runs tests for actions which receive an empty initializer (no source or exec).
+   *
+   * @param stub true if the proxy provides a stub
    */
-  def testNoSourceOrExec() = {
-    it should "report an error when no code is present for initialization" in {
-      val (out, err) = withActionContainer() { c =>
-        val (initCode, out) = c.init(initPayload(""))
-        initCode should not be (200)
-        out should be(Some(JsObject("error" -> JsString("The action did not initialize."))))
-      }
-    }
-  }
+  def testNoSourceOrExec: TestConfig
 
   /**
    * Runs tests for actions which do not return a dictionary and confirms expected error messages.
-   * @param codeNotReturningJson code to execute, should not return a JSON object
+   *
+   * @param code code to execute, should not return a JSON object
+   * @param main the main function
    * @param checkResultInLogs should be true iff the result of the action is expected to appear in stdout or stderr
    */
-  def testNotReturningJson(codeNotReturningJson: String, checkResultInLogs: Boolean = true) = {
-    it should "run and report an error for script not returning a json object" in {
-      val (out, err) = withActionContainer() { c =>
-        val (initCode, _) = c.init(initPayload(codeNotReturningJson))
-        initCode should be(200)
-        val (runCode, out) = c.run(JsObject.empty)
-        runCode should be(502)
-        out should be(Some(JsObject("error" -> JsString("The action did not return a dictionary."))))
-      }
+  def testNotReturningJson: TestConfig
 
-      checkStreams(out, err, {
-        case (o, e) =>
-          if (checkResultInLogs) {
-            (o + e) should include("not a json object")
-          } else {
-            o shouldBe empty
-            e shouldBe empty
-          }
-      })
-    }
-  }
+  /**
+   * Tests that an action will not allow more than one initialization and confirms expected error messages.
+   *
+   * @param code the code to execute, the identity/echo function is sufficient.
+   * @param main the main function
+   */
+  def testInitCannotBeCalledMoreThanOnce: TestConfig
+
+  /**
+   * Tests the echo action for an entry point other than "main".
+   *
+   * @param code the code to execute, must be the identity/echo function.
+   * @param main the main function to run, must not be "main"
+   */
+  def testEntryPointOtherThanMain: TestConfig
 
   /**
    * Tests the echo action for different input parameters.
    * The test actions must also print hello [stdout, stderr] to the respective streams.
-   * @param stdCodeSamples a sequence of tuples, where each tuple provide a test name in the first component
-   *                       and the identity/echo function in the second component.
+   *
+   * @param code the code to execute, must be the identity/echo function.
+   * @param main the main function
    */
-  def testEcho(stdCodeSamples: Seq[(String, String)]) = {
-    stdCodeSamples.foreach { s =>
-      it should s"run a ${s._1} script" in {
-        val argss = List(
-          JsObject("string" -> JsString("hello")),
-          JsObject("string" -> JsString("❄ ☃ ❄")),
-          JsObject("numbers" -> JsArray(JsNumber(42), JsNumber(1))),
-          // JsObject("boolean" -> JsBoolean(true)), // fails with swift3 returning boolean: 1
-          JsObject("object" -> JsObject("a" -> JsString("A"))))
-
-        val (out, err) = withActionContainer() { c =>
-          val (initCode, _) = c.init(initPayload(s._2))
-          initCode should be(200)
-
-          for (args <- argss) {
-            val (runCode, out) = c.run(runPayload(args))
-            runCode should be(200)
-            out should be(Some(args))
-          }
-        }
-
-        checkStreams(out, err, {
-          case (o, e) =>
-            o should include("hello stdout")
-            e should include("hello stderr")
-        }, argss.length)
-      }
-    }
-  }
+  def testEcho: TestConfig
 
   /**
    * Tests a unicode action. The action must properly handle unicode characters in the executable,
    * receive a unicode character, and construct a response with a unicode character. It must also
    * emit unicode characters correctly to stdout.
-   * @param stdUnicodeSamples a sequence of tuples, where each tuple provide a test name in the first component
-   *                          a function in the second component: { delimiter } => { winter: "❄ " + delimiter + " ❄"}
+   *
+   * @param code a function { delimiter } => { winter: "❄ " + delimiter + " ❄"}
+   * @param main the main function
    */
-  def testUnicode(stdUnicodeSamples: Seq[(String, String)]) = {
-    stdUnicodeSamples.foreach { s =>
-      it should s"run a ${s._1} action and handle unicode in source, input params, logs, and result" in {
-        val (out, err) = withActionContainer() { c =>
-          val (initCode, _) = c.init(initPayload(s._2))
-          initCode should be(200)
-
-          val (runCode, runRes) = c.run(runPayload(JsObject("delimiter" -> JsString("❄"))))
-          runRes.get.fields.get("winter") shouldBe Some(JsString("❄ ☃ ❄"))
-        }
-
-        checkStreams(out, err, {
-          case (o, _) =>
-            o.toLowerCase should include("❄ ☃ ❄")
-        })
-      }
-    }
-  }
+  def testUnicode: TestConfig
 
   /**
    * Tests the action constructs the activation context correctly.
    *
-   * @param stdEnvSamples a sequence of tuples, where each tuple provide a test name in the first component
-   *                      and a function returning the activation context consisting of the following dictionary
-   *                      {
-   *                        "api_host": process.env.__OW_API_HOST,
-   *                        "api_key": process.env__OW_API_KEY,
-   *                        "namespace": process.env.__OW_NAMESPACE,
-   *                        "action_name": process.env.__OW_ACTION_NAME,
-   *                        "activation_id": process.env.__OW_ACTIVATION_ID,
-   *                        "deadline": process.env.__OW_DEADLINE
-   *                      }
+   * @param code a function returning the activation context consisting of the following dictionary
+   *             { "api_host": process.env.__OW_API_HOST,
+   *               "api_key": process.env__OW_API_KEY,
+   *               "namespace": process.env.__OW_NAMESPACE,
+   *               "action_name": process.env.__OW_ACTION_NAME,
+   *               "activation_id": process.env.__OW_ACTIVATION_ID,
+   *               "deadline": process.env.__OW_DEADLINE
+   *             }
+   * @param main the main function
+   * @param enforceEmptyOutputStream true to check empty stdout stream
+   * @param enforceEmptyErrorStream true to check empty stderr stream
+   */
+  def testEnv: TestConfig
+
+  /**
+   * Tests the action to confirm it can handle a large parameter (larger than 128K) when using STDIN.
    *
+   * @param code the identity/echo function.
+   * @param main the main function
    */
-  def testEnv(stdEnvSamples: Seq[(String, String)],
-              enforceEmptyOutputStream: Boolean = true,
-              enforceEmptyErrorStream: Boolean = true) = {
-    stdEnvSamples.foreach { s =>
-      it should s"run a ${s._1} script and confirm expected environment variables" in {
-        val props = Seq(
-          "api_host" -> "xyz",
-          "api_key" -> "abc",
-          "namespace" -> "zzz",
-          "action_name" -> "xxx",
-          "activation_id" -> "iii",
-          "deadline" -> "123")
-        val env = props.map { case (k, v) => s"__OW_${k.toUpperCase()}" -> v }
-
-        val (out, err) = withActionContainer(env.take(1).toMap) { c =>
-          val (initCode, _) = c.init(initPayload(s._2))
-          initCode should be(200)
-
-          val (runCode, out) = c.run(runPayload(JsObject.empty, Some(props.toMap.toJson.asJsObject)))
-          runCode should be(200)
-          out shouldBe defined
-          props.map {
-            case (k, v) =>
-              withClue(k) {
-                out.get.fields(k) shouldBe JsString(v)
-              }
+  def testLargeInput: TestConfig
 
-          }
-        }
+  behavior of "runtime proxy"
+
+  it should "handle initialization with no code" in {
+    val config = testNoSourceOrExec
 
-        checkStreams(out, err, {
-          case (o, e) =>
-            if (enforceEmptyOutputStream) o shouldBe empty
-            if (enforceEmptyErrorStream) e shouldBe empty
-        })
+    val (out, err) = withActionContainer() { c =>
+      val (initCode, out) = c.init(initPayload("", ""))
+      if (config.hasCodeStub) {
+        initCode should be(200)
+      } else {
+        initCode should not be (200)
       }
     }
   }
 
-  /**
-   * Tests the action to confirm it can handle a large parameter (larger than 128K) when using STDIN.
-   * @param stdLargeInputSamples a sequence of tuples, where each tuple provide a test name in the first component
-   *                             and the identity/echo function in the second component.
-   */
-  def testLargeInput(stdLargeInputSamples: Seq[(String, String)]) = {
-    stdLargeInputSamples.foreach { s =>
-      it should s"run a ${s._1} script with large input" in {
-        val (out, err) = withActionContainer() { c =>
-          val (initCode, _) = c.init(initPayload(s._2))
-          initCode should be(200)
-
-          val arg = JsObject("arg" -> JsString(("a" * 1048561)))
-          val (_, runRes) = c.run(runPayload(arg))
-          runRes.get shouldBe arg
+  it should "handle initialization with no content" in {
+    val config = testNoSourceOrExec
+
+    val (out, err) = withActionContainer() { c =>
+      val (initCode, out) = c.init(JsObject.empty)
+      if (!config.hasCodeStub) {
+        initCode should not be (200)
+        out should {
+          be(Some(JsObject("error" -> JsString("Missing main/no code to execute.")))) or
+            be(Some(
+              JsObject("error" -> JsString("The action failed to generate or locate a binary. See logs for details."))))
         }
+      } else {
+        initCode should be(200)
       }
     }
   }
 
-  /**
-   * Tests that an action will not allow more than one initialization and confirms expected error messages.
-   * @param code the code to execute, the identity/echo function is sufficient.
-   */
-  def testInitCannotBeCalledMoreThanOnce(code: String) = {
-    it should "fail to initialize a second time" in {
-      val errorMessage = "Cannot initialize the action more than once."
+  it should "run and report an error for function not returning a json object" in {
+    val config = testNotReturningJson
+    if (!config.skipTest) { // this may not be possible in types languages
       val (out, err) = withActionContainer() { c =>
-        val (initCode1, _) = c.init(initPayload(code))
-        initCode1 should be(200)
-
-        val (initCode2, error2) = c.init(initPayload(code))
-        initCode2 should be(403)
-        error2 should be(Some(JsObject("error" -> JsString(errorMessage))))
+        val (initCode, _) = c.init(initPayload(config.code, config.main))
+        initCode should be(200)
+        val (runCode, out) = c.run(JsObject.empty)
+        runCode should not be (200)
+        out should be(Some(JsObject("error" -> JsString("The action did not return a dictionary."))))
       }
 
       checkStreams(out, err, {
         case (o, e) =>
-          (o + e) should include(errorMessage)
+          // some runtimes may emita an error message,
+          // or for native runtimes emit the result to stdout
+          if (config.enforceEmptyOutputStream) o shouldBe empty
+          if (config.enforceEmptyErrorStream) e shouldBe empty
       })
     }
   }
+
+  it should "fail to initialize a second time" in {
+    val config = testInitCannotBeCalledMoreThanOnce
+
+    val errorMessage = "Cannot initialize the action more than once."
+
+    val (out, err) = withActionContainer() { c =>
+      val (initCode1, _) = c.init(initPayload(config.code, config.main))
+      initCode1 should be(200)
+
+      val (initCode2, error2) = c.init(initPayload(config.code, config.main))
+      initCode2 should not be (200)
+      error2 should be(Some(JsObject("error" -> JsString(errorMessage))))
+    }
+
+    checkStreams(out, err, {
+      case (o, e) =>
+        (o + e) should include(errorMessage)
+    }, sentinelCount = if (config.skipTest) 1 else 0)
+  }
+
+  it should s"invoke non-standard entry point" in {
+    val config = testEntryPointOtherThanMain
+    config.main should not be ("main")
+
+    val (out, err) = withActionContainer() { c =>
+      val (initCode, _) = c.init(initPayload(config.code, config.main))
+      initCode should be(200)
+
+      val arg = JsObject("string" -> JsString("hello"))
+      val (runCode, out) = c.run(runPayload(arg))
+      runCode should be(200)
+      out should be(Some(arg))
+    }
+
+    checkStreams(out, err, {
+      case (o, e) =>
+        // some native runtimes will emit the result to stdout
+        if (config.enforceEmptyOutputStream) o shouldBe empty
+        e shouldBe empty
+    })
+  }
+
+  it should s"echo arguments and print message to stdout/stderr" in {
+    val config = testEcho
+
+    val argss = List(
+      JsObject("string" -> JsString("hello")),
+      JsObject("string" -> JsString("❄ ☃ ❄")),
+      JsObject("numbers" -> JsArray(JsNumber(42), JsNumber(1))),
+      // JsObject("boolean" -> JsBoolean(true)), // fails with swift3 returning boolean: 1
+      JsObject("object" -> JsObject("a" -> JsString("A"))))
+
+    val (out, err) = withActionContainer() { c =>
+      val (initCode, _) = c.init(initPayload(config.code, config.main))
+      initCode should be(200)
+
+      for (args <- argss) {
+        val (runCode, out) = c.run(runPayload(args))
+        runCode should be(200)
+        out should be(Some(args))
+      }
+    }
+
+    checkStreams(out, err, {
+      case (o, e) =>
+        o should include("hello stdout")
+        e should include("hello stderr")
+    }, argss.length)
+  }
+
+  it should s"handle unicode in source, input params, logs, and result" in {
+    val config = testUnicode
+
+    val (out, err) = withActionContainer() { c =>
+      val (initCode, _) = c.init(initPayload(config.code, config.main))
+      initCode should be(200)
+
+      val (runCode, runRes) = c.run(runPayload(JsObject("delimiter" -> JsString("❄"))))
+      runRes.get.fields.get("winter") shouldBe Some(JsString("❄ ☃ ❄"))
+    }
+
+    checkStreams(out, err, {
+      case (o, _) =>
+        o.toLowerCase should include("❄ ☃ ❄")
+    })
+  }
+
+  it should s"confirm expected environment variables" in {
+    val config = testEnv
+
+    val props = Seq(
+      "api_host" -> "xyz",
+      "api_key" -> "abc",
+      "namespace" -> "zzz",
+      "action_name" -> "xxx",
+      "activation_id" -> "iii",
+      "deadline" -> "123")
+
+    val env = props.map { case (k, v) => s"__OW_${k.toUpperCase()}" -> v }
+
+    val (out, err) = withActionContainer(env.take(1).toMap) { c =>
+      val (initCode, _) = c.init(initPayload(config.code, config.main))
+      initCode should be(200)
+
+      val (runCode, out) = c.run(runPayload(JsObject.empty, Some(props.toMap.toJson.asJsObject)))
+      runCode should be(200)
+      out shouldBe defined
+      props.map {
+        case (k, v) =>
+          withClue(k) {
+            out.get.fields(k) shouldBe JsString(v)
+          }
+
+      }
+    }
+
+    checkStreams(out, err, {
+      case (o, e) =>
+        if (config.enforceEmptyOutputStream) o shouldBe empty
+        if (config.enforceEmptyErrorStream) e shouldBe empty
+    })
+  }
+
+  it should s"echo a large input" in {
+    val config = testLargeInput
+
+    val (out, err) = withActionContainer() { c =>
+      val (initCode, _) = c.init(initPayload(config.code, config.main))
+      initCode should be(200)
+
+      val arg = JsObject("arg" -> JsString(("a" * 1048561)))
+      val (_, runRes) = c.run(runPayload(arg))
+      runRes.get shouldBe arg
+    }
+  }
+
+  case class TestConfig(code: String,
+                        main: String = "main",
+                        enforceEmptyOutputStream: Boolean = true,
+                        enforceEmptyErrorStream: Boolean = true,
+                        hasCodeStub: Boolean = false,
+                        skipTest: Boolean = false)
 }
diff --git a/tests/src/test/scala/common/rest/WskRestOperations.scala b/tests/src/test/scala/common/rest/WskRestOperations.scala
index ccea88b..eefa782 100644
--- a/tests/src/test/scala/common/rest/WskRestOperations.scala
+++ b/tests/src/test/scala/common/rest/WskRestOperations.scala
@@ -22,7 +22,7 @@ import java.time.Instant
 import java.util.Base64
 import java.security.cert.X509Certificate
 
-import org.apache.commons.io.FileUtils
+import org.apache.commons.io.{FileUtils, FilenameUtils}
 import org.scalatest.Matchers
 import org.scalatest.concurrent.ScalaFutures
 import org.scalatest.time.Span.convertDurationToSpan
@@ -36,7 +36,6 @@ import scala.util.{Failure, Success}
 import akka.http.scaladsl.model.StatusCode
 import akka.http.scaladsl.model.StatusCodes.Accepted
 import akka.http.scaladsl.model.StatusCodes.NotFound
-import akka.http.scaladsl.model.StatusCodes.BadRequest
 import akka.http.scaladsl.model.StatusCodes.OK
 import akka.http.scaladsl.model.HttpRequest
 import akka.http.scaladsl.model.HttpMethod
@@ -289,160 +288,106 @@ class RestActionOperations(implicit val actorSystem: ActorSystem)
     websecure: Option[String] = None,
     expectedExitCode: Int = OK.intValue)(implicit wp: WskProps): RestResult = {
 
-    val (namespace, actName) = getNamespaceEntityName(name)
-    val (kindTypeByAction, code, artifactName) = artifact match {
-      case Some(artifactFile) => {
-        val ext = getExt(artifactFile)
-        ext match {
-          case ".jar" => {
-            val jar = FileUtils.readFileToByteArray(new File(artifactFile))
-            ("java:default", Base64.getEncoder.encodeToString(jar), artifactFile)
-          }
-          case ".zip" => {
-            val zip = FileUtils.readFileToByteArray(new File(artifactFile))
-            ("", Base64.getEncoder.encodeToString(zip), artifactFile)
-          }
-          case ".js" => {
-            ("nodejs:default", FileUtils.readFileToString(new File(artifactFile), StandardCharsets.UTF_8), artifactFile)
-          }
-          case ".py" => {
-            ("python:default", FileUtils.readFileToString(new File(artifactFile), StandardCharsets.UTF_8), artifactFile)
-          }
-          case ".swift" => {
-            ("swift:default", FileUtils.readFileToString(new File(artifactFile), StandardCharsets.UTF_8), artifactFile)
-          }
-          case ".php" => {
-            ("php:default", FileUtils.readFileToString(new File(artifactFile), StandardCharsets.UTF_8), artifactFile)
-          }
-          case _ => ("", "", artifactFile)
-        }
-      }
-      case None => ("", "", "")
-    }
-
-    val kindType = docker map { d =>
-      "blackbox"
-    } getOrElse kindTypeByAction
-
+    val (namespace, actionName) = getNamespaceEntityName(name)
     val (paramsInput, annosInput) = getParamsAnnos(parameters, annotations, parameterFile, annotationFile, web = web)
-
-    val (params, annos, execByKind) = kind match {
-      case Some(k) => {
+    val (params: Array[JsValue], annos: Array[JsValue], exec: Map[String, JsValue]) = kind match {
+      case Some(k) =>
         k match {
-          case "copy" => {
-            val actName = entityName(artifactName)
-            val actionPath = Path(s"$basePath/namespaces/$namespace/$noun/$actName")
+          case "copy" =>
+            require(artifact.isDefined, "copy requires an artifact name")
+            val actionName = entityName(artifact.get)
+            val actionPath = Path(s"$basePath/namespaces/$namespace/$noun/$actionName")
             val resp = requestEntity(GET, actionPath)
-            if (resp == None)
-              return new RestResult(NotFound)
+            if (resp == None) return new RestResult(NotFound) // NOTE return, aborts the method
             else {
               val result = new RestResult(resp.status, getRespData(resp))
               val params = result.getFieldListJsObject("parameters").toArray[JsValue]
               val annos = result.getFieldListJsObject("annotations").toArray[JsValue]
               val exec = result.getFieldJsObject("exec").fields
-              (params, annos, exec)
+              (paramsInput ++ params, annosInput ++ annos, exec)
             }
-          }
-          case "sequence" => {
-            val comps = convertIntoComponents(artifactName)
+
+          case "sequence" =>
+            require(artifact.isDefined, "sequence requires a component list")
+            val comps = convertIntoComponents(artifact.get)
             val exec =
               if (comps.size > 0) Map("components" -> comps.toJson, "kind" -> k.toJson)
-              else
-                Map("kind" -> k.toJson)
-            (paramsInput, annosInput, exec)
-          }
-          case "native" => {
-            val exec =
-              Map("code" -> code.toJson, "kind" -> "blackbox".toJson, "image" -> "openwhisk/dockerskeleton".toJson)
+              else Map("kind" -> k.toJson)
             (paramsInput, annosInput, exec)
-          }
-          case _ => {
-            val exec = Map("code" -> code.toJson, "kind" -> k.toJson)
-            (paramsInput, annosInput, exec)
-          }
-        }
-      }
-      case None => (paramsInput, annosInput, Map("code" -> code.toJson, "kind" -> kindType.toJson))
-    }
 
-    val exec = execByKind ++ {
-      main map { m =>
-        Map("main" -> m.toJson)
-      } getOrElse Map.empty
-    } ++ {
-      docker map { d =>
-        Map("kind" -> "blackbox".toJson, "image" -> d.toJson)
-      } getOrElse Map.empty
-    }
-
-    val bodyHead = Map("name" -> name.toJson, "namespace" -> namespace.toJson)
-
-    val (updateExistence, resultExistence) = update match {
-      case true => {
-        val (ns, entity) = getNamespaceEntityName(name)
-        val entPath = Path(s"$basePath/namespaces/$ns/$noun/$entity")
-        val resp = requestEntity(GET, entPath)(wp)
-        val result = if (resp == None) new RestResult(NotFound) else new RestResult(resp.status, getRespData(resp))
-        if (result.statusCode == NotFound) (false, result) else (true, result)
-      }
-      case _ => (false, new RestResult(NotFound))
-    }
+          case _ =>
+            val code = readCodeFromFile(artifact).map(c => Map("code" -> c.toJson)).getOrElse(Map.empty)
+            val exec: Map[String, JsValue] = if (k == "native" || k == "docker") {
+              require(k == "native" && docker.isEmpty || k == "docker" && docker.isDefined)
+              Map("kind" -> "blackbox".toJson, "image" -> docker.getOrElse("openwhisk/dockerskeleton").toJson) ++ code
+            } else {
+              require(artifact.isDefined, "file name required as an artifact")
+              Map("kind" -> k.toJson) ++ code
+            }
 
-    val bodyWithParamsAnnos = if (updateExistence) {
-      val oldAnnos = resultExistence.getFieldListJsObject("annotations")
-      val inputParams = convertMapIntoKeyValue(parameters)
-      val inputAnnos = convertMapIntoKeyValue(annotations, web = web, oldParams = oldAnnos.toList)
+            (paramsInput, annosInput, exec)
+        }
 
-      bodyHead ++ {
-        kind match {
-          case Some(k) if (k == "sequence" || k == "native") => {
-            Map("exec" -> exec.toJson)
+      case None =>
+        docker
+          .map(_ => "blackbox")
+          .orElse {
+            artifact.map { file =>
+              getExt(file) match {
+                case "js"    => "nodejs:default"
+                case "py"    => "python:default"
+                case "swift" => "swift:default"
+                case "jar"   => "java:default"
+                case _ =>
+                  throw new IllegalStateException(s"Extension for $file not recognized and kind cannot be inferred.")
+              }
+            }
+          }
+          .map { k =>
+            val code = readCodeFromFile(artifact).map(c => Map("code" -> c.toJson)).getOrElse(Map.empty)
+            val image = docker.map(i => Map("image" -> i.toJson)).getOrElse(Map.empty)
+            (paramsInput, annosInput, Map("kind" -> k.toJson) ++ code ++ image)
+          }
+          .getOrElse {
+            if (!update && artifact.isDefined)
+              throw new IllegalStateException(
+                s"Extension for ${artifact.get} not recognized and kind cannot be inferred.")
+            else (paramsInput, annosInput, Map.empty)
           }
-          case _ => Map.empty
-        }
-      } ++ {
-        shared map { s =>
-          Map("publish" -> s.toJson)
-        } getOrElse Map.empty
-      } ++ {
-        if (inputParams.size > 0) {
-          Map("parameters" -> params.toJson)
-        } else Map.empty
-      } ++ {
-        if (inputAnnos.size > 0) {
-          Map("annotations" -> inputAnnos.toJson)
-        } else Map.empty
-      }
-    } else {
-      bodyHead ++ Map("exec" -> exec.toJson, "parameters" -> params.toJson, "annotations" -> annos.toJson)
     }
 
     val limits: Map[String, JsValue] = {
-      timeout map { t =>
-        Map("timeout" -> t.toMillis.toJson)
-      } getOrElse Map.empty
-    } ++ {
-      logsize map { log =>
-        Map("logs" -> log.toMB.toJson)
-      } getOrElse Map.empty
-    } ++ {
-      memory map { m =>
-        Map("memory" -> m.toMB.toJson)
-      } getOrElse Map.empty
+      timeout.map(t => Map("timeout" -> t.toMillis.toJson)).getOrElse(Map.empty) ++
+        logsize.map(log => Map("logs" -> log.toMB.toJson)).getOrElse(Map.empty) ++
+        memory.map(m => Map("memory" -> m.toMB.toJson)).getOrElse(Map.empty)
     }
 
-    val bodyContent =
-      if (!limits.isEmpty)
-        bodyWithParamsAnnos ++ Map("limits" -> limits.toJson)
-      else bodyWithParamsAnnos
+    val body: Map[String, JsValue] = if (!update) {
+      require(exec.nonEmpty, "exec cannot be empty on create")
+      Map(
+        "exec" -> main.map(m => exec ++ Map("main" -> m.toJson)).getOrElse(exec).toJson,
+        "parameters" -> params.toJson,
+        "annotations" -> annos.toJson) ++ Map("limits" -> limits.toJson)
+    } else {
+      var content: Map[String, JsValue] = Map.empty
+      if (exec.nonEmpty)
+        content = Map("exec" -> main.map(m => exec ++ Map("main" -> m.toJson)).getOrElse(exec).toJson)
+      if (params.length > 0)
+        content = content + ("parameters" -> params.toJson)
+      if (annos.length > 0)
+        content = content + ("annotations" -> annos.toJson)
+      if (limits.nonEmpty)
+        content = content + ("limits" -> limits.toJson)
+      content
+    }
 
-    val path = Path(s"$basePath/namespaces/$namespace/$noun/$actName")
+    val path = Path(s"$basePath/namespaces/$namespace/$noun/$actionName")
     val resp =
-      if (update) requestEntity(PUT, path, Map("overwrite" -> "true"), Some(JsObject(bodyContent).toString))
-      else requestEntity(PUT, path, body = Some(JsObject(bodyContent).toString))
-    val r = new RestResult(resp.status, getRespData(resp))
-    validateStatusCode(expectedExitCode, r.statusCode.intValue)
-    r
+      if (update) requestEntity(PUT, path, Map("overwrite" -> "true"), Some(JsObject(body).toString))
+      else requestEntity(PUT, path, body = Some(JsObject(body).toString))
+    val rr = new RestResult(resp.status, getRespData(resp))
+    validateStatusCode(expectedExitCode, rr.statusCode.intValue)
+    rr
   }
 
   override def invoke(name: String,
@@ -453,6 +398,19 @@ class RestActionOperations(implicit val actorSystem: ActorSystem)
                       expectedExitCode: Int = Accepted.intValue)(implicit wp: WskProps): RestResult = {
     super.invokeAction(name, parameters, parameterFile, blocking, result, expectedExitCode = expectedExitCode)
   }
+
+  private def readCodeFromFile(artifact: Option[String]): Option[String] = {
+    artifact.map { file =>
+      val ext = getExt(file)
+      val isBinary = ext == "zip" || ext == "jar" || ext == "bin"
+      if (!isBinary) {
+        FileUtils.readFileToString(new File(file), StandardCharsets.UTF_8)
+      } else {
+        val zip = FileUtils.readFileToByteArray(new File(file))
+        Base64.getEncoder.encodeToString(zip)
+      }
+    }
+  }
 }
 
 class RestTriggerOperations(implicit val actorSystem: ActorSystem)
@@ -480,42 +438,40 @@ class RestTriggerOperations(implicit val actorSystem: ActorSystem)
                       update: Boolean = false,
                       expectedExitCode: Int = OK.intValue)(implicit wp: WskProps): RestResult = {
 
-    val (ns, triggerName) = this.getNamespaceEntityName(name)
+    val (ns, triggerName) = getNamespaceEntityName(name)
     val path = Path(s"$basePath/namespaces/$ns/$noun/$triggerName")
     val (params, annos) = getParamsAnnos(parameters, annotations, parameterFile, annotationFile, feed)
-    var bodyContent = JsObject("name" -> name.toJson, "namespace" -> s"$ns".toJson)
+    var bodyContent: Map[String, JsValue] = Map.empty
 
     if (!update) {
-      val published = shared.getOrElse(false)
-      bodyContent = JsObject(
-        bodyContent.fields + ("publish" -> published.toJson,
-        "parameters" -> params.toJson, "annotations" -> annos.toJson))
+      bodyContent =
+        Map("publish" -> shared.getOrElse(false).toJson, "parameters" -> params.toJson, "annotations" -> annos.toJson)
     } else {
-      bodyContent = shared map { s =>
-        JsObject(bodyContent.fields + ("publish" -> s.toJson))
-      } getOrElse bodyContent
+      shared.foreach { p =>
+        bodyContent = Map("publish" -> p.toJson)
+      }
 
       val inputParams = convertMapIntoKeyValue(parameters)
       if (inputParams.size > 0) {
-        bodyContent = JsObject(bodyContent.fields + ("parameters" -> params.toJson))
+        bodyContent = bodyContent + ("parameters" -> params.toJson)
       }
       val inputAnnos = convertMapIntoKeyValue(annotations)
       if (inputAnnos.size > 0) {
-        bodyContent = JsObject(bodyContent.fields + ("annotations" -> annos.toJson))
+        bodyContent = bodyContent + ("annotations" -> annos.toJson)
       }
     }
 
     val resp =
-      if (update) requestEntity(PUT, path, Map("overwrite" -> "true"), Some(bodyContent.toString))
-      else requestEntity(PUT, path, body = Some(bodyContent.toString))
+      if (update) requestEntity(PUT, path, Map("overwrite" -> "true"), Some(JsObject(bodyContent).toString))
+      else requestEntity(PUT, path, body = Some(JsObject(bodyContent).toString))
     val result = new RestResult(resp.status, getRespData(resp))
     if (result.statusCode != OK) {
       validateStatusCode(expectedExitCode, result.statusCode.intValue)
       result
     }
-    val r = feed map { f =>
+    val rr = feed map { f =>
       // Invoke the feed
-      val (nsFeed, feedName) = this.getNamespaceEntityName(f)
+      val (nsFeed, feedName) = getNamespaceEntityName(f)
       val path = Path(s"$basePath/namespaces/$nsFeed/actions/$feedName")
       val paramMap = Map("blocking" -> "true", "result" -> "false")
       var body: Map[String, JsValue] = Map(
@@ -529,7 +485,7 @@ class RestTriggerOperations(implicit val actorSystem: ActorSystem)
         expectedExitCode shouldBe resultInvoke.statusCode.intValue
       if (resultInvoke.statusCode != OK) {
         // Remove the trigger, because the feed failed to invoke.
-        this.delete(triggerName)
+        delete(triggerName)
         new RestResult(NotFound)
       } else {
         result
@@ -538,7 +494,7 @@ class RestTriggerOperations(implicit val actorSystem: ActorSystem)
       validateStatusCode(expectedExitCode, result.statusCode.intValue)
       result
     }
-    r
+    rr
   }
 
   /**
@@ -552,7 +508,7 @@ class RestTriggerOperations(implicit val actorSystem: ActorSystem)
                     parameters: Map[String, JsValue] = Map.empty,
                     parameterFile: Option[String] = None,
                     expectedExitCode: Int = Accepted.intValue)(implicit wp: WskProps): RestResult = {
-    val path = getNamePath(noun, name)
+    val path = getNamePath(wp.namespace, noun, name)
     val params = parameterFile map { l =>
       val input = FileUtils.readFileToString(new File(l), StandardCharsets.UTF_8)
       input.parseJson.convertTo[Map[String, JsValue]]
@@ -588,14 +544,13 @@ class RestRuleOperations(implicit val actorSystem: ActorSystem)
                       shared: Option[Boolean] = None,
                       update: Boolean = false,
                       expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RestResult = {
-    val path = getNamePath(noun, name)
+    val path = getNamePath(wp.namespace, noun, name)
     val annos = convertMapIntoKeyValue(annotations)
     val published = shared.getOrElse(false)
     val bodyContent = JsObject(
       "trigger" -> fullEntityName(trigger).toJson,
       "action" -> fullEntityName(action).toJson,
       "publish" -> published.toJson,
-      "name" -> name.toJson,
       "status" -> "".toJson,
       "annotations" -> annos.toJson)
 
@@ -640,7 +595,7 @@ class RestRuleOperations(implicit val actorSystem: ActorSystem)
 
   def changeRuleState(ruleName: String, state: String = "active")(implicit wp: WskProps): RestResult = {
     val enName = entityName(ruleName)
-    val path = getNamePath(noun, enName)
+    val path = getNamePath(wp.namespace, noun, enName)
     val bodyContent = JsObject("status" -> state.toJson)
     val resp = requestEntity(POST, path, body = Some(bodyContent.toString))
     new RestResult(resp.status, getRespData(resp))
@@ -668,9 +623,7 @@ class RestActivationOperations(implicit val actorSystem: ActorSystem)
     require(duration > 1.second, "duration must be at least 1 second")
     val sinceTime = {
       val now = System.currentTimeMillis()
-      since map { s =>
-        now - s.toMillis
-      } getOrElse now
+      since.map(s => now - s.toMillis).getOrElse(now)
     }
 
     retry({
@@ -694,19 +647,10 @@ class RestActivationOperations(implicit val actorSystem: ActorSystem)
                      docs: Boolean = true,
                      expectedExitCode: Int = SUCCESS_EXIT)(implicit wp: WskProps): RestResult = {
     val entityPath = Path(s"${basePath}/namespaces/${wp.namespace}/$noun")
-    val paramMap = Map("skip" -> "0", "docs" -> docs.toString) ++ {
-      limit map { l =>
-        Map("limit" -> l.toString)
-      } getOrElse Map.empty
-    } ++ {
-      filter map { f =>
-        Map("name" -> f.toString)
-      } getOrElse Map.empty
-    } ++ {
-      since map { s =>
-        Map("since" -> s.toEpochMilli.toString)
-      } getOrElse Map.empty
-    }
+    val paramMap = Map("skip" -> "0", "docs" -> docs.toString) ++
+      limit.map(l => Map("limit" -> l.toString)).getOrElse(Map.empty) ++
+      filter.map(f => Map("name" -> f.toString)).getOrElse(Map.empty) ++
+      since.map(s => Map("since" -> s.toEpochMilli.toString)).getOrElse(Map.empty)
     val resp = requestEntity(GET, entityPath, paramMap)
     new RestResult(resp.status, getRespData(resp))
   }
@@ -731,9 +675,9 @@ class RestActivationOperations(implicit val actorSystem: ActorSystem)
   def activationLogs(activationId: String, expectedExitCode: Int = OK.intValue)(implicit wp: WskProps): RestResult = {
     val path = Path(s"${basePath}/namespaces/${wp.namespace}/$noun/$activationId/logs")
     val resp = requestEntity(GET, path)
-    val r = new RestResult(resp.status, getRespData(resp))
-    validateStatusCode(expectedExitCode, r.statusCode.intValue)
-    r
+    val rr = new RestResult(resp.status, getRespData(resp))
+    validateStatusCode(expectedExitCode, rr.statusCode.intValue)
+    rr
   }
 
   /**
@@ -746,9 +690,9 @@ class RestActivationOperations(implicit val actorSystem: ActorSystem)
   def activationResult(activationId: String, expectedExitCode: Int = OK.intValue)(implicit wp: WskProps): RestResult = {
     val path = Path(s"${basePath}/namespaces/${wp.namespace}/$noun/$activationId/result")
     val resp = requestEntity(GET, path)
-    val r = new RestResult(resp.status, getRespData(resp))
-    validateStatusCode(expectedExitCode, r.statusCode.intValue)
-    r
+    val rr = new RestResult(resp.status, getRespData(resp))
+    validateStatusCode(expectedExitCode, rr.statusCode.intValue)
+    rr
   }
 
   /**
@@ -788,17 +732,16 @@ class RestActivationOperations(implicit val actorSystem: ActorSystem)
                    fieldFilter: Option[String] = None,
                    last: Option[Boolean] = None,
                    summary: Option[Boolean] = None)(implicit wp: WskProps): RestResult = {
-    val r = activationId match {
-      case Some(id) => {
-        val resp = requestEntity(GET, getNamePath(noun, id))
+    val rr = activationId match {
+      case Some(id) =>
+        val resp = requestEntity(GET, getNamePath(wp.namespace, noun, id))
         new RestResult(resp.status, getRespData(resp))
-      }
-      case None => {
+
+      case None =>
         new RestResult(NotFound)
-      }
     }
-    validateStatusCode(expectedExitCode, r.statusCode.intValue)
-    r
+    validateStatusCode(expectedExitCode, rr.statusCode.intValue)
+    rr
   }
 
   /**
@@ -830,33 +773,31 @@ class RestActivationOperations(implicit val actorSystem: ActorSystem)
   override def logs(activationId: Option[String] = None,
                     expectedExitCode: Int = OK.intValue,
                     last: Option[Boolean] = None)(implicit wp: WskProps): RestResult = {
-    val r = activationId match {
-      case Some(id) => {
-        val resp = requestEntity(GET, getNamePath(noun, s"$id/logs"))
+    val rr = activationId match {
+      case Some(id) =>
+        val resp = requestEntity(GET, getNamePath(wp.namespace, noun, s"$id/logs"))
         new RestResult(resp.status, getRespData(resp))
-      }
-      case None => {
+
+      case None =>
         new RestResult(NotFound)
-      }
     }
-    validateStatusCode(expectedExitCode, r.statusCode.intValue)
-    r
+    validateStatusCode(expectedExitCode, rr.statusCode.intValue)
+    rr
   }
 
   override def result(activationId: Option[String] = None,
                       expectedExitCode: Int = OK.intValue,
                       last: Option[Boolean] = None)(implicit wp: WskProps): RestResult = {
-    val r = activationId match {
-      case Some(id) => {
-        val resp = requestEntity(GET, getNamePath(noun, s"$id/result"))
+    val rr = activationId match {
+      case Some(id) =>
+        val resp = requestEntity(GET, getNamePath(wp.namespace, noun, s"$id/result"))
         new RestResult(resp.status, getRespData(resp))
-      }
-      case None => {
+
+      case None =>
         new RestResult(NotFound)
-      }
     }
-    validateStatusCode(expectedExitCode, r.statusCode.intValue)
-    r
+    validateStatusCode(expectedExitCode, rr.statusCode.intValue)
+    rr
   }
 
   /** Used in polling for activations to record partial results from retry poll. */
@@ -916,34 +857,31 @@ class RestPackageOperations(implicit val actorSystem: ActorSystem)
                       shared: Option[Boolean] = None,
                       update: Boolean = false,
                       expectedExitCode: Int = OK.intValue)(implicit wp: WskProps): RestResult = {
-    val path = getNamePath(noun, name)
-    var bodyContent = JsObject("namespace" -> s"${wp.namespace}".toJson, "name" -> name.toJson)
+    val path = getNamePath(wp.namespace, noun, name)
+    var bodyContent: Map[String, JsValue] = Map.empty
 
-    val (params, annos) = this.getParamsAnnos(parameters, annotations, parameterFile, annotationFile)
+    val (params, annos) = getParamsAnnos(parameters, annotations, parameterFile, annotationFile)
     if (!update) {
       val published = shared.getOrElse(false)
-      bodyContent = JsObject(
-        bodyContent.fields + ("publish" -> published.toJson,
-        "parameters" -> params.toJson, "annotations" -> annos.toJson))
+      bodyContent = Map("publish" -> published.toJson, "parameters" -> params.toJson, "annotations" -> annos.toJson)
     } else {
-      if (shared != None)
-        bodyContent = shared map { s =>
-          JsObject(bodyContent.fields + ("publish" -> s.toJson))
-        } getOrElse bodyContent
+      shared.foreach { s =>
+        bodyContent = bodyContent + ("publish" -> s.toJson)
+      }
 
       val inputParams = convertMapIntoKeyValue(parameters)
       if (inputParams.size > 0) {
-        bodyContent = JsObject(bodyContent.fields + ("parameters" -> params.toJson))
+        bodyContent = bodyContent + ("parameters" -> params.toJson)
       }
       val inputAnnos = convertMapIntoKeyValue(annotations)
       if (inputAnnos.size > 0) {
-        bodyContent = JsObject(bodyContent.fields + ("annotations" -> annos.toJson))
+        bodyContent = bodyContent + ("annotations" -> annos.toJson)
       }
     }
 
     val resp =
-      if (update) requestEntity(PUT, path, Map("overwrite" -> "true"), Some(bodyContent.toString))
-      else requestEntity(PUT, path, body = Some(bodyContent.toString))
+      if (update) requestEntity(PUT, path, Map("overwrite" -> "true"), Some(JsObject(bodyContent).toString))
+      else requestEntity(PUT, path, body = Some(JsObject(bodyContent).toString))
     val r = new RestResult(resp.status, getRespData(resp))
     validateStatusCode(expectedExitCode, r.statusCode.intValue)
     r
@@ -964,15 +902,15 @@ class RestPackageOperations(implicit val actorSystem: ActorSystem)
     val params = convertMapIntoKeyValue(parameters)
     val annos = convertMapIntoKeyValue(annotations)
 
-    val (ns, packageName) = this.getNamespaceEntityName(provider)
-    val path = getNamePath(noun, name)
+    val (ns, packageName) = getNamespaceEntityName(provider)
+    val path = getNamePath(wp.namespace, noun, name)
     val binding = JsObject("namespace" -> ns.toJson, "name" -> packageName.toJson)
     val bodyContent =
       JsObject("binding" -> binding.toJson, "parameters" -> params.toJson, "annotations" -> annos.toJson)
     val resp = requestEntity(PUT, path, Map("overwrite" -> "false"), Some(bodyContent.toString))
-    val r = new RestResult(resp.status, getRespData(resp))
-    validateStatusCode(expectedExitCode, r.statusCode.intValue)
-    r
+    val rr = new RestResult(resp.status, getRespData(resp))
+    validateStatusCode(expectedExitCode, rr.statusCode.intValue)
+    rr
   }
 
 }
@@ -997,7 +935,7 @@ class RestGatewayOperations(implicit val actorSystem: ActorSystem) extends Gatew
                       cliCfgFile: Option[String] = None)(implicit wp: WskProps): RestResult = {
     val r = action match {
       case Some(action) => {
-        val (ns, actionName) = this.getNamespaceEntityName(action)
+        val (ns, actionName) = getNamespaceEntityName(action)
         val actionUrl = s"${WhiskProperties.getApiHostForAction}$basePath/web/$ns/default/$actionName.http"
         val actionAuthKey = wp.authKey
         val testaction = Some(
@@ -1227,15 +1165,9 @@ trait RunRestCmd extends Matchers with ScalaFutures {
       }
   }
 
-  def getNamePath(noun: String, name: String)(implicit wp: WskProps): Path = {
-    Path(s"$basePath/namespaces/${wp.namespace}/$noun/$name")
-  }
+  def getNamePath(ns: String, noun: String, name: String) = Path(s"$basePath/namespaces/$ns/$noun/$name")
 
-  def getExt(filePath: String)(implicit wp: WskProps) = {
-    val sep = "."
-    if (filePath.contains(sep)) filePath.substring(filePath.lastIndexOf(sep), filePath.length)
-    else ""
-  }
+  def getExt(filePath: String) = Option(FilenameUtils.getExtension(filePath)).getOrElse("")
 
   def requestEntity(method: HttpMethod,
                     path: Path,
@@ -1251,16 +1183,11 @@ trait RunRestCmd extends Matchers with ScalaFutures {
       Uri().withScheme("https").withHost(wp.apihost)
     }
 
-    val entity = body map { b =>
-      HttpEntity(ContentTypes.`application/json`, b)
-    } getOrElse HttpEntity.Empty
-
-    val request =
-      HttpRequest(
-        method,
-        hostWithScheme.withPath(path).withQuery(Query(params)),
-        List(Authorization(creds)),
-        entity = entity)
+    val request = HttpRequest(
+      method,
+      hostWithScheme.withPath(path).withQuery(Query(params)),
+      List(Authorization(creds)),
+      entity = body.map(b => HttpEntity(ContentTypes.`application/json`, b)).getOrElse(HttpEntity.Empty))
     Http().singleRequest(request, connectionContext).futureValue
   }
 
@@ -1354,11 +1281,7 @@ trait RunRestCmd extends Matchers with ScalaFutures {
 
   def getRespData(resp: HttpResponse): String = {
     val timeout = 5.seconds
-    Try {
-      resp.entity.toStrict(timeout).map { _.data }.map(_.utf8String).futureValue
-    } getOrElse {
-      ""
-    }
+    Try(resp.entity.toStrict(timeout).map { _.data }.map(_.utf8String).futureValue).getOrElse("")
   }
 
   def getNamespaceEntityName(name: String)(implicit wp: WskProps): (String, String) = {
@@ -1386,7 +1309,7 @@ trait RunRestCmd extends Matchers with ScalaFutures {
                    result: Boolean = false,
                    web: Boolean = false,
                    expectedExitCode: Int = Accepted.intValue)(implicit wp: WskProps): RestResult = {
-    val (ns, actName) = this.getNamespaceEntityName(name)
+    val (ns, actName) = getNamespaceEntityName(name)
 
     val path =
       if (web) Path(s"$basePath/web/$systemNamespace/$actName.http")
@@ -1417,9 +1340,6 @@ trait RunRestCmd extends Matchers with ScalaFutures {
 }
 
 object RestResult {
-
-  val codeConversion = 256
-
   def getField(obj: JsObject, key: String): String = {
     obj.fields.get(key).map(_.convertTo[String]).getOrElse("")
   }
@@ -1438,16 +1358,11 @@ object RestResult {
 
   def convertStausCodeToExitCode(statusCode: StatusCode, blocking: Boolean = false): Int = {
     if ((statusCode == OK) || (!blocking && (statusCode == Accepted))) 0
-    else if (statusCode.intValue < BadRequest.intValue) statusCode.intValue
-    else statusCode.intValue - codeConversion
+    else statusCode.intValue % 256
   }
 
   def convertHttpResponseToStderr(respData: String): String = {
-    Try {
-      getField(respData.parseJson.asJsObject, "error")
-    } getOrElse {
-      ""
-    }
+    Try(getField(respData.parseJson.asJsObject, "error")).getOrElse("")
   }
 }
 
diff --git a/tests/src/test/scala/system/basic/WskActionTests.scala b/tests/src/test/scala/system/basic/WskActionTests.scala
index 4eddb13..c499f50 100644
--- a/tests/src/test/scala/system/basic/WskActionTests.scala
+++ b/tests/src/test/scala/system/basic/WskActionTests.scala
@@ -19,21 +19,16 @@ package system.basic
 
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-import common.ActivationResult
-import common.JsHelpers
-import common.TestHelpers
-import common.TestUtils
-import common.WskOperations
-import common.WskProps
-import common.WskTestHelpers
+import common._
+import common.rest.WskRestOperations
 import spray.json._
 import spray.json.DefaultJsonProtocol._
 
 @RunWith(classOf[JUnitRunner])
-abstract class WskActionTests extends TestHelpers with WskTestHelpers with JsHelpers {
+class WskActionTests extends TestHelpers with WskTestHelpers with JsHelpers with WskActorSystem {
 
   implicit val wskprops = WskProps()
-  val wsk: WskOperations
+  val wsk: WskOperations = new WskRestOperations
 
   val testString = "this is a test"
   val testResult = JsObject("count" -> testString.split(" ").length.toJson)
@@ -41,6 +36,15 @@ abstract class WskActionTests extends TestHelpers with WskTestHelpers with JsHel
 
   behavior of "Whisk actions"
 
+  it should "create an action with an empty file" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
+    val name = "empty"
+    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+      action.create(name, Some(TestUtils.getTestActionFilename("empty.js")))
+    }
+    val rr = wsk.action.get(name)
+    wsk.parseJsonString(rr.stdout).getFieldPath("exec", "code") shouldBe Some(JsString(""))
+  }
+
   it should "invoke an action returning a promise" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
     val name = "hello promise"
     assetHelper.withCleaner(wsk.action, name) { (action, _) =>
@@ -269,7 +273,7 @@ abstract class WskActionTests extends TestHelpers with WskTestHelpers with JsHel
     }
   }
 
-  ignore should "support UTF-8 as input and output format" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
+  it should "support UTF-8 as input and output format" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
     val name = "utf8Test"
     assetHelper.withCleaner(wsk.action, name) { (action, _) =>
       action.create(name, Some(TestUtils.getTestActionFilename("hello.js")))
@@ -279,7 +283,7 @@ abstract class WskActionTests extends TestHelpers with WskTestHelpers with JsHel
     val run = wsk.action.invoke(name, Map("payload" -> utf8.toJson))
     withActivation(wsk.activation, run) { activation =>
       activation.response.status shouldBe "success"
-      activation.logs.get.mkString(" ") should include(s"hello $utf8")
+      activation.logs.get.mkString(" ") should include(s"hello, $utf8")
     }
   }
 }
diff --git a/tests/src/test/scala/system/basic/WskActivationTests.scala b/tests/src/test/scala/system/basic/WskActivationTests.scala
index 4d5c216..2df8bdd 100644
--- a/tests/src/test/scala/system/basic/WskActivationTests.scala
+++ b/tests/src/test/scala/system/basic/WskActivationTests.scala
@@ -17,23 +17,21 @@
 
 package system.basic
 
+import common.rest.WskRestOperations
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-
-import common.{TestHelpers, TestUtils, WskOperations, WskProps, WskTestHelpers}
-
+import common._
 import whisk.utils.retry
 
 import scala.concurrent.duration._
-
 import spray.json._
 import spray.json.DefaultJsonProtocol._
 
 @RunWith(classOf[JUnitRunner])
-abstract class WskActivationTests extends TestHelpers with WskTestHelpers {
-  implicit val wskprops = WskProps()
+class WskActivationTests extends TestHelpers with WskTestHelpers with WskActorSystem {
 
-  val wsk: WskOperations
+  implicit val wskprops = WskProps()
+  val wsk: WskOperations = new WskRestOperations
 
   behavior of "Whisk activations"
 
diff --git a/tests/src/test/scala/system/basic/WskBasicJavaTests.scala b/tests/src/test/scala/system/basic/WskBasicJavaTests.scala
deleted file mode 100644
index ba64d53..0000000
--- a/tests/src/test/scala/system/basic/WskBasicJavaTests.scala
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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 system.basic
-
-import scala.concurrent.duration.DurationInt
-
-import common.TestHelpers
-import common.TestUtils
-import common.TestUtils.ANY_ERROR_EXIT
-import common.WskTestHelpers
-import common.WskProps
-import common.WskOperations
-
-import org.junit.runner.RunWith
-import org.scalatest.Matchers
-import org.scalatest.junit.JUnitRunner
-
-import spray.json.JsString
-
-@RunWith(classOf[JUnitRunner])
-abstract class WskBasicJavaTests extends TestHelpers with WskTestHelpers with Matchers {
-
-  implicit val wskprops = WskProps()
-  val wsk: WskOperations
-  val expectedDuration = 120.seconds
-  val activationPollDuration = 60.seconds
-
-  behavior of "Java Actions"
-
-  /**
-   * Test the Java "hello world" demo sequence
-   */
-  it should "Invoke a java action" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
-    val name = "helloJava"
-    val file = Some(TestUtils.getTestActionFilename("helloJava.jar"))
-
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, file, main = Some("hello.HelloJava"))
-    }
-
-    val start = System.currentTimeMillis()
-    withActivation(wsk.activation, wsk.action.invoke(name), totalWait = activationPollDuration) {
-      _.response.result.get.toString should include("Hello stranger!")
-    }
-
-    withActivation(
-      wsk.activation,
-      wsk.action.invoke(name, Map("name" -> JsString("Sir"))),
-      totalWait = activationPollDuration) {
-      _.response.result.get.toString should include("Hello Sir!")
-    }
-
-    withClue("Test duration exceeds expectation (ms)") {
-      val duration = System.currentTimeMillis() - start
-      duration should be <= expectedDuration.toMillis
-    }
-  }
-
-  /*
-   * Example from the docs.
-   */
-  it should "Invoke a Java action where main is in the default package" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "helloJavaDefaultPkg"
-      val file = Some(TestUtils.getTestActionFilename("helloJavaDefaultPackage.jar"))
-
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, file, main = Some("Hello"))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name, Map()), totalWait = activationPollDuration) {
-        _.response.result.get.toString should include("Hello stranger!")
-      }
-  }
-
-  it should "Ensure that Java actions cannot be created without a specified main method" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "helloJavaWithNoMainSpecified"
-      val file = Some(TestUtils.getTestActionFilename("helloJava.jar"))
-
-      val createResult = assetHelper.withCleaner(wsk.action, name, confirmDelete = false) { (action, _) =>
-        action.create(name, file, expectedExitCode = ANY_ERROR_EXIT)
-      }
-
-      val output = s"${createResult.stdout}\n${createResult.stderr}"
-
-      output should include("main")
-  }
-}
diff --git a/tests/src/test/scala/system/basic/WskBasicNode6Tests.scala b/tests/src/test/scala/system/basic/WskBasicNode6Tests.scala
deleted file mode 100644
index c829b6f..0000000
--- a/tests/src/test/scala/system/basic/WskBasicNode6Tests.scala
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-import common.JsHelpers
-import common.TestHelpers
-import common.TestUtils
-import common.TestUtils.RunResult
-import common.WskOperations
-import common.WskProps
-import common.WskTestHelpers
-import spray.json._
-
-@RunWith(classOf[JUnitRunner])
-abstract class WskBasicNode6Tests extends TestHelpers with WskTestHelpers with JsHelpers {
-
-  implicit val wskprops = WskProps()
-  val wsk: WskOperations
-  val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
-  lazy val currentNodeJsKind = "nodejs:6"
-
-  behavior of s"Runtime $currentNodeJsKind"
-
-  it should "Ensure that NodeJS actions can have a non-default entrypoint" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "niamNpmAction"
-      val file = Some(TestUtils.getTestActionFilename("niam.js"))
-
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, file, main = Some("niam"), kind = Some(currentNodeJsKind))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
-        val response = activation.response
-        response.result.get.fields.get("error") shouldBe empty
-        response.result.get.fields.get("greetings") should be(Some(JsString("Hello from a non-standard entrypoint.")))
-      }
-  }
-
-  it should "Ensure that zipped actions are encoded and uploaded as NodeJS actions" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "zippedNpmAction"
-      val file = Some(TestUtils.getTestActionFilename("zippedaction.zip"))
-
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, file, kind = Some(currentNodeJsKind))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
-        val response = activation.response
-        response.result.get.fields.get("error") shouldBe empty
-        response.result.get.fields.get("author") shouldBe defined
-      }
-  }
-
-  it should "Ensure that returning an empty rejected Promise results in an errored activation" in withAssetCleaner(
-    wskprops) { (wp, assetHelper) =>
-    val name = "jsEmptyRejectPromise"
-
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, Some(TestUtils.getTestActionFilename("issue-1562.js")), kind = Some(currentNodeJsKind))
-    }
-
-    withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
-      val response = activation.response
-      response.success should be(false)
-      response.result.get.fields.get("error") shouldBe defined
-    }
-  }
-
-  def convertRunResultToJsObject(result: RunResult): JsObject = {
-    val stdout = result.stdout
-    val firstNewline = stdout.indexOf("\n")
-    stdout.substring(firstNewline + 1).parseJson.asJsObject
-  }
-}
diff --git a/tests/src/test/scala/system/basic/WskBasicNode8Tests.scala b/tests/src/test/scala/system/basic/WskBasicNode8Tests.scala
deleted file mode 100644
index cbb7725..0000000
--- a/tests/src/test/scala/system/basic/WskBasicNode8Tests.scala
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-abstract class WskBasicNode8Tests extends WskBasicNode6Tests {
-  override lazy val currentNodeJsKind = "nodejs:8"
-}
diff --git a/tests/src/test/scala/system/basic/WskBasicNodeDefaultTests.scala b/tests/src/test/scala/system/basic/WskBasicNodeDefaultTests.scala
deleted file mode 100644
index 0e7ed87..0000000
--- a/tests/src/test/scala/system/basic/WskBasicNodeDefaultTests.scala
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-import common.JsHelpers
-import common.TestHelpers
-import common.TestUtils
-import common.TestUtils.ANY_ERROR_EXIT
-import common.TestUtils.RunResult
-import common.WskOperations
-import common.WskProps
-import common.WskTestHelpers
-import spray.json._
-import spray.json.DefaultJsonProtocol._
-
-@RunWith(classOf[JUnitRunner])
-abstract class WskBasicNodeDefaultTests extends TestHelpers with WskTestHelpers with JsHelpers {
-
-  implicit val wskprops = WskProps()
-  val wsk: WskOperations
-  val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
-  val currentNodeJsDefaultKind = "nodejs:6"
-
-  behavior of "NodeJS default runtime"
-
-  it should "Map a kind of nodejs:default to the current default NodeJS runtime" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "usingDefaultNodeAlias"
-
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, defaultAction, kind = Some("nodejs:default"))
-      }
-
-      val result = wsk.action.get(name)
-      withPrintOnFailure(result) { () =>
-        val action = convertRunResultToJsObject(result)
-        action.getFieldPath("exec", "kind") should be(Some(currentNodeJsDefaultKind.toJson))
-      }
-  }
-
-  it should "Ensure that zipped actions cannot be created without a kind specified" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "zippedNpmActionWithNoKindSpecified"
-      val file = Some(TestUtils.getTestActionFilename("zippedaction.zip"))
-
-      val createResult = assetHelper.withCleaner(wsk.action, name, confirmDelete = false) { (action, _) =>
-        action.create(name, file, expectedExitCode = ANY_ERROR_EXIT)
-      }
-
-      val output = s"${createResult.stdout}\n${createResult.stderr}"
-
-      output should include("kind")
-  }
-
-  it should "Ensure that JS actions created with no explicit kind use the current default NodeJS runtime" in withAssetCleaner(
-    wskprops) { (wp, assetHelper) =>
-    val name = "jsWithNoKindSpecified"
-
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, defaultAction)
-    }
-
-    val result = wsk.action.get(name)
-    withPrintOnFailure(result) { () =>
-      val action = convertRunResultToJsObject(result)
-      action.getFieldPath("exec", "kind") should be(Some(currentNodeJsDefaultKind.toJson))
-    }
-  }
-
-  def convertRunResultToJsObject(result: RunResult): JsObject = {
-    val stdout = result.stdout
-    val firstNewline = stdout.indexOf("\n")
-    stdout.substring(firstNewline + 1).parseJson.asJsObject
-  }
-
-  it should "Ensure that NodeJS actions can have a non-default entrypoint" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "niamNpmAction"
-      val file = Some(TestUtils.getTestActionFilename("niam.js"))
-
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, file, main = Some("niam"))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
-        val response = activation.response
-        response.result.get.fields.get("error") shouldBe empty
-        response.result.get.fields.get("greetings") should be(Some(JsString("Hello from a non-standard entrypoint.")))
-      }
-  }
-
-  it should "Ensure that zipped actions are encoded and uploaded as NodeJS actions" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "zippedNpmAction"
-      val file = Some(TestUtils.getTestActionFilename("zippedaction.zip"))
-
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, file, kind = Some("nodejs:default"))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
-        val response = activation.response
-        response.result.get.fields.get("error") shouldBe empty
-        response.result.get.fields.get("author") shouldBe defined
-      }
-  }
-}
diff --git a/tests/src/test/scala/system/basic/WskBasicPythonTests.scala b/tests/src/test/scala/system/basic/WskBasicPythonTests.scala
deleted file mode 100644
index 61941d0..0000000
--- a/tests/src/test/scala/system/basic/WskBasicPythonTests.scala
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-import org.scalatest.Matchers
-import scala.concurrent.duration.DurationInt
-import spray.json._
-import spray.json.DefaultJsonProtocol.StringJsonFormat
-import common.JsHelpers
-import common.TestHelpers
-import common.TestUtils
-import common.WskOperations
-import common.WskProps
-import common.WskTestHelpers
-import common.WhiskProperties
-
-@RunWith(classOf[JUnitRunner])
-abstract class WskBasicPythonTests extends TestHelpers with WskTestHelpers with Matchers with JsHelpers {
-
-  implicit val wskprops = WskProps()
-  val wsk: WskOperations
-
-  behavior of "Native Python Action"
-
-  it should "invoke an action and get the result" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
-    val name = "basicInvoke"
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, Some(TestUtils.getTestActionFilename("hello.py")))
-    }
-
-    withActivation(wsk.activation, wsk.action.invoke(name, Map("name" -> "Prince".toJson))) {
-      _.response.result.get.toString should include("Prince")
-    }
-  }
-
-  it should "invoke an action with a non-default entry point" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
-    val name = "nonDefaultEntryPoint"
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, Some(TestUtils.getTestActionFilename("niam.py")), main = Some("niam"))
-    }
-
-    withActivation(wsk.activation, wsk.action.invoke(name, Map())) {
-      _.response.result.get.fields.get("greetings") should be(Some(JsString("Hello from a non-standard entrypoint.")))
-    }
-  }
-
-  it should "invoke an action from a zip file with a non-default entry point" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "pythonZipWithNonDefaultEntryPoint"
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(
-          name,
-          Some(TestUtils.getTestActionFilename("python.zip")),
-          main = Some("niam"),
-          kind = Some("python:default"))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name, Map("name" -> "Prince".toJson))) {
-        _.response.result.get shouldBe JsObject("greeting" -> JsString("Hello Prince!"))
-      }
-  }
-
-  Seq("python:2", "python:3").foreach { kind =>
-    it should s"invoke a $kind action" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
-      val name = s"${kind.replace(":", "")}-action"
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, Some(TestUtils.getTestActionFilename("pythonVersion.py")), kind = Some(kind))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name)) {
-        _.response.result.get shouldBe JsObject("version" -> JsNumber(kind.takeRight(1).toInt))
-      }
-    }
-  }
-
-  it should "invoke an action and confirm expected environment is defined" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "stdenv"
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, Some(TestUtils.getTestActionFilename("stdenv.py")))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
-        val result = activation.response.result.get
-        result.fields.get("error") shouldBe empty
-        result.fields.get("auth") shouldBe Some(
-          JsString(WhiskProperties.readAuthKey(WhiskProperties.getAuthFileForTesting)))
-        result.fields.get("edge").toString.trim should not be empty
-      }
-  }
-
-  it should "invoke an invalid action and get error back" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
-    val name = "bad code"
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, Some(TestUtils.getTestActionFilename("malformed.py")))
-    }
-
-    withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
-      activation.response.result.get.fields.get("error") shouldBe Some(
-        JsString("The action failed to generate or locate a binary. See logs for details."))
-      activation.logs.get.mkString("\n") should { not include ("pythonaction.py") and not include ("flask") }
-    }
-  }
-
-  behavior of "Python virtualenv"
-
-  Seq(("python2_virtualenv.zip", "python:2"), ("python3_virtualenv.zip", "python:3")).foreach {
-    case (filename, kind) =>
-      it should s"invoke a zipped $kind action with virtualenv package" in withAssetCleaner(wskprops) {
-        (wp, assetHelper) =>
-          val name = filename
-          val zippedPythonAction = Some(TestUtils.getTestActionFilename(filename))
-
-          assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-            action.create(name, zippedPythonAction, kind = Some(kind))
-          }
-
-          withActivation(wsk.activation, wsk.action.invoke(name), totalWait = 120.seconds) { activation =>
-            val response = activation.response
-            response.result.get.fields.get("error") shouldBe empty
-            response.result.get.fields.get("Networkinfo: ") shouldBe defined
-          }
-      }
-  }
-}
diff --git a/tests/src/test/scala/system/basic/WskBasicSwift3Tests.scala b/tests/src/test/scala/system/basic/WskBasicSwift3Tests.scala
deleted file mode 100644
index 174bd29..0000000
--- a/tests/src/test/scala/system/basic/WskBasicSwift3Tests.scala
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-import scala.concurrent.duration.DurationInt
-import common.JsHelpers
-import common.TestHelpers
-import common.TestUtils
-import common.WskOperations
-import common.WskProps
-import common.WskTestHelpers
-import spray.json._
-import common.TestUtils.RunResult
-
-@RunWith(classOf[JUnitRunner])
-abstract class WskBasicSwift3Tests extends TestHelpers with WskTestHelpers with JsHelpers {
-
-  implicit val wskprops: common.WskProps = WskProps()
-  val wsk: WskOperations
-  val defaultAction: Some[String] = Some(TestUtils.getTestActionFilename("hello.swift"))
-  lazy val actionKind = "swift:3.1.1"
-  val activationMaxDuration = 2.minutes
-  val activationPollDuration = 3.minutes
-
-  behavior of "Swift runtime"
-
-  it should "Ensure that Swift actions can have a non-default entrypoint" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "niamSwiftAction"
-      val file = Some(TestUtils.getTestActionFilename("niam.swift"))
-
-      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-        action.create(name, file, main = Some("niam"), kind = Some(actionKind), timeout = Some(activationMaxDuration))
-      }
-
-      withActivation(wsk.activation, wsk.action.invoke(name), totalWait = activationPollDuration) { activation =>
-        val response = activation.response
-        response.result.get.fields.get("error") shouldBe empty
-        response.result.get.fields.get("greetings") should be(Some(JsString("Hello from a non-standard entrypoint.")))
-      }
-  }
-
-  def convertRunResultToJsObject(result: RunResult): JsObject = {
-    val stdout = result.stdout
-    val firstNewline = stdout.indexOf("\n")
-    stdout.substring(firstNewline + 1).parseJson.asJsObject
-  }
-}
diff --git a/tests/src/test/scala/system/basic/WskConductorTests.scala b/tests/src/test/scala/system/basic/WskConductorTests.scala
index 22ba657..7302e85 100644
--- a/tests/src/test/scala/system/basic/WskConductorTests.scala
+++ b/tests/src/test/scala/system/basic/WskConductorTests.scala
@@ -19,31 +19,22 @@ package system.basic
 
 import scala.concurrent.duration.DurationInt
 import scala.language.postfixOps
-
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-
-import common.ActivationResult
-import common.StreamLogging
-import common.JsHelpers
-import common.TestHelpers
-import common.TestUtils
-import common.WskOperations
-import common.WskProps
-import common.WskTestHelpers
-
+import common._
+import common.rest.WskRestOperations
 import spray.json._
 import spray.json.DefaultJsonProtocol._
-
 import whisk.core.entity.size.SizeInt
 import whisk.core.WhiskConfig
 import whisk.http.Messages._
 
 @RunWith(classOf[JUnitRunner])
-abstract class WskConductorTests extends TestHelpers with WskTestHelpers with JsHelpers with StreamLogging {
+class WskConductorTests extends TestHelpers with WskTestHelpers with JsHelpers with StreamLogging with WskActorSystem {
 
   implicit val wskprops = WskProps()
-  val wsk: WskOperations
+  val wsk: WskOperations = new WskRestOperations
+
   val allowedActionDuration = 120 seconds
 
   val testString = "this is a test"
diff --git a/tests/src/test/scala/system/basic/WskPackageTests.scala b/tests/src/test/scala/system/basic/WskPackageTests.scala
index 3b3c76b..bcfd836 100644
--- a/tests/src/test/scala/system/basic/WskPackageTests.scala
+++ b/tests/src/test/scala/system/basic/WskPackageTests.scala
@@ -18,26 +18,24 @@
 package system.basic
 
 import java.util.Date
+
 import scala.language.postfixOps
 import scala.collection.mutable.HashMap
 import scala.concurrent.duration.DurationInt
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-import common.TestUtils
-import common.WskOperations
-import common.WskProps
+import common._
 import spray.json._
 import spray.json.DefaultJsonProtocol.StringJsonFormat
-import common.TestHelpers
-import common.WskTestHelpers
-import common.TestHelpers
 import common.WskProps
+import common.rest.WskRestOperations
 
 @RunWith(classOf[JUnitRunner])
-abstract class WskPackageTests extends TestHelpers with WskTestHelpers {
+class WskPackageTests extends TestHelpers with WskTestHelpers with WskActorSystem {
 
   implicit val wskprops = WskProps()
-  val wsk: WskOperations
+  val wsk: WskOperations = new WskRestOperations
+
   val LOG_DELAY = 80 seconds
 
   behavior of "Wsk Package"
diff --git a/tests/src/test/scala/system/basic/WskRestActionTests.scala b/tests/src/test/scala/system/basic/WskRestActionTests.scala
deleted file mode 100644
index be41abf..0000000
--- a/tests/src/test/scala/system/basic/WskRestActionTests.scala
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 system.basic
-
-import common.{TestUtils, WskActorSystem}
-import common.rest.WskRestOperations
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-import spray.json._
-
-@RunWith(classOf[JUnitRunner])
-class WskRestActionTests extends WskActionTests with WskActorSystem {
-  override val wsk = new WskRestOperations
-
-  it should "create an action with an empty file" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
-    val name = "empty"
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, Some(TestUtils.getTestActionFilename("empty.js")))
-    }
-    val rr = wsk.action.get(name)
-    wsk.parseJsonString(rr.stdout).getFieldPath("exec", "code") shouldBe Some(JsString(""))
-  }
-}
diff --git a/tests/src/test/scala/system/basic/WskRestActivationTests.scala b/tests/src/test/scala/system/basic/WskRestActivationTests.scala
deleted file mode 100644
index 86cd831..0000000
--- a/tests/src/test/scala/system/basic/WskRestActivationTests.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-@RunWith(classOf[JUnitRunner])
-class WskRestActivationTests extends WskActivationTests with WskActorSystem {
-  override val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/system/basic/WskRestBasicJavaTests.scala b/tests/src/test/scala/system/basic/WskRestBasicJavaTests.scala
deleted file mode 100644
index 4567be2..0000000
--- a/tests/src/test/scala/system/basic/WskRestBasicJavaTests.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 system.basic
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestBasicJavaTests extends WskBasicJavaTests with WskActorSystem {
-  override val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/system/basic/WskRestBasicNode6Tests.scala b/tests/src/test/scala/system/basic/WskRestBasicNode6Tests.scala
deleted file mode 100644
index 8eb0cd6..0000000
--- a/tests/src/test/scala/system/basic/WskRestBasicNode6Tests.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 system.basic
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestBasicNode6Tests extends WskBasicNode6Tests with WskActorSystem {
-  override val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/system/basic/WskRestBasicNode8Tests.scala b/tests/src/test/scala/system/basic/WskRestBasicNode8Tests.scala
deleted file mode 100644
index ca1ee84..0000000
--- a/tests/src/test/scala/system/basic/WskRestBasicNode8Tests.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 system.basic
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestBasicNode8Tests extends WskBasicNode8Tests with WskActorSystem {
-  override val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/system/basic/WskRestBasicNodeDefaultTests.scala b/tests/src/test/scala/system/basic/WskRestBasicNodeDefaultTests.scala
deleted file mode 100644
index ed5b3dd..0000000
--- a/tests/src/test/scala/system/basic/WskRestBasicNodeDefaultTests.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 system.basic
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestBasicNodeDefaultTests extends WskBasicNodeDefaultTests with WskActorSystem {
-  override val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/system/basic/WskRestBasicPythonTests.scala b/tests/src/test/scala/system/basic/WskRestBasicPythonTests.scala
deleted file mode 100644
index 8a65b9c..0000000
--- a/tests/src/test/scala/system/basic/WskRestBasicPythonTests.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 system.basic
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestBasicPythonTests extends WskBasicPythonTests with WskActorSystem {
-  override val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/system/basic/WskRestBasicSwift311Tests.scala b/tests/src/test/scala/system/basic/WskRestBasicSwift311Tests.scala
deleted file mode 100644
index 92161b0..0000000
--- a/tests/src/test/scala/system/basic/WskRestBasicSwift311Tests.scala
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * 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 system.basic
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestBasicSwift311Tests extends WskBasicSwift3Tests with WskActorSystem {
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "swift:3.1.1"
-}
diff --git a/tests/src/test/scala/system/basic/WskRestBasicSwift41Tests.scala b/tests/src/test/scala/system/basic/WskRestBasicSwift41Tests.scala
deleted file mode 100644
index c6a7908..0000000
--- a/tests/src/test/scala/system/basic/WskRestBasicSwift41Tests.scala
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 system.basic
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestBasicSwift41Tests extends WskBasicSwift3Tests with WskActorSystem {
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "swift:4.1"
-
-}
diff --git a/tests/src/test/scala/system/basic/WskRestBasicTests.scala b/tests/src/test/scala/system/basic/WskRestBasicTests.scala
index 6932ee6..6f6a987 100644
--- a/tests/src/test/scala/system/basic/WskRestBasicTests.scala
+++ b/tests/src/test/scala/system/basic/WskRestBasicTests.scala
@@ -22,22 +22,14 @@ import akka.http.scaladsl.model.StatusCodes.BadGateway
 import akka.http.scaladsl.model.StatusCodes.Conflict
 import akka.http.scaladsl.model.StatusCodes.Unauthorized
 import akka.http.scaladsl.model.StatusCodes.NotFound
-
 import java.time.Instant
 
 import scala.concurrent.duration.DurationInt
-
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-
-import common.TestHelpers
-import common.TestUtils
+import common._
 import common.rest.WskRestOperations
 import common.rest.RestResult
-import common.WskProps
-import common.WskTestHelpers
-import common.WskActorSystem
-
 import spray.json._
 import spray.json.DefaultJsonProtocol._
 import whisk.http.Messages
@@ -47,6 +39,7 @@ class WskRestBasicTests extends TestHelpers with WskTestHelpers with WskActorSys
 
   implicit val wskprops = WskProps()
   val wsk = new WskRestOperations
+
   val defaultAction: Some[String] = Some(TestUtils.getTestActionFilename("hello.js"))
 
   /**
diff --git a/tests/src/test/scala/system/basic/WskRestConductorTests.scala b/tests/src/test/scala/system/basic/WskRestConductorTests.scala
deleted file mode 100644
index a739bed..0000000
--- a/tests/src/test/scala/system/basic/WskRestConductorTests.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-@RunWith(classOf[JUnitRunner])
-class WskRestConductorTests extends WskConductorTests with WskActorSystem {
-  override val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/system/basic/WskRestPackageTests.scala b/tests/src/test/scala/system/basic/WskRestPackageTests.scala
deleted file mode 100644
index d0d6d51..0000000
--- a/tests/src/test/scala/system/basic/WskRestPackageTests.scala
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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 system.basic
-
-import common.WskActorSystem
-import common.rest.WskRestOperations
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestPackageTests extends WskPackageTests with WskActorSystem {
-  override val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/system/basic/WskRestSequenceTests.scala b/tests/src/test/scala/system/basic/WskRestSequenceTests.scala
deleted file mode 100644
index 5d5c8b5..0000000
--- a/tests/src/test/scala/system/basic/WskRestSequenceTests.scala
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-import common.rest.WskRestOperations
-import whisk.core.WhiskConfig
-
-/**
- * Tests sequence execution
- */
-
-@RunWith(classOf[JUnitRunner])
-class WskRestSequenceTests extends WskSequenceTests {
-  override val wsk: common.rest.WskRestOperations = new WskRestOperations
-  override val whiskConfig = new WhiskConfig(Map(WhiskConfig.actionSequenceMaxLimit -> null))
-  assert(whiskConfig.isValid)
-}
diff --git a/tests/src/test/scala/system/basic/WskRestUnicodeJavaTests.scala b/tests/src/test/scala/system/basic/WskRestUnicodeJavaTests.scala
deleted file mode 100644
index bb9ddb7..0000000
--- a/tests/src/test/scala/system/basic/WskRestUnicodeJavaTests.scala
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-import common.JsHelpers
-import common.WskTestHelpers
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-@RunWith(classOf[JUnitRunner])
-class WskRestUnicodeJavaTests extends WskUnicodeTests with WskTestHelpers with WskActorSystem with JsHelpers {
-
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "java"
-  override lazy val actionSource = "unicode.jar"
-
-}
diff --git a/tests/src/test/scala/system/basic/WskRestUnicodeNode6Tests.scala b/tests/src/test/scala/system/basic/WskRestUnicodeNode6Tests.scala
deleted file mode 100644
index c20de9d..0000000
--- a/tests/src/test/scala/system/basic/WskRestUnicodeNode6Tests.scala
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-import common.JsHelpers
-import common.WskTestHelpers
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-@RunWith(classOf[JUnitRunner])
-class WskRestUnicodeNode6Tests extends WskUnicodeTests with WskTestHelpers with WskActorSystem with JsHelpers {
-
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "nodejs:6"
-  override lazy val actionSource = "unicode.js"
-
-}
diff --git a/tests/src/test/scala/system/basic/WskRestUnicodeNode8Tests.scala b/tests/src/test/scala/system/basic/WskRestUnicodeNode8Tests.scala
deleted file mode 100644
index c788757..0000000
--- a/tests/src/test/scala/system/basic/WskRestUnicodeNode8Tests.scala
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-import common.JsHelpers
-import common.WskTestHelpers
-import common.rest.WskRestOperations
-import common.WskActorSystem
-
-@RunWith(classOf[JUnitRunner])
-class WskRestUnicodeNode8Tests extends WskUnicodeTests with WskTestHelpers with WskActorSystem with JsHelpers {
-
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "nodejs:8"
-  override lazy val actionSource = "unicode.js"
-
-}
diff --git a/tests/src/test/scala/system/basic/WskRestUnicodePython2Tests.scala b/tests/src/test/scala/system/basic/WskRestUnicodePython2Tests.scala
deleted file mode 100644
index 0d58ca1..0000000
--- a/tests/src/test/scala/system/basic/WskRestUnicodePython2Tests.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-import common.{JsHelpers, WskActorSystem, WskTestHelpers}
-import common.rest.WskRestOperations
-
-@RunWith(classOf[JUnitRunner])
-class WskRestUnicodePython2Tests extends WskUnicodeTests with WskTestHelpers with WskActorSystem with JsHelpers {
-
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "python:2"
-  override lazy val actionSource = "unicode2.py"
-
-}
diff --git a/tests/src/test/scala/system/basic/WskRestUnicodePython3Tests.scala b/tests/src/test/scala/system/basic/WskRestUnicodePython3Tests.scala
deleted file mode 100644
index 287baff..0000000
--- a/tests/src/test/scala/system/basic/WskRestUnicodePython3Tests.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-import common.{JsHelpers, WskActorSystem, WskTestHelpers}
-import common.rest.WskRestOperations
-
-@RunWith(classOf[JUnitRunner])
-class WskRestUnicodePython3Tests extends WskUnicodeTests with WskTestHelpers with WskActorSystem with JsHelpers {
-
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "python:3"
-  override lazy val actionSource = "unicode3.py"
-
-}
diff --git a/tests/src/test/scala/system/basic/WskRestUnicodeSwift311Tests.scala b/tests/src/test/scala/system/basic/WskRestUnicodeSwift311Tests.scala
deleted file mode 100644
index 68a7928..0000000
--- a/tests/src/test/scala/system/basic/WskRestUnicodeSwift311Tests.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 system.basic
-
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-import common.{JsHelpers, WskActorSystem, WskTestHelpers}
-import common.rest.WskRestOperations
-
-@RunWith(classOf[JUnitRunner])
-class WskRestUnicodeSwift311Tests extends WskUnicodeTests with WskTestHelpers with WskActorSystem with JsHelpers {
-
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "swift:3.1.1"
-  override lazy val actionSource = "unicode.swift"
-
-}
diff --git a/tests/src/test/scala/system/basic/WskRestUnicodeSwift41Tests.scala b/tests/src/test/scala/system/basic/WskRestUnicodeSwift41Tests.scala
deleted file mode 100644
index 51577c1..0000000
--- a/tests/src/test/scala/system/basic/WskRestUnicodeSwift41Tests.scala
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 system.basic
-
-import common.{JsHelpers, WskActorSystem, WskTestHelpers}
-import common.rest.WskRestOperations
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-
-@RunWith(classOf[JUnitRunner])
-class WskRestUnicodeSwift41Tests extends WskUnicodeTests with WskTestHelpers with WskActorSystem with JsHelpers {
-
-  override val wsk = new WskRestOperations
-  override lazy val actionKind = "swift:4.1"
-  override lazy val actionSource = "unicode.swift"
-
-}
diff --git a/tests/src/test/scala/system/basic/WskSequenceTests.scala b/tests/src/test/scala/system/basic/WskSequenceTests.scala
index 5b9c509..b264acd 100644
--- a/tests/src/test/scala/system/basic/WskSequenceTests.scala
+++ b/tests/src/test/scala/system/basic/WskSequenceTests.scala
@@ -23,25 +23,13 @@ import java.util.Date
 import scala.concurrent.duration.DurationInt
 import scala.language.postfixOps
 import scala.util.matching.Regex
-
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-
-import common.ActivationResult
-import common.StreamLogging
-import common.TestHelpers
-import common.TestUtils
+import common._
 import common.TestUtils._
-import common.WskOperations
-import common.WskProps
-import common.RuleActivationResult
-import common.WskTestHelpers
-
-import akka.http.scaladsl.testkit.ScalatestRouteTest
-
+import common.rest.WskRestOperations
 import spray.json._
 import spray.json.DefaultJsonProtocol._
-
 import whisk.core.WhiskConfig
 import whisk.http.Messages.sequenceIsTooLong
 
@@ -49,14 +37,15 @@ import whisk.http.Messages.sequenceIsTooLong
  * Tests sequence execution
  */
 @RunWith(classOf[JUnitRunner])
-abstract class WskSequenceTests extends TestHelpers with ScalatestRouteTest with WskTestHelpers with StreamLogging {
+class WskSequenceTests extends TestHelpers with WskTestHelpers with StreamLogging with WskActorSystem {
 
   implicit val wskprops = WskProps()
-  val wsk: WskOperations
+  val wsk: WskOperations = new WskRestOperations
+  val whiskConfig = new WhiskConfig(Map(WhiskConfig.actionSequenceMaxLimit -> null))
   val allowedActionDuration = 120 seconds
   val shortDuration = 10 seconds
 
-  val whiskConfig: WhiskConfig
+  assert(whiskConfig.isValid)
 
   behavior of "Wsk Sequence"
 
diff --git a/tests/src/test/scala/system/basic/WskUnicodeTests.scala b/tests/src/test/scala/system/basic/WskUnicodeTests.scala
index 13f9376..f425418 100644
--- a/tests/src/test/scala/system/basic/WskUnicodeTests.scala
+++ b/tests/src/test/scala/system/basic/WskUnicodeTests.scala
@@ -17,50 +17,87 @@
 
 package system.basic
 
+import java.io.File
+import com.jayway.restassured.RestAssured
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
+
 import scala.concurrent.duration.DurationInt
-import common.JsHelpers
-import common.TestHelpers
-import common.TestUtils
-import common.WskOperations
-import common.WskProps
-import common.WskTestHelpers
+import common._
+import common.rest.WskRestOperations
 import spray.json._
+import system.rest.RestUtil
 
 @RunWith(classOf[JUnitRunner])
-abstract class WskUnicodeTests extends TestHelpers with WskTestHelpers with JsHelpers {
-
-  val actionKind: String
-  val actionSource: String
+class WskUnicodeTests extends TestHelpers with WskTestHelpers with JsHelpers with WskActorSystem with RestUtil {
 
   implicit val wskprops: common.WskProps = WskProps()
-  val wsk: WskOperations
+  val wsk: WskOperations = new WskRestOperations
+
   val activationMaxDuration = 2.minutes
   val activationPollDuration = 3.minutes
 
-  s"$actionKind action" should "Ensure that UTF-8 in supported in source files, input params, logs, and output results" in withAssetCleaner(
-    wskprops) { (wp, assetHelper) =>
-    val name = s"unicodeGalore.${actionKind.replace(":", "")}"
-
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(
-        name,
-        Some(TestUtils.getTestActionFilename(actionSource)),
-        main = if (actionKind == "java") Some("Unicode") else None,
-        kind = Some(actionKind),
-        timeout = Some(activationMaxDuration))
+  import WskUnicodeTests._
+
+  val actionKinds: Iterable[Kind] = {
+    val response = RestAssured.given.config(sslconfig).get(getServiceURL)
+    response.statusCode should be(200)
+
+    val mf = response.body.asString.parseJson.asJsObject.fields("runtimes").asJsObject
+    mf.fields.values.map(_.convertTo[Vector[Kind]]).flatten.filter(!_.deprecated)
+  }
+
+  println(s"Kinds to test: ${actionKinds.map(_.kind).mkString(", ")}")
+
+  def main(kind: String): Option[String] = {
+    kind match {
+      case "java" => Some("Unicode")
+      case _      => None
     }
+  }
+
+  def getFileLocation(kind: String): Option[String] = {
+    // the test file is either named kind.txt or kind.bin
+    // one of the two must exist otherwise, fail the test.
+    val prefix = "unicode.tests" + File.separator + kind
+    val txt = new File(TestUtils.getTestActionFilename(s"$prefix.txt"))
+    val bin = new File(TestUtils.getTestActionFilename(s"$prefix.bin"))
+    if (txt.exists) Some(txt.toString)
+    else if (bin.exists) Some(bin.toString)
+    else throw new IllegalStateException(s"did not find text or binary action for kind $kind")
+  }
+
+  actionKinds.foreach { k =>
+    val actionKind = k.kind
 
-    withActivation(
-      wsk.activation,
-      wsk.action.invoke(name, parameters = Map("delimiter" -> JsString("❄"))),
-      totalWait = activationPollDuration) { activation =>
-      val response = activation.response
-      response.result.get.fields.get("error") shouldBe empty
-      response.result.get.fields.get("winter") should be(Some(JsString("❄ ☃ ❄")))
+    s"$actionKind action" should "Ensure that UTF-8 in supported in source files, input params, logs, and output results" in withAssetCleaner(
+      wskprops) { (wp, assetHelper) =>
+      val name = s"unicodeGalore.${actionKind.replace(":", "")}"
 
-      activation.logs.toList.flatten.mkString(" ") should include("❄ ☃ ❄")
+      assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+        action.create(
+          name,
+          getFileLocation(actionKind),
+          main = main(actionKind),
+          kind = Some(actionKind),
+          timeout = Some(activationMaxDuration))
+      }
+
+      withActivation(
+        wsk.activation,
+        wsk.action.invoke(name, parameters = Map("delimiter" -> JsString("❄"))),
+        totalWait = activationPollDuration) { activation =>
+        val response = activation.response
+        response.result.get.fields.get("error") shouldBe empty
+        response.result.get.fields.get("winter") should be(Some(JsString("❄ ☃ ❄")))
+
+        activation.logs.toList.flatten.mkString(" ") should include("❄ ☃ ❄")
+      }
     }
   }
 }
+
+protected[basic] object WskUnicodeTests extends DefaultJsonProtocol {
+  case class Kind(kind: String, deprecated: Boolean)
+  implicit val serdes: RootJsonFormat[Kind] = jsonFormat2(Kind)
+}
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskRestBasicUsageTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskRestBasicUsageTests.scala
index 27364f7..2320908 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskRestBasicUsageTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskRestBasicUsageTests.scala
@@ -109,17 +109,6 @@ class WskRestBasicUsageTests extends TestHelpers with WskTestHelpers with WskAct
     }
   }
 
-  it should "reject action update for sequence with no components" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
-    val name = "updateMissingComponents"
-    val file = Some(TestUtils.getTestActionFilename("hello.js"))
-    assetHelper.withCleaner(wsk.action, name) { (action, name) =>
-      action.create(name, file)
-    }
-    wsk.action
-      .create(name, None, update = true, kind = Some("sequence"), expectedExitCode = BadRequest.intValue)
-      .stderr should include("The request content was malformed:\n'components' must be defined for sequence kind")
-  }
-
   it should "create, and get an action to verify parameter and annotation parsing" in withAssetCleaner(wskprops) {
     (wp, assetHelper) =>
       val name = "actionAnnotations"
@@ -282,29 +271,6 @@ class WskRestBasicUsageTests extends TestHelpers with WskTestHelpers with WskAct
       }
   }
 
-  it should "report error when creating an action with unknown kind" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val rr = assetHelper.withCleaner(wsk.action, "invalid kind", confirmDelete = false) { (action, name) =>
-        action.create(
-          name,
-          Some(TestUtils.getTestActionFilename("echo.js")),
-          kind = Some("foobar"),
-          expectedExitCode = BadRequest.intValue)
-      }
-      rr.stderr should include("kind 'foobar' not in Set")
-  }
-
-  it should "report error when creating an action with zip but without kind" in withAssetCleaner(wskprops) {
-    (wp, assetHelper) =>
-      val name = "zipWithNoKind"
-      val zippedPythonAction = Some(TestUtils.getTestActionFilename("python.zip"))
-      val createResult = assetHelper.withCleaner(wsk.action, name, confirmDelete = false) { (action, _) =>
-        action.create(name, zippedPythonAction, expectedExitCode = ANY_ERROR_EXIT)
-      }
-
-      createResult.stderr should include("kind '' not in Set")
-  }
-
   it should "create, and invoke an action that utilizes an invalid docker container with appropriate error" in withAssetCleaner(
     wskprops) {
     val name = "invalidDockerContainer"
@@ -443,47 +409,6 @@ class WskRestBasicUsageTests extends TestHelpers with WskTestHelpers with WskAct
     }
   }
 
-  it should "ensure action update with --web flag only copies existing annotations when new annotations are not provided" in withAssetCleaner(
-    wskprops) { (wp, assetHelper) =>
-    val name = "webaction"
-    val file = Some(TestUtils.getTestActionFilename("echo.js"))
-    val createKey = "createKey"
-    val createValue = JsString("createValue")
-    val updateKey = "updateKey"
-    val updateValue = JsString("updateValue")
-    val origKey = "origKey"
-    val origValue = JsString("origValue")
-    val overwrittenValue = JsString("overwrittenValue")
-    val createAnnots = Map(createKey -> createValue, origKey -> origValue)
-    val updateAnnots = Map(updateKey -> updateValue, origKey -> overwrittenValue)
-
-    assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, file, annotations = createAnnots)
-    }
-
-    wsk.action.create(name, file, web = Some("true"), update = true)
-
-    val action = wsk.action.get(name)
-    action.getFieldJsValue("annotations") shouldBe JsArray(
-      JsObject("key" -> JsString("web-export"), "value" -> JsBoolean(true)),
-      JsObject("key" -> JsString(origKey), "value" -> origValue),
-      JsObject("key" -> JsString("raw-http"), "value" -> JsBoolean(false)),
-      JsObject("key" -> JsString("final"), "value" -> JsBoolean(true)),
-      JsObject("key" -> JsString(createKey), "value" -> createValue),
-      JsObject("key" -> JsString("exec"), "value" -> JsString("nodejs:6")))
-
-    wsk.action.create(name, file, web = Some("true"), update = true, annotations = updateAnnots)
-
-    val updatedAction = wsk.action.get(name)
-    updatedAction.getFieldJsValue("annotations") shouldBe JsArray(
-      JsObject("key" -> JsString("web-export"), "value" -> JsBoolean(true)),
-      JsObject("key" -> JsString(origKey), "value" -> overwrittenValue),
-      JsObject("key" -> JsString(updateKey), "value" -> updateValue),
-      JsObject("key" -> JsString("raw-http"), "value" -> JsBoolean(false)),
-      JsObject("key" -> JsString("final"), "value" -> JsBoolean(true)),
-      JsObject("key" -> JsString("exec"), "value" -> JsString("nodejs:6")))
-  }
-
   it should "ensure action update creates an action with --web flag" in withAssetCleaner(wskprops) {
     (wp, assetHelper) =>
       val name = "webaction"
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskRestWebActionsTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskRestWebActionsTests.scala
deleted file mode 100644
index 52c7583..0000000
--- a/tests/src/test/scala/whisk/core/cli/test/WskRestWebActionsTests.scala
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 whisk.core.cli.test
-
-import common.WskActorSystem
-import org.junit.runner.RunWith
-import org.scalatest.junit.JUnitRunner
-import common.rest.WskRestOperations
-
-@RunWith(classOf[JUnitRunner])
-class WskRestWebActionsTests extends WskWebActionsTests with WskActorSystem {
-  override lazy val wsk = new WskRestOperations
-}
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskWebActionsTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskWebActionsTests.scala
index 6abf672..62bef19 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskWebActionsTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskWebActionsTests.scala
@@ -23,17 +23,11 @@ import java.util.Base64
 import scala.util.Failure
 import scala.util.Try
 import org.junit.runner.RunWith
-import org.scalatest.BeforeAndAfterAll
 import org.scalatest.junit.JUnitRunner
 import com.jayway.restassured.RestAssured
 import com.jayway.restassured.response.Header
-import common.TestHelpers
-import common.TestUtils
-import common.WhiskProperties
-import common.WskOperations
-import common.WskProps
-import common.WskTestHelpers
-import common.SimpleExec
+import common._
+import common.rest.WskRestOperations
 import spray.json._
 import spray.json.DefaultJsonProtocol._
 import system.rest.RestUtil
@@ -45,11 +39,11 @@ import whisk.core.entity.Subject
  * Tests web actions.
  */
 @RunWith(classOf[JUnitRunner])
-abstract class WskWebActionsTests extends TestHelpers with WskTestHelpers with RestUtil with BeforeAndAfterAll {
+class WskWebActionsTests extends TestHelpers with WskTestHelpers with RestUtil with WskActorSystem {
   val MAX_URL_LENGTH = 8192 // 8K matching nginx default
 
   private implicit val wskprops = WskProps()
-  val wsk: WskOperations
+  val wsk: WskOperations = new WskRestOperations
   lazy val namespace = wsk.namespace.whois()
 
   protected val testRoutePath: String = "/api/v1/web"
diff --git a/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala b/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala
index 7ed95ca..8eddc51 100644
--- a/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/ActionsApiTests.scala
@@ -21,27 +21,23 @@ import java.time.Instant
 
 import scala.concurrent.duration.DurationInt
 import scala.language.postfixOps
-
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-
 import akka.http.scaladsl.model.StatusCodes._
 import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsonMarshaller
 import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsonUnmarshaller
 import akka.http.scaladsl.server.Route
-
 import spray.json._
 import spray.json.DefaultJsonProtocol._
-
 import whisk.core.controller.WhiskActionsApi
 import whisk.core.entity._
 import whisk.core.entity.size._
 import whisk.core.entitlement.Collection
 import whisk.http.ErrorResponse
 import whisk.http.Messages
-
 import java.io.ByteArrayInputStream
 import java.util.Base64
+
 import akka.stream.scaladsl._
 
 /**
@@ -453,6 +449,19 @@ class ActionsApiTests extends ControllerTestCommon with WhiskActionsApi {
     }
   }
 
+  it should "reject exec with unknown or missing kind" in {
+    implicit val tid = transid()
+    Seq("", "foobar").foreach { kind =>
+      val content = s"""{"exec":{"kind": "$kind", "code":"??"}}""".stripMargin.parseJson.asJsObject
+      Put(s"$collectionPath/${aname()}", content) ~> Route.seal(routes(creds)) ~> check {
+        status should be(BadRequest)
+        responseAs[String] should include {
+          s"kind '$kind' not in Set"
+        }
+      }
+    }
+  }
+
   it should "reject update with exec which is too big" in {
     implicit val tid = transid()
     val oldCode = "function main()"
diff --git a/tests/src/test/scala/whisk/core/controller/test/SequenceApiTests.scala b/tests/src/test/scala/whisk/core/controller/test/SequenceApiTests.scala
index 56af3a8..9784278 100644
--- a/tests/src/test/scala/whisk/core/controller/test/SequenceApiTests.scala
+++ b/tests/src/test/scala/whisk/core/controller/test/SequenceApiTests.scala
@@ -276,6 +276,19 @@ class SequenceApiTests extends ControllerTestCommon with WhiskActionsApi {
     }
   }
 
+  it should "reject create or update of a sequence with no components" in {
+    implicit val tid = transid()
+    val content = JsObject("exec" -> JsObject("kind" -> Exec.SEQUENCE.toJson))
+    Seq(true, false).foreach { overwrite =>
+      Put(s"$collectionPath/${aname()}?overwrite=$overwrite", content) ~> Route.seal(routes(creds)) ~> check {
+        status should be(BadRequest)
+        // the content will fail to deserialize on the route directive,
+        // and without a custom rejection, the response will be a string
+        responseAs[String] shouldBe "The request content was malformed:\n'components' must be defined for sequence kind"
+      }
+    }
+  }
+
   it should "reject update of a sequence with components that don't have at least namespace and action name" in {
     implicit val tid = transid()
     val content = JsObject("exec" -> JsObject("kind" -> Exec.SEQUENCE.toJson, "components" -> Vector("a", "b").toJson))