openwhisk-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From markusthoem...@apache.org
Subject [incubator-openwhisk] branch master updated: Action time limit test refactoring and cleanup. (#3485)
Date Wed, 28 Mar 2018 10:55:54 GMT
This is an automated email from the ASF dual-hosted git repository.

markusthoemmes 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 51f8c18  Action time limit test refactoring and cleanup. (#3485)
51f8c18 is described below

commit 51f8c1820c122cf7353039029c0496e948d32f01
Author: Sven Lange-Last <sven.lange-last@de.ibm.com>
AuthorDate: Wed Mar 28 12:55:49 2018 +0200

    Action time limit test refactoring and cleanup. (#3485)
    
    * Added variations where the requested timeout and memory size limit is slightly above
the maximum allowed limit to detect fuzzy constraint checking. In the past, values were much
higher than allowed for negative tests.
    * Move action creation tests verifying that creation is allowed/rejected depending on
specified limits from `WskBasicUsageTests` suite to `ActionLimitsTests` suite. Said tests
fit better into the limits test suite.
    * Improve automated test naming so that the meaning of limit values can easily be spotted,
i.e. which category it belongs to: minimum, standard, maximum, below minimum, above maximum,
other.
    
    * Before this change, the test used Python and Java actions with a fixed sleep time. With
this approach, the test may fail if the default maximum timeout limit setting is changed.
Introducing new test actions `sleep.jar` (Java including source) and `sleep.py` (Python) which
sleep as many milliseconds as specified during invocation.
    
    * `timeout.js` has been replaced by `sleep.js`.
    * `timedout.py` has been replaced by `sleep.py`.
    * `timedout.jar` has been replaced by `sleep.jar`. `TimedOut.java` is the Java source
file for `timedout.jar`.
    * Use CPU-friendly `sleep.js` action instead of CPU-intensive `timeout.js` action
---
 tests/dat/actions/Sleep.java                       |  52 +++++++
 tests/dat/actions/TimedOut.java                    |  24 ---
 tests/dat/actions/sleep.jar                        | Bin 0 -> 1167 bytes
 tests/dat/actions/sleep.js                         |  40 +++++
 tests/dat/actions/sleep.py                         |  21 +++
 tests/dat/actions/timedout.jar                     | Bin 771 -> 0 bytes
 tests/dat/actions/timedout.py                      |   7 -
 tests/dat/actions/timeout.js                       |  18 ---
 tests/src/test/scala/limits/ThrottleTests.scala    |  12 +-
 .../test/scala/system/basic/WskBasicTests.scala    |  14 +-
 .../test/scala/system/basic/WskSequenceTests.scala |  14 +-
 .../whisk/core/cli/test/WskBasicUsageTests.scala   |  59 --------
 .../whisk/core/limits/ActionLimitsTests.scala      | 161 ++++++++++++++++++---
 .../whisk/core/limits/MaxActionDurationTests.scala |  73 ++++++----
 14 files changed, 319 insertions(+), 176 deletions(-)

diff --git a/tests/dat/actions/Sleep.java b/tests/dat/actions/Sleep.java
new file mode 100644
index 0000000..1799852
--- /dev/null
+++ b/tests/dat/actions/Sleep.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+/**
+ * Build instructions:
+ * - Assumption: the dependency GSON is in the local dicrectory, e.g. "gson-2.8.2.jar"
+ * - Compile with "javac -cp gson-2.8.2.jar Sleep.java"
+ * - Create .jar archive with "jar cvf sleep.jar Sleep.class"
+ */
+
+/**
+ * Java based OpenWhisk action that sleeps for the specified number
+ * of milliseconds before returning.
+ * The function actually sleeps slightly longer than requested.
+ *
+ * @param parm JSON object with Number property sleepTimeInMs
+ * @returns JSON object with String property msg describing how long the function slept
+ */
+
+import com.google.gson.JsonObject;
+public class Sleep {
+    public static JsonObject main(JsonObject parm) throws InterruptedException {
+        int sleepTimeInMs = 1;
+        if (parm.has("sleepTimeInMs")) {
+            sleepTimeInMs = parm.getAsJsonPrimitive("sleepTimeInMs").getAsInt();
+        }
+        System.out.println("Specified sleep time is " + sleepTimeInMs + " ms.");
+
+        final String responseText = "Terminated successfully after around " + sleepTimeInMs
+ " ms.";
+        final JsonObject response = new JsonObject();
+        response.addProperty("msg", responseText);
+
+        Thread.sleep(sleepTimeInMs);
+
+        System.out.println(responseText);
+        return response;
+    }
+}
diff --git a/tests/dat/actions/TimedOut.java b/tests/dat/actions/TimedOut.java
deleted file mode 100644
index 4cfa3dd..0000000
--- a/tests/dat/actions/TimedOut.java
+++ /dev/null
@@ -1,24 +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.
- */
-
-import com.google.gson.JsonObject;
-public class TimedOut {
-    public static JsonObject main(JsonObject args) throws InterruptedException {
-        Thread.sleep(310000);
-        return new JsonObject();
-    }
-}
diff --git a/tests/dat/actions/sleep.jar b/tests/dat/actions/sleep.jar
new file mode 100644
index 0000000..536711b
Binary files /dev/null and b/tests/dat/actions/sleep.jar differ
diff --git a/tests/dat/actions/sleep.js b/tests/dat/actions/sleep.js
new file mode 100644
index 0000000..c6c2e17
--- /dev/null
+++ b/tests/dat/actions/sleep.js
@@ -0,0 +1,40 @@
+/**
+ * Node.js based OpenWhisk action that sleeps for the specified number
+ * of milliseconds before returning. Uses a timer instead of a busy loop.
+ * The function actually sleeps slightly longer than requested.
+ *
+ * @param parm Object with Number property sleepTimeInMs
+ * @returns Object with String property msg describing how long the function slept
+ *          or an error object on failure
+ */
+function main(parm) {
+    if(!('sleepTimeInMs' in parm)) {
+        const result = { error: "Parameter 'sleepTimeInMs' not specified." }
+        console.error(result.error)
+        return result
+    }
+
+    if(!Number.isInteger(parm.sleepTimeInMs)) {
+        const result = { error: "Parameter 'sleepTimeInMs' must be an integer value." }
+        console.error(result.error)
+        return result
+    }
+
+    if((parm.sleepTimeInMs < 0) || !Number.isFinite(parm.sleepTimeInMs)) {
+        const result = { error: "Parameter 'sleepTimeInMs' must be finite, postive integer
value." }
+        console.error(result.error)
+        return result
+    }
+
+    console.log("Specified sleep time is " + parm.sleepTimeInMs + " ms.")
+
+    return new Promise(function(resolve, reject) {
+        const timeBeforeSleep = new Date()
+        setTimeout(function () {
+            const actualSleepTimeInMs = new Date() - timeBeforeSleep
+            const result = { msg: "Terminated successfully after around " + actualSleepTimeInMs
+ " ms." }
+            console.log(result.msg)
+            resolve(result)
+        }, parm.sleepTimeInMs)
+    })
+}
diff --git a/tests/dat/actions/sleep.py b/tests/dat/actions/sleep.py
new file mode 100644
index 0000000..c8b05db
--- /dev/null
+++ b/tests/dat/actions/sleep.py
@@ -0,0 +1,21 @@
+#
+# Python based OpenWhisk action that sleeps for the specified number
+# of milliseconds before returning.
+# The function actually sleeps slightly longer than requested.
+#
+# @param parm Object with Number property sleepTimeInMs
+# @returns Object with String property msg describing how long the function slept
+#
+import sys
+import time
+
+def main(parm):
+    sleepTimeInMs = parm.get("sleepTimeInMs", 1)
+    print("Specified sleep time is {} ms.".format(sleepTimeInMs))
+
+    result = { "msg": "Terminated successfully after around {} ms.".format(sleepTimeInMs)
}
+
+    time.sleep(sleepTimeInMs/1000.0)
+
+    print(result['msg'])
+    return result
diff --git a/tests/dat/actions/timedout.jar b/tests/dat/actions/timedout.jar
deleted file mode 100644
index 4d8978e..0000000
Binary files a/tests/dat/actions/timedout.jar and /dev/null differ
diff --git a/tests/dat/actions/timedout.py b/tests/dat/actions/timedout.py
deleted file mode 100644
index 87d4785..0000000
--- a/tests/dat/actions/timedout.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""Python timeout test."""
-import time
-
-
-def main(dict):
-    """Main."""
-    time.sleep(310)
diff --git a/tests/dat/actions/timeout.js b/tests/dat/actions/timeout.js
deleted file mode 100644
index f3bc74b..0000000
--- a/tests/dat/actions/timeout.js
+++ /dev/null
@@ -1,18 +0,0 @@
-function sleep(milliseconds) {
-  var start = new Date().getTime();
-  while (true) {
-    var now = new Date().getTime();
-    if ((now - start) > milliseconds){
-      break;
-    }
-  }
-}
-
-function main(msg) {
-    console.log('dosTimeout', 'timeout ' + msg.payload+'');
-    var n = msg.payload;
-    sleep(n);
-    var res = "[OK] message terminated successfully after " + msg.payload + " milliseconds.";
-    console.log('dosTimeout', 'result:', res);
-    return {msg: res};
-}
diff --git a/tests/src/test/scala/limits/ThrottleTests.scala b/tests/src/test/scala/limits/ThrottleTests.scala
index efeb909..f132995 100644
--- a/tests/src/test/scala/limits/ThrottleTests.scala
+++ b/tests/src/test/scala/limits/ThrottleTests.scala
@@ -235,7 +235,7 @@ class ThrottleTests
   it should "throttle 'concurrent' activations of one action" in withAssetCleaner(wskprops)
{ (wp, assetHelper) =>
     val name = "checkConcurrentActionThrottle"
     assetHelper.withCleaner(wsk.action, name) {
-      val timeoutAction = Some(TestUtils.getTestActionFilename("timeout.js"))
+      val timeoutAction = Some(TestUtils.getTestActionFilename("sleep.js"))
       (action, _) =>
         action.create(name, timeoutAction)
     }
@@ -252,7 +252,10 @@ class ThrottleTests
     // These invokes will stay active long enough that all are issued and load balancer has
recognized concurrency.
     val startSlowInvokes = Instant.now
     val slowResults = untilThrottled(slowInvokes) { () =>
-      wsk.action.invoke(name, Map("payload" -> slowInvokeDuration.toMillis.toJson), expectedExitCode
= DONTCARE_EXIT)
+      wsk.action.invoke(
+        name,
+        Map("sleepTimeInMs" -> slowInvokeDuration.toMillis.toJson),
+        expectedExitCode = DONTCARE_EXIT)
     }
     val afterSlowInvokes = Instant.now
     val slowIssueDuration = durationBetween(startSlowInvokes, afterSlowInvokes)
@@ -266,7 +269,10 @@ class ThrottleTests
     // These fast invokes will trigger the concurrency-based throttling.
     val startFastInvokes = Instant.now
     val fastResults = untilThrottled(fastInvokes) { () =>
-      wsk.action.invoke(name, Map("payload" -> slowInvokeDuration.toMillis.toJson), expectedExitCode
= DONTCARE_EXIT)
+      wsk.action.invoke(
+        name,
+        Map("sleepTimeInMs" -> fastInvokeDuration.toMillis.toJson),
+        expectedExitCode = DONTCARE_EXIT)
     }
     val afterFastInvokes = Instant.now
     val fastIssueDuration = durationBetween(afterFastInvokes, startFastInvokes)
diff --git a/tests/src/test/scala/system/basic/WskBasicTests.scala b/tests/src/test/scala/system/basic/WskBasicTests.scala
index 9d9b0d6..6584a1c 100644
--- a/tests/src/test/scala/system/basic/WskBasicTests.scala
+++ b/tests/src/test/scala/system/basic/WskBasicTests.scala
@@ -26,7 +26,6 @@ import akka.http.scaladsl.model.StatusCodes.NotFound
 import java.time.Instant
 
 import scala.concurrent.duration.DurationInt
-import scala.language.postfixOps
 
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
@@ -482,11 +481,16 @@ class WskBasicTests extends TestHelpers with WskTestHelpers {
 
   it should "create, and invoke an action that times out to ensure the proper response is
received" in withAssetCleaner(
     wskprops) { (wp, assetHelper) =>
-    val name = "sleepAction"
-    val params = Map("payload" -> "100000".toJson)
-    val allowedActionDuration = 120 seconds
+    val name = "sleepAction-" + System.currentTimeMillis()
+    // Must be larger than 60 seconds to see the expected exit code
+    val allowedActionDuration = 120.seconds
+    // Sleep time must be larger than 60 seconds to see the expected exit code
+    // Set sleep time to a value smaller than allowedActionDuration to not raise a timeout
+    val sleepTime = allowedActionDuration - 20.seconds
+    sleepTime should be >= 60.seconds
+    val params = Map("sleepTimeInMs" -> sleepTime.toMillis.toJson)
     val res = assetHelper.withCleaner(wsk.action, name) { (action, _) =>
-      action.create(name, Some(TestUtils.getTestActionFilename("timeout.js")), timeout =
Some(allowedActionDuration))
+      action.create(name, Some(TestUtils.getTestActionFilename("sleep.js")), timeout = Some(allowedActionDuration))
       action.invoke(name, parameters = params, result = true, expectedExitCode = Accepted.intValue)
     }
   }
diff --git a/tests/src/test/scala/system/basic/WskSequenceTests.scala b/tests/src/test/scala/system/basic/WskSequenceTests.scala
index f7dc39d..ca9ad0e 100644
--- a/tests/src/test/scala/system/basic/WskSequenceTests.scala
+++ b/tests/src/test/scala/system/basic/WskSequenceTests.scala
@@ -388,7 +388,7 @@ abstract class WskSequenceTests extends TestHelpers with ScalatestRouteTest
with
   it should "execute a sequence in blocking fashion and finish execution even if longer than
blocking response timeout" in withAssetCleaner(
     wskprops) { (wp, assetHelper) =>
     val sName = "sSequence"
-    val sleep = "timeout"
+    val sleep = "sleep"
     val echo = "echo"
 
     // create actions
@@ -403,22 +403,18 @@ abstract class WskSequenceTests extends TestHelpers with ScalatestRouteTest
with
     assetHelper.withCleaner(wsk.action, sName) { (action, seqName) =>
       action.create(seqName, artifact = Some(actions.mkString(",")), kind = Some("sequence"))
     }
-    // run sequence s with sleep equal to payload
-    val payload = 65000
+    // run sequence s with sleep time
+    val sleepTime = 90 seconds
     val run = wsk.action.invoke(
       sName,
-      parameters = Map("payload" -> JsNumber(payload)),
+      parameters = Map("sleepTimeInMs" -> sleepTime.toMillis.toJson),
       blocking = true,
       expectedExitCode = ACCEPTED)
     withActivation(wsk.activation, run, initialWait = 5 seconds, totalWait = 3 * allowedActionDuration)
{ activation =>
       checkSequenceLogsAndAnnotations(activation, 2) // 2 actions
       activation.response.success shouldBe (true)
-      // the status should be error
-      //activation.response.status shouldBe("application error")
       val result = activation.response.result.get
-      // the result of the activation should be timeout
-      result shouldBe (JsObject(
-        "msg" -> JsString(s"[OK] message terminated successfully after $payload milliseconds.")))
+      result.toString should include("""Terminated successfully after around""")
     }
   }
 
diff --git a/tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala b/tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala
index 925c098..50d4c57 100644
--- a/tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/WskBasicUsageTests.scala
@@ -26,7 +26,6 @@ import java.time.Instant
 import java.time.Clock
 
 import scala.language.postfixOps
-import scala.concurrent.duration.Duration
 import scala.concurrent.duration.DurationInt
 import scala.util.Random
 import org.junit.runner.RunWith
@@ -41,9 +40,6 @@ import common.rest.WskRest
 import spray.json.DefaultJsonProtocol._
 import spray.json._
 import whisk.core.entity._
-import whisk.core.entity.LogLimit._
-import whisk.core.entity.MemoryLimit._
-import whisk.core.entity.TimeLimit._
 import whisk.core.entity.size.SizeInt
 import TestJsonArgs._
 import whisk.http.Messages
@@ -699,59 +695,4 @@ class WskBasicUsageTests extends TestHelpers with WskTestHelpers {
       wsk.trigger.delete(triggerName).statusCode shouldBe OK
     }
   }
-
-  behavior of "Wsk action parameters"
-
-  it should "create an action with different permutations of limits" in withAssetCleaner(wskprops)
{
-    (wp, assetHelper) =>
-      val file = Some(TestUtils.getTestActionFilename("hello.js"))
-
-      def testLimit(timeout: Option[Duration] = None,
-                    memory: Option[ByteSize] = None,
-                    logs: Option[ByteSize] = None,
-                    ec: Int = SUCCESS_EXIT) = {
-        // Limits to assert, standard values if CLI omits certain values
-        val limits = JsObject(
-          "timeout" -> timeout.getOrElse(STD_DURATION).toMillis.toJson,
-          "memory" -> memory.getOrElse(stdMemory).toMB.toInt.toJson,
-          "logs" -> logs.getOrElse(STD_LOGSIZE).toMB.toInt.toJson)
-
-        val name = "ActionLimitTests" + Instant.now.toEpochMilli
-        val createResult = assetHelper.withCleaner(wsk.action, name, confirmDelete = (ec
== SUCCESS_EXIT)) {
-          (action, _) =>
-            val result = action.create(
-              name,
-              file,
-              logsize = logs,
-              memory = memory,
-              timeout = timeout,
-              expectedExitCode = DONTCARE_EXIT)
-            withClue(s"create failed for parameters: timeout = $timeout, memory = $memory,
logsize = $logs:") {
-              result.exitCode should be(ec)
-            }
-            result
-        }
-
-        if (ec == SUCCESS_EXIT) {
-          val JsObject(parsedAction) = wsk.action.get(name).respBody
-          parsedAction("limits") shouldBe limits
-        } else {
-          createResult.stderr should include("allowed threshold")
-        }
-      }
-
-      // Assert for valid permutations that the values are set correctly
-      for {
-        time <- Seq(None, Some(MIN_DURATION), Some(MAX_DURATION))
-        mem <- Seq(None, Some(minMemory), Some(maxMemory))
-        log <- Seq(None, Some(MIN_LOGSIZE), Some(MAX_LOGSIZE))
-      } testLimit(time, mem, log)
-
-      // Assert that invalid permutation are rejected
-      testLimit(Some(0.milliseconds), None, None, BAD_REQUEST)
-      testLimit(Some(100.minutes), None, None, BAD_REQUEST)
-      testLimit(None, Some(0.MB), None, BAD_REQUEST)
-      testLimit(None, Some(32768.MB), None, BAD_REQUEST)
-      testLimit(None, None, Some(32768.MB), BAD_REQUEST)
-  }
 }
diff --git a/tests/src/test/scala/whisk/core/limits/ActionLimitsTests.scala b/tests/src/test/scala/whisk/core/limits/ActionLimitsTests.scala
index 5c77f30..e820130 100644
--- a/tests/src/test/scala/whisk/core/limits/ActionLimitsTests.scala
+++ b/tests/src/test/scala/whisk/core/limits/ActionLimitsTests.scala
@@ -19,28 +19,25 @@ package whisk.core.limits
 
 import akka.http.scaladsl.model.StatusCodes.RequestEntityTooLarge
 import akka.http.scaladsl.model.StatusCodes.BadGateway
-
 import java.io.File
 import java.io.PrintWriter
+import java.time.Instant
 
-import scala.concurrent.duration.DurationInt
+import scala.concurrent.duration.{Duration, DurationInt}
 import scala.language.postfixOps
-
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
-
 import common.ActivationResult
 import common.TestHelpers
 import common.TestUtils
+import common.TestUtils.{BAD_REQUEST, DONTCARE_EXIT, SUCCESS_EXIT}
 import common.WhiskProperties
 import common.rest.WskRest
 import common.WskProps
 import common.WskTestHelpers
 import spray.json._
 import spray.json.DefaultJsonProtocol._
-import whisk.core.entity.ActivationEntityLimit
-import whisk.core.entity.ActivationResponse
-import whisk.core.entity.Exec
+import whisk.core.entity.{ActivationEntityLimit, ActivationResponse, ByteSize, Exec, LogLimit,
MemoryLimit, TimeLimit}
 import whisk.core.entity.size._
 import whisk.http.Messages
 
@@ -50,7 +47,7 @@ class ActionLimitsTests extends TestHelpers with WskTestHelpers {
   implicit val wskprops = WskProps()
   val wsk = new WskRest
 
-  val defaultDosAction = TestUtils.getTestActionFilename("timeout.js")
+  val defaultSleepAction = TestUtils.getTestActionFilename("sleep.js")
   val allowedActionDuration = 10 seconds
 
   val testActionsDir = WhiskProperties.getFileRelativeToWhiskHome("tests/dat/actions")
@@ -63,37 +60,155 @@ class ActionLimitsTests extends TestHelpers with WskTestHelpers {
   behavior of "Action limits"
 
   /**
-   * Test a long running action that exceeds the maximum execution time allowed for action
-   * by setting the action limit explicitly and attempting to run the action for an additional
second.
+   * Helper class for the integration test following below.
+   * @param timeout the action timeout limit to be set in test
+   * @param memory the action memory size limit to be set in test
+   * @param logs the action log size limit to be set in test
+   * @param ec the expected exit code when creating the action
+   */
+  sealed case class PermutationTestParameter(timeout: Option[Duration] = None,
+                                             memory: Option[ByteSize] = None,
+                                             logs: Option[ByteSize] = None,
+                                             ec: Int = SUCCESS_EXIT) {
+    override def toString: String =
+      s"timeout: ${toTimeoutString}, memory: ${toMemoryString}, logsize: ${toLogsString}"
+
+    val toTimeoutString = timeout match {
+      case None                                    => "None"
+      case Some(TimeLimit.MIN_DURATION)            => s"${TimeLimit.MIN_DURATION} (= min)"
+      case Some(TimeLimit.STD_DURATION)            => s"${TimeLimit.STD_DURATION} (= std)"
+      case Some(TimeLimit.MAX_DURATION)            => s"${TimeLimit.MAX_DURATION} (= max)"
+      case Some(t) if (t < TimeLimit.MIN_DURATION) => s"${t} (< min)"
+      case Some(t) if (t > TimeLimit.MAX_DURATION) => s"${t} (> max)"
+      case Some(t)                                 => s"${t} (allowed)"
+    }
+
+    val toMemoryString = memory match {
+      case None                                   => "None"
+      case Some(MemoryLimit.minMemory)            => s"${MemoryLimit.minMemory.toMB.MB}
(= min)"
+      case Some(MemoryLimit.stdMemory)            => s"${MemoryLimit.stdMemory.toMB.MB}
(= std)"
+      case Some(MemoryLimit.maxMemory)            => s"${MemoryLimit.maxMemory.toMB.MB}
(= max)"
+      case Some(m) if (m < MemoryLimit.minMemory) => s"${m.toMB.MB} (< min)"
+      case Some(m) if (m > MemoryLimit.maxMemory) => s"${m.toMB.MB} (> max)"
+      case Some(m)                                => s"${m.toMB.MB} (allowed)"
+    }
+
+    val toLogsString = logs match {
+      case None                                  => "None"
+      case Some(LogLimit.MIN_LOGSIZE)            => s"${LogLimit.MIN_LOGSIZE} (= min)"
+      case Some(LogLimit.STD_LOGSIZE)            => s"${LogLimit.STD_LOGSIZE} (= std)"
+      case Some(LogLimit.MAX_LOGSIZE)            => s"${LogLimit.MAX_LOGSIZE} (= max)"
+      case Some(l) if (l < LogLimit.MIN_LOGSIZE) => s"${l} (< min)"
+      case Some(l) if (l > LogLimit.MAX_LOGSIZE) => s"${l} (> max)"
+      case Some(l)                               => s"${l} (allowed)"
+    }
+
+    val toExpectedResultString: String = if (ec == SUCCESS_EXIT) "allow" else "reject"
+  }
+
+  val perms = { // Assert for valid permutations that the values are set correctly
+    for {
+      time <- Seq(None, Some(TimeLimit.MIN_DURATION), Some(TimeLimit.MAX_DURATION))
+      mem <- Seq(None, Some(MemoryLimit.minMemory), Some(MemoryLimit.maxMemory))
+      log <- Seq(None, Some(LogLimit.MIN_LOGSIZE), Some(LogLimit.MAX_LOGSIZE))
+    } yield PermutationTestParameter(time, mem, log)
+  } ++
+    // Add variations for negative tests
+    Seq(
+      PermutationTestParameter(Some(0.milliseconds), None, None, BAD_REQUEST), // timeout
that is lower than allowed
+      PermutationTestParameter(Some(TimeLimit.MAX_DURATION.plus(1 second)), None, None, BAD_REQUEST),
// timeout that is slightly higher than allowed
+      PermutationTestParameter(Some(TimeLimit.MAX_DURATION * 10), None, None, BAD_REQUEST),
// timeout that is much higher than allowed
+      PermutationTestParameter(None, Some(0.MB), None, BAD_REQUEST), // memory limit that
is lower than allowed
+      PermutationTestParameter(None, Some(MemoryLimit.maxMemory + 1.MB), None, BAD_REQUEST),
// memory limit that is slightly higher than allowed
+      PermutationTestParameter(None, Some((MemoryLimit.maxMemory.toMB * 5).MB), None, BAD_REQUEST),
// memory limit that is much higher than allowed
+      PermutationTestParameter(None, None, Some((LogLimit.MAX_LOGSIZE.toMB * 5).MB), BAD_REQUEST))
// log size limit that is much higher than allowed
+
+  /**
+   * Integration test to verify that valid timeout, memory and log size limits are accepted
+   * when creating an action while any invalid limit is rejected.
+   *
+   * At the first sight, this test looks like a typical unit test that should not be performed
+   * as an integration test. It is performed as an integration test requiring an OpenWhisk
+   * deployment to verify that limit settings of the tested deployment fit with the values
+   * used in this test.
+   */
+  perms.foreach { parm =>
+    it should s"${parm.toExpectedResultString} creation of an action with these limits: ${parm}"
in withAssetCleaner(
+      wskprops) { (wp, assetHelper) =>
+      val file = Some(TestUtils.getTestActionFilename("hello.js"))
+
+      // Limits to assert, standard values if CLI omits certain values
+      val limits = JsObject(
+        "timeout" -> parm.timeout.getOrElse(TimeLimit.STD_DURATION).toMillis.toJson,
+        "memory" -> parm.memory.getOrElse(MemoryLimit.stdMemory).toMB.toInt.toJson,
+        "logs" -> parm.logs.getOrElse(LogLimit.STD_LOGSIZE).toMB.toInt.toJson)
+
+      val name = "ActionLimitTests-" + Instant.now.toEpochMilli
+      val createResult = assetHelper.withCleaner(wsk.action, name, confirmDelete = (parm.ec
== SUCCESS_EXIT)) {
+        (action, _) =>
+          val result = action.create(
+            name,
+            file,
+            logsize = parm.logs,
+            memory = parm.memory,
+            timeout = parm.timeout,
+            expectedExitCode = DONTCARE_EXIT)
+          withClue(s"Unexpected result when creating action '${name}':\n${result.toString}\nFailed
assertion:") {
+            result.exitCode should be(parm.ec)
+          }
+          result
+      }
+
+      if (parm.ec == SUCCESS_EXIT) {
+        val JsObject(parsedAction) = wsk.action.get(name).respBody
+        parsedAction("limits") shouldBe limits
+      } else {
+        createResult.stderr should include("allowed threshold")
+      }
+    }
+  }
+
+  /**
+   * Test an action that exceeds its specified time limit. Explicitly specify a rather low
time
+   * limit to keep the test's runtime short. Invoke an action that sleeps for the specified
time
+   * limit plus one second.
    */
   it should "error with a proper warning if the action exceeds its time limits" in withAssetCleaner(wskprops)
{
     (wp, assetHelper) =>
-      val name = "TestActionCausingTimeout"
+      val name = "TestActionCausingTimeout-" + System.currentTimeMillis()
       assetHelper.withCleaner(wsk.action, name, confirmDelete = true) { (action, _) =>
-        action.create(name, Some(defaultDosAction), timeout = Some(allowedActionDuration))
+        action.create(name, Some(defaultSleepAction), timeout = Some(allowedActionDuration))
       }
 
-      val run = wsk.action.invoke(name, Map("payload" -> allowedActionDuration.plus(1
second).toMillis.toJson))
-      withActivation(wsk.activation, run) {
-        _.response.result.get.fields("error") shouldBe {
-          Messages.timedoutActivation(allowedActionDuration, false).toJson
+      val run = wsk.action.invoke(name, Map("sleepTimeInMs" -> allowedActionDuration.plus(1
second).toMillis.toJson))
+      withActivation(wsk.activation, run) { result =>
+        withClue("Activation result not as expected:") {
+          result.response.status shouldBe ActivationResponse.messageForCode(ActivationResponse.ApplicationError)
+          result.response.result.get.fields("error") shouldBe {
+            Messages.timedoutActivation(allowedActionDuration, init = false).toJson
+          }
+          result.duration.toInt should be >= allowedActionDuration.toMillis.toInt
         }
       }
   }
 
   /**
-   * Test an action that does not exceed the allowed execution timeout of an action.
+   * Test an action that tightly stays within its specified time limit. Explicitly specify
a rather low time
+   * limit to keep the test's runtime short. Invoke an action that sleeps for the specified
time
+   * limit minus one second.
    */
   it should "succeed on an action staying within its time limits" in withAssetCleaner(wskprops)
{ (wp, assetHelper) =>
-    val name = "TestActionCausingNoTimeout"
+    val name = "TestActionCausingNoTimeout-" + System.currentTimeMillis()
     assetHelper.withCleaner(wsk.action, name, confirmDelete = true) { (action, _) =>
-      action.create(name, Some(defaultDosAction), timeout = Some(allowedActionDuration))
+      action.create(name, Some(defaultSleepAction), timeout = Some(allowedActionDuration))
     }
 
-    val run = wsk.action.invoke(name, Map("payload" -> allowedActionDuration.minus(1 second).toMillis.toJson))
-    withActivation(wsk.activation, run) {
-      _.response.result.get.toString should include("""[OK] message terminated successfully""")
-
+    val run = wsk.action.invoke(name, Map("sleepTimeInMs" -> allowedActionDuration.minus(1
second).toMillis.toJson))
+    withActivation(wsk.activation, run) { result =>
+      withClue("Activation result not as expected:") {
+        result.response.status shouldBe ActivationResponse.messageForCode(ActivationResponse.Success)
+        result.response.result.get.toString should include("""Terminated successfully after
around""")
+      }
     }
   }
 
diff --git a/tests/src/test/scala/whisk/core/limits/MaxActionDurationTests.scala b/tests/src/test/scala/whisk/core/limits/MaxActionDurationTests.scala
index dba2eeb..b35ffa8 100644
--- a/tests/src/test/scala/whisk/core/limits/MaxActionDurationTests.scala
+++ b/tests/src/test/scala/whisk/core/limits/MaxActionDurationTests.scala
@@ -32,6 +32,7 @@ import spray.json.DefaultJsonProtocol._
 import spray.json._
 import whisk.http.Messages
 import whisk.core.entity.TimeLimit
+import org.scalatest.tagobjects.Slow
 
 /**
  * Tests for action duration limits. These tests require a deployed backend.
@@ -42,38 +43,54 @@ class MaxActionDurationTests extends TestHelpers with WskTestHelpers {
   implicit val wskprops = WskProps()
   val wsk = new WskRest
 
-  // swift is not tested, because it uses the same proxy like python
-  "node-, python, and java-action" should "run up to the max allowed duration" in withAssetCleaner(wskprops)
{
-    (wp, assetHelper) =>
-      // When you add more runtimes, keep in mind, how many actions can be processed in parallel
by the Invokers!
-      Map("node" -> "helloDeadline.js", "python" -> "timedout.py", "java" -> "timedout.jar").par.map
{
-        case (k, name) =>
-          assetHelper.withCleaner(wsk.action, name) {
-            if (k == "java") { (action, _) =>
-              action.create(
-                name,
-                Some(TestUtils.getTestActionFilename(name)),
-                timeout = Some(TimeLimit.MAX_DURATION),
-                main = Some("TimedOut"))
-            } else { (action, _) =>
-              action.create(name, Some(TestUtils.getTestActionFilename(name)), timeout =
Some(TimeLimit.MAX_DURATION))
-            }
-          }
+  /**
+   * Purpose of the following integration test is to verify that the action proxy
+   * supports the configured maximum action time limit and does not interrupt a
+   * running action before the invoker does.
+   *
+   * Action proxies have to run actions potentially endlessly. It's the invoker's
+   * duty to enforce action time limits.
+   *
+   * Background: in the past, the Node.js action proxy terminated an action
+   * before it actually reached its maximum action time limit.
+   *
+   * Swift is not tested because it uses the same action proxy as Python.
+   *
+   * ATTENTION: this test runs for at least TimeLimit.MAX_DURATION + 1 minute.
+   * With default settings, this is around 6 minutes.
+   */
+  "node-, python, and java-action" should s"run up to the max allowed duration (${TimeLimit.MAX_DURATION})"
taggedAs (Slow) in withAssetCleaner(
+    wskprops) { (wp, assetHelper) =>
+    // When you add more runtimes, keep in mind, how many actions can be processed in parallel
by the Invokers!
+    Map("node" -> "helloDeadline.js", "python" -> "sleep.py", "java" -> "sleep.jar").par.map
{
+      case (k, name) =>
+        println(s"Testing action kind '${k}' with action '${name}'")
+        assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+          val main = if (k == "java") Some("Sleep") else None
+          action.create(
+            name,
+            Some(TestUtils.getTestActionFilename(name)),
+            timeout = Some(TimeLimit.MAX_DURATION),
+            main = main)
+        }
 
-          val run = wsk.action.invoke(name, Map("forceHang" -> true.toJson))
-          withActivation(
-            wsk.activation,
-            run,
-            initialWait = 1.minute,
-            pollPeriod = 1.minute,
-            totalWait = TimeLimit.MAX_DURATION + 1.minute) { activation =>
+        val run = wsk.action.invoke(
+          name,
+          Map("forceHang" -> true.toJson, "sleepTimeInMs" -> (TimeLimit.MAX_DURATION
+ 30.seconds).toMillis.toJson))
+        withActivation(
+          wsk.activation,
+          run,
+          initialWait = 1.minute,
+          pollPeriod = 1.minute,
+          totalWait = TimeLimit.MAX_DURATION + 2.minutes) { activation =>
+          withClue("Activation result not as expected:") {
             activation.response.status shouldBe ActivationResponse.messageForCode(ActivationResponse.ApplicationError)
             activation.response.result shouldBe Some(
-              JsObject("error" -> Messages.timedoutActivation(TimeLimit.MAX_DURATION,
false).toJson))
+              JsObject("error" -> Messages.timedoutActivation(TimeLimit.MAX_DURATION,
init = false).toJson))
             activation.duration.toInt should be >= TimeLimit.MAX_DURATION.toMillis.toInt
-
           }
-      }
+        }
+        () // explicitly map to Unit
+    }
   }
-
 }

-- 
To stop receiving notification emails like this one, please contact
markusthoemmes@apache.org.

Mime
View raw message