This is an automated email from the ASF dual-hosted git repository.
ningyougang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openwhisk.git
The following commit(s) were added to refs/heads/master by this push:
new 62b8a50d4 Support array result for common action and sequence action
(#5290)
62b8a50d4 is described below
commit 62b8a50d447c066a4e579ed1d8c85eef02e37e41
Author: ningyougang <[email protected]>
AuthorDate: Mon Aug 1 17:32:12 2022 +0800
Support array result for common action and sequence action (#5290)
* Support array result
* Make controller accept json array
* Make elasticsearch support json array
Couchdb already suports
* Make go runtime test cases due to depend on this
* Add test case for array result for nodejs runtime
* Make sequence action to support array result
* Optimize sequence action to support array result
* Fix test case for sequence action feature
* Add test case for sequence action
This test case is just for nodejs
* Add extra method runForJsArray for runtime tests
* Fix build error
* Fix review comment
---
.../apache/openwhisk/core/connector/Message.scala | 12 +++--
.../core/containerpool/AkkaContainerClient.scala | 33 ++++++++++++
.../openwhisk/core/containerpool/Container.scala | 4 +-
.../ElasticSearchActivationStore.scala | 13 +++--
.../openwhisk/core/entity/ActivationResult.scala | 15 +++++-
.../org/apache/openwhisk/http/ErrorResponse.scala | 2 +-
.../apache/openwhisk/core/controller/Actions.scala | 8 ++-
.../controller/actions/PostActionActivation.scala | 2 +-
.../core/controller/actions/PrimitiveActions.scala | 32 ++++++++----
.../core/controller/actions/SequenceActions.scala | 20 ++++---
.../core/containerpool/ContainerProxy.scala | 25 +++++----
tests/dat/actions/helloArray.js | 24 +++++++++
tests/dat/actions/sort-array.js | 28 ++++++++++
tests/dat/actions/split-array.js | 31 +++++++++++
.../scala/actionContainers/ActionContainer.scala | 13 +++++
.../actionContainers/BasicActionRunnerTests.scala | 2 +-
tests/src/test/scala/common/WskTestHelpers.scala | 2 +-
.../scala/invokerShoot/ShootInvokerTests.scala | 2 +-
.../core/cli/test/WskRestBasicUsageTests.scala | 10 ++--
.../containerpool/test/ContainerProxyTests.scala | 10 ++--
.../test/FunctionPullingContainerProxyTests.scala | 8 +--
.../core/controller/test/ConductorsApiTests.scala | 61 ++++++++++++++--------
.../core/controller/test/WebActionsApiTests.scala | 8 ++-
.../openwhisk/core/limits/ActionLimitsTests.scala | 10 ++--
.../test/scala/system/basic/WskActionTests.scala | 16 +++++-
.../scala/system/basic/WskConductorTests.scala | 4 +-
.../test/scala/system/basic/WskSequenceTests.scala | 51 +++++++++++++-----
.../test/scala/system/basic/WskUnicodeTests.scala | 4 +-
28 files changed, 342 insertions(+), 108 deletions(-)
diff --git
a/common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala
b/common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala
index 11749bed0..d3c550eb2 100644
---
a/common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala
+++
b/common/scala/src/main/scala/org/apache/openwhisk/core/connector/Message.scala
@@ -57,7 +57,7 @@ case class ActivationMessage(override val transid:
TransactionId,
activationId: ActivationId,
rootControllerIndex: ControllerInstanceId,
blocking: Boolean,
- content: Option[JsObject],
+ content: Option[JsValue],
initArgs: Set[String] = Set.empty,
lockedArgs: Map[String, String] = Map.empty,
cause: Option[ActivationId] = None,
@@ -380,9 +380,13 @@ object Activation extends DefaultJsonProtocol {
/** Get "StatusCode" from result response set by action developer * */
def userDefinedStatusCode(result: Option[JsValue]): Option[Int] = {
- val statusCode = JsHelpers
- .getFieldPath(result.get.asJsObject, ERROR_FIELD, "statusCode")
- .orElse(JsHelpers.getFieldPath(result.get.asJsObject, "statusCode"))
+ val statusCode: Option[JsValue] = result match {
+ case Some(JsObject(fields)) =>
+ JsHelpers
+ .getFieldPath(JsObject(fields), ERROR_FIELD, "statusCode")
+ .orElse(JsHelpers.getFieldPath(JsObject(fields), "statusCode"))
+ case _ => None
+ }
statusCode.map {
case value =>
Try(value.convertTo[BigInt].intValue).toOption.getOrElse(BadRequest.intValue)
}
diff --git
a/common/scala/src/main/scala/org/apache/openwhisk/core/containerpool/AkkaContainerClient.scala
b/common/scala/src/main/scala/org/apache/openwhisk/core/containerpool/AkkaContainerClient.scala
index 4e06d6111..ea5978bb7 100644
---
a/common/scala/src/main/scala/org/apache/openwhisk/core/containerpool/AkkaContainerClient.scala
+++
b/common/scala/src/main/scala/org/apache/openwhisk/core/containerpool/AkkaContainerClient.scala
@@ -201,6 +201,19 @@ object AkkaContainerClient {
result
}
+ /** A helper method to post one single request to a connection. Used for
container tests. */
+ def postForJsArray(host: String, port: Int, endPoint: String, content:
JsValue, timeout: FiniteDuration)(
+ implicit logging: Logging,
+ as: ActorSystem,
+ ec: ExecutionContext,
+ tid: TransactionId): (Int, Option[JsArray]) = {
+ val connection = new AkkaContainerClient(host, port, timeout, 1.MB, 1.MB,
1)
+ val response = executeRequestForJsArray(connection, endPoint, content)
+ val result = Await.result(response, timeout + 10.seconds) //additional
timeout to complete futures
+ connection.close()
+ result
+ }
+
/** A helper method to post multiple concurrent requests to a single
connection. Used for container tests. */
def concurrentPost(host: String, port: Int, endPoint: String, contents:
Seq[JsValue], timeout: FiniteDuration)(
implicit logging: Logging,
@@ -233,4 +246,24 @@ object AkkaContainerClient {
res
}
+
+ private def executeRequestForJsArray(connection: AkkaContainerClient,
endpoint: String, content: JsValue)(
+ implicit logging: Logging,
+ as: ActorSystem,
+ ec: ExecutionContext,
+ tid: TransactionId): Future[(Int, Option[JsArray])] = {
+
+ val res = connection
+ .post(endpoint, content, true)
+ .map({
+ case Right(r) => (r.statusCode,
Try(r.entity.parseJson.convertTo[JsArray]).toOption)
+ case Left(NoResponseReceived()) => throw new IllegalStateException("no
response from container")
+ case Left(Timeout(_)) => throw new
java.util.concurrent.TimeoutException()
+ case Left(ConnectionError(t: java.net.SocketTimeoutException)) =>
+ throw new java.util.concurrent.TimeoutException()
+ case Left(ConnectionError(t)) => throw new
IllegalStateException(t.getMessage)
+ })
+
+ res
+ }
}
diff --git
a/common/scala/src/main/scala/org/apache/openwhisk/core/containerpool/Container.scala
b/common/scala/src/main/scala/org/apache/openwhisk/core/containerpool/Container.scala
index 6b557f3a3..5290f3e4a 100644
---
a/common/scala/src/main/scala/org/apache/openwhisk/core/containerpool/Container.scala
+++
b/common/scala/src/main/scala/org/apache/openwhisk/core/containerpool/Container.scala
@@ -26,7 +26,7 @@ import akka.util.ByteString
import pureconfig._
import pureconfig.generic.auto._
import spray.json.DefaultJsonProtocol._
-import spray.json.JsObject
+import spray.json.{JsObject, JsValue}
import org.apache.openwhisk.common.{Logging, LoggingMarkers, TransactionId}
import org.apache.openwhisk.core.ConfigKeys
import
org.apache.openwhisk.core.entity.ActivationResponse.{ContainerConnectionError,
ContainerResponse}
@@ -159,7 +159,7 @@ trait Container {
}
/** Runs code in the container. Thread-safe - caller may invoke concurrently
for concurrent activation processing. */
- def run(parameters: JsObject,
+ def run(parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
maxConcurrent: Int,
diff --git
a/common/scala/src/main/scala/org/apache/openwhisk/core/database/elasticsearch/ElasticSearchActivationStore.scala
b/common/scala/src/main/scala/org/apache/openwhisk/core/database/elasticsearch/ElasticSearchActivationStore.scala
index 06de2af41..097b26b39 100644
---
a/common/scala/src/main/scala/org/apache/openwhisk/core/database/elasticsearch/ElasticSearchActivationStore.scala
+++
b/common/scala/src/main/scala/org/apache/openwhisk/core/database/elasticsearch/ElasticSearchActivationStore.scala
@@ -388,8 +388,8 @@ class ElasticSearchActivationStore(
restoreAnnotations(restoreResponse(hit.sourceAsString.parseJson.asJsObject)).convertTo[WhiskActivation]
}
- private def restoreAnnotations(js: JsObject): JsObject = {
- val annotations = js.fields
+ private def restoreAnnotations(js: JsValue): JsObject = {
+ val annotations = js.asJsObject.fields
.get("annotations")
.map { anno =>
Try {
@@ -399,10 +399,10 @@ class ElasticSearchActivationStore(
}.getOrElse(JsArray.empty)
}
.getOrElse(JsArray.empty)
- JsObject(js.fields.updated("annotations", annotations))
+ JsObject(js.asJsObject.fields.updated("annotations", annotations))
}
- private def restoreResponse(js: JsObject): JsObject = {
+ private def restoreResponse(js: JsObject): JsValue = {
val response = js.fields
.get("response")
.map { res =>
@@ -412,7 +412,10 @@ class ElasticSearchActivationStore(
.get("result")
.map { r =>
val JsString(data) = r
- data.parseJson.asJsObject
+ data.parseJson match {
+ case JsArray(elements) => JsArray(elements)
+ case _ => data.parseJson.asJsObject
+ }
}
.getOrElse(JsObject.empty)
JsObject(temp.updated("result", result))
diff --git
a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/ActivationResult.scala
b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/ActivationResult.scala
index c8a1ddfb2..42961425f 100644
---
a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/ActivationResult.scala
+++
b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/ActivationResult.scala
@@ -111,7 +111,7 @@ protected[core] object ActivationResponse extends
DefaultJsonProtocol {
* NOTE: the code is application error (since this response could be used as
a response for the sequence
* if the payload contains an error)
*/
- protected[core] def payloadPlaceholder(payload: Option[JsObject]) =
ActivationResponse(ApplicationError, payload)
+ protected[core] def payloadPlaceholder(payload: Option[JsValue]) =
ActivationResponse(ApplicationError, payload)
/**
* Class of errors for invoker-container communication.
@@ -203,7 +203,7 @@ protected[core] object ActivationResponse extends
DefaultJsonProtocol {
truncated match {
case None =>
val sizeOpt = Option(str).map(_.length)
- Try { str.parseJson.asJsObject } match {
+ Try { str.parseJson } match {
case scala.util.Success(result @ JsObject(fields)) =>
// If the response is a JSON object container an error field,
accept it as the response error.
val errorOpt = fields.get(ERROR_FIELD)
@@ -222,6 +222,17 @@ protected[core] object ActivationResponse extends
DefaultJsonProtocol {
developerError(errorContent, sizeOpt)
}
+ case scala.util.Success(result @ JsArray(_)) =>
+ if (res.okStatus) {
+ success(Some(result), sizeOpt)
+ } else {
+ // Any non-200 code is treated as a container failure. We
still need to check whether
+ // there was a useful error message in there.
+ val errorContent = invalidRunResponse(str).toJson
+ //developerErrorWithLog(errorContent, sizeOpt, None)
+ developerError(errorContent, sizeOpt)
+ }
+
case scala.util.Success(notAnObj) =>
// This should affect only blackbox containers, since our own
containers should already test for that.
developerError(invalidRunResponse(str), sizeOpt)
diff --git
a/common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala
b/common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala
index 35af54fa8..07818418e 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala
@@ -204,7 +204,7 @@ object Messages {
}
def invalidRunResponse(actualResponse: String) = {
- "The action did not produce a valid JSON response" + {
+ "The action did not produce a valid JSON or JSON Array response" + {
Option(actualResponse) filter { _.nonEmpty } map { s =>
s": $s"
} getOrElse "."
diff --git
a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala
b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala
index a6e4554b0..7820dfa63 100644
---
a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala
+++
b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala
@@ -289,8 +289,12 @@ trait WhiskActionsApi extends WhiskCollectionAPI with
PostActionActivation with
complete(Accepted, activationId.toJsObject)
}
case Success(Right(activation)) =>
- val response = if (result) activation.resultAsJson else
activation.toExtendedJson()
-
+ val response = activation.response.result match {
+ case Some(JsArray(elements)) =>
+ JsArray(elements)
+ case _ =>
+ if (result) activation.resultAsJson else
activation.toExtendedJson()
+ }
respondWithActivationIdHeader(activation.activationId) {
if (activation.response.isSuccess) {
complete(OK, response)
diff --git
a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PostActionActivation.scala
b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PostActionActivation.scala
index 1b14fc3a5..db9a38d42 100644
---
a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PostActionActivation.scala
+++
b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PostActionActivation.scala
@@ -47,7 +47,7 @@ protected[core] trait PostActionActivation extends
PrimitiveActions with Sequenc
protected[controller] def invokeAction(
user: Identity,
action: WhiskActionMetaData,
- payload: Option[JsObject],
+ payload: Option[JsValue],
waitForResponse: Option[FiniteDuration],
cause: Option[ActivationId])(implicit transid: TransactionId):
Future[Either[ActivationId, WhiskActivation]] = {
action.toExecutableWhiskAction match {
diff --git
a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PrimitiveActions.scala
b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PrimitiveActions.scala
index 19ca12e9f..694ef7fa0 100644
---
a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PrimitiveActions.scala
+++
b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/PrimitiveActions.scala
@@ -80,7 +80,7 @@ protected[actions] trait PrimitiveActions {
user: Identity,
action: WhiskActionMetaData,
components: Vector[FullyQualifiedEntityName],
- payload: Option[JsObject],
+ payload: Option[JsValue],
waitForOutermostResponse: Option[FiniteDuration],
cause: Option[ActivationId],
topmost: Boolean,
@@ -109,7 +109,7 @@ protected[actions] trait PrimitiveActions {
protected[actions] def invokeSingleAction(
user: Identity,
action: ExecutableWhiskActionMetaData,
- payload: Option[JsObject],
+ payload: Option[JsValue],
waitForResponse: Option[FiniteDuration],
cause: Option[ActivationId])(implicit transid: TransactionId):
Future[Either[ActivationId, WhiskActivation]] = {
@@ -152,12 +152,16 @@ protected[actions] trait PrimitiveActions {
private def invokeSimpleAction(
user: Identity,
action: ExecutableWhiskActionMetaData,
- payload: Option[JsObject],
+ payload: Option[JsValue],
waitForResponse: Option[FiniteDuration],
cause: Option[ActivationId])(implicit transid: TransactionId):
Future[Either[ActivationId, WhiskActivation]] = {
// merge package parameters with action (action parameters supersede),
then merge in payload
- val args = action.parameters merge payload
+ val args: Option[JsValue] = payload match {
+ case Some(JsObject(fields)) => action.parameters merge
Some(JsObject(fields))
+ case Some(JsArray(elements)) => Some(JsArray(elements))
+ case _ => Some(action.parameters.toJsObject)
+ }
val activationId = activationIdFactory.make()
val startActivation = transid.started(
@@ -169,6 +173,10 @@ protected[actions] trait PrimitiveActions {
val startLoadbalancer =
transid.started(this, LoggingMarkers.CONTROLLER_LOADBALANCER, s"action
activation id: ${activationId}")
+ val keySet = payload match {
+ case Some(JsObject(fields)) => Some(fields.keySet)
+ case _ => None
+ }
val message = ActivationMessage(
transid,
FullyQualifiedEntityName(action.namespace, action.name,
Some(action.version), action.binding),
@@ -179,7 +187,7 @@ protected[actions] trait PrimitiveActions {
waitForResponse.isDefined,
args,
action.parameters.initParameters,
-
action.parameters.lockedParameters(payload.map(_.fields.keySet).getOrElse(Set.empty)),
+ action.parameters.lockedParameters(keySet.getOrElse(Set.empty)),
cause = cause,
WhiskTracerProvider.tracer.getTraceContext(transid))
@@ -271,7 +279,7 @@ protected[actions] trait PrimitiveActions {
*/
private def invokeComposition(user: Identity,
action: ExecutableWhiskActionMetaData,
- payload: Option[JsObject],
+ payload: Option[JsValue],
waitForResponse: Option[FiniteDuration],
cause: Option[ActivationId],
accounting: Option[CompositionAccounting] =
None)(
@@ -319,7 +327,7 @@ protected[actions] trait PrimitiveActions {
* @param parentTid a parent transaction id
*/
private def invokeConductor(user: Identity,
- payload: Option[JsObject],
+ payload: Option[JsValue],
session: Session,
parentTid: TransactionId):
Future[ActivationResponse] = {
@@ -330,9 +338,13 @@ protected[actions] trait PrimitiveActions {
Future.successful(ActivationResponse.applicationError(compositionIsTooLong))
} else {
// inject state into payload if any
- val params = session.state
- .map(state => Some(JsObject(payload.getOrElse(JsObject.empty).fields
++ state.fields)))
- .getOrElse(payload)
+ val params: Option[JsValue] = payload match {
+ case Some(JsObject(fields)) =>
+ session.state
+ .map(state => Some(JsObject(JsObject(fields).fields ++
state.fields)))
+ .getOrElse(payload)
+ case _ => None
+ }
// invoke conductor action
session.accounting.conductors += 1
diff --git
a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/SequenceActions.scala
b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/SequenceActions.scala
index 0bc082bf3..4490cf158 100644
---
a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/SequenceActions.scala
+++
b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/actions/SequenceActions.scala
@@ -71,7 +71,7 @@ protected[actions] trait SequenceActions {
protected[actions] def invokeAction(
user: Identity,
action: WhiskActionMetaData,
- payload: Option[JsObject],
+ payload: Option[JsValue],
waitForResponse: Option[FiniteDuration],
cause: Option[ActivationId])(implicit transid: TransactionId):
Future[Either[ActivationId, WhiskActivation]]
@@ -93,7 +93,7 @@ protected[actions] trait SequenceActions {
user: Identity,
action: WhiskActionMetaData,
components: Vector[FullyQualifiedEntityName],
- payload: Option[JsObject],
+ payload: Option[JsValue],
waitForOutermostResponse: Option[FiniteDuration],
cause: Option[ActivationId],
topmost: Boolean,
@@ -266,7 +266,7 @@ protected[actions] trait SequenceActions {
user: Identity,
seqAction: WhiskActionMetaData,
seqActivationId: ActivationId,
- inputPayload: Option[JsObject],
+ inputPayload: Option[JsValue],
components: Vector[FullyQualifiedEntityName],
cause: Option[ActivationId],
atomicActionCnt: Int)(implicit transid: TransactionId):
Future[SequenceAccounting] = {
@@ -347,7 +347,12 @@ protected[actions] trait SequenceActions {
// the accounting no longer needs to hold a reference to it once the
action is
// invoked, so previousResponse.getAndSet(null) drops the reference at
this point
// which prevents dragging the previous response for the lifetime of the
next activation
- val inputPayload =
accounting.previousResponse.getAndSet(null).result.map(_.asJsObject)
+ val previousResult = accounting.previousResponse.getAndSet(null).result
+ val inputPayload: Option[JsValue] = previousResult match {
+ case Some(JsObject(fields)) => Some(JsObject(fields))
+ case Some(JsArray(elements)) => Some(JsArray(elements))
+ case _ => None
+ }
// invoke the action by calling the right method depending on whether
it's an atomic action or a sequence
val futureWhiskActivationTuple = action.toExecutableWhiskAction match {
@@ -460,9 +465,10 @@ protected[actions] case class
SequenceAccounting(atomicActionCnt: Int,
// check conditions on payload that may lead to interrupting the execution
of the sequence
// short-circuit the execution of the sequence iff the payload
contains an error field
// and is the result of an action return, not the initial payload
- val outputPayload = activation.response.result.map(_.asJsObject)
- val payloadContent = outputPayload getOrElse JsObject.empty
- val errorField = payloadContent.fields.get(ActivationResponse.ERROR_FIELD)
+ val errorField: Option[JsValue] = activation.response.result match {
+ case Some(JsObject(fields)) => fields.get(ActivationResponse.ERROR_FIELD)
+ case _ => None
+ }
val withinSeqLimit = newCnt <= maxSequenceCnt
if (withinSeqLimit && errorField.isEmpty) {
diff --git
a/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerProxy.scala
b/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerProxy.scala
index 1f8476c29..d5ae131b1 100644
---
a/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerProxy.scala
+++
b/core/invoker/src/main/scala/org/apache/openwhisk/core/containerpool/ContainerProxy.scala
@@ -1082,25 +1082,28 @@ object ContainerProxy {
* @param initArgs set of parameters to treat as initialization arguments
* @return A partition of the arguments into an environment variables map
and the JsObject argument to the action
*/
- def partitionArguments(content: Option[JsObject], initArgs: Set[String]):
(Map[String, JsValue], JsObject) = {
+ def partitionArguments(content: Option[JsValue], initArgs: Set[String]):
(Map[String, JsValue], JsValue) = {
content match {
- case None => (Map.empty, JsObject.empty)
- case Some(js) if initArgs.isEmpty => (Map.empty, js)
- case Some(js) =>
- val (env, args) = js.fields.partition(k => initArgs.contains(k._1))
+ case None => (Map.empty,
JsObject.empty)
+ case Some(JsArray(elements)) => (Map.empty,
JsArray(elements))
+ case Some(JsObject(fields)) if initArgs.isEmpty => (Map.empty,
JsObject(fields))
+ case Some(JsObject(fields)) =>
+ val (env, args) = fields.partition(k => initArgs.contains(k._1))
(env, JsObject(args))
}
}
- def unlockArguments(content: Option[JsObject],
+ def unlockArguments(content: Option[JsValue],
lockedArgs: Map[String, String],
- decoder: ParameterEncryption): Option[JsObject] = {
- content.map {
- case JsObject(fields) =>
- JsObject(fields.map {
+ decoder: ParameterEncryption): Option[JsValue] = {
+ content match {
+ case Some(JsObject(fields)) =>
+ Some(JsObject(fields.map {
case (k, v: JsString) if lockedArgs.contains(k) => (k ->
decoder.encryptor(lockedArgs(k)).decrypt(v))
case p => p
- })
+ }))
+ // keep the original for other type(e.g. JsArray)
+ case contentValue => contentValue
}
}
}
diff --git a/tests/dat/actions/helloArray.js b/tests/dat/actions/helloArray.js
new file mode 100644
index 000000000..a112c5db4
--- /dev/null
+++ b/tests/dat/actions/helloArray.js
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+/**
+ * helloArray action
+ */
+
+function main(params) {
+ return [{"key1":"value1"},{"key2":"value2"}];
+}
diff --git a/tests/dat/actions/sort-array.js b/tests/dat/actions/sort-array.js
new file mode 100644
index 000000000..f031ca0b3
--- /dev/null
+++ b/tests/dat/actions/sort-array.js
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+/**
+ * Sort a set of lines.
+ * @param lines An array of strings to sort.
+ */
+function main(msg) {
+ var lines = msg || [];
+ console.log('sort before: ' + lines);
+ lines.sort();
+ console.log('sort after: ' + lines);
+ return lines;
+}
diff --git a/tests/dat/actions/split-array.js b/tests/dat/actions/split-array.js
new file mode 100644
index 000000000..f3f29fd23
--- /dev/null
+++ b/tests/dat/actions/split-array.js
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+/**
+ * Splits a string into an array of strings
+ * Return lines in an array.
+ * @param payload A string.
+ * @param separator The character, or the regular expression, to use for
splitting the string
+ */
+function main(msg) {
+ var separator = msg.separator || /\r?\n/;
+ var payload = msg.payload.toString();
+ var lines = payload.split(separator);
+ // return array as next action's input
+ return lines;
+}
+
diff --git a/tests/src/test/scala/actionContainers/ActionContainer.scala
b/tests/src/test/scala/actionContainers/ActionContainer.scala
index 28c6937d3..4e7b4e7fa 100644
--- a/tests/src/test/scala/actionContainers/ActionContainer.scala
+++ b/tests/src/test/scala/actionContainers/ActionContainer.scala
@@ -52,6 +52,7 @@ import org.apache.openwhisk.core.containerpool.Container
trait ActionContainer {
def init(value: JsValue): (Int, Option[JsObject])
def run(value: JsValue): (Int, Option[JsObject])
+ def runForJsArray(value: JsValue): (Int, Option[JsArray])
def runMultiple(values: Seq[JsValue])(implicit ec: ExecutionContext):
Seq[(Int, Option[JsObject])]
}
@@ -227,6 +228,7 @@ object ActionContainer {
val mock = new ActionContainer {
def init(value: JsValue): (Int, Option[JsObject]) = syncPost(ip, port,
"/init", value)
def run(value: JsValue): (Int, Option[JsObject]) = syncPost(ip, port,
"/run", value)
+ def runForJsArray(value: JsValue): (Int, Option[JsArray]) =
syncPostForJsArray(ip, port, "/run", value)
def runMultiple(values: Seq[JsValue])(implicit ec: ExecutionContext):
Seq[(Int, Option[JsObject])] =
concurrentSyncPost(ip, port, "/run", values)
}
@@ -252,6 +254,17 @@ object ActionContainer {
org.apache.openwhisk.core.containerpool.AkkaContainerClient.post(host,
port, endPoint, content, 30.seconds)
}
+
+ private def syncPostForJsArray(host: String, port: Int, endPoint: String,
content: JsValue)(
+ implicit logging: Logging,
+ as: ActorSystem): (Int, Option[JsArray]) = {
+
+ implicit val transid = TransactionId.testing
+
+ org.apache.openwhisk.core.containerpool.AkkaContainerClient
+ .postForJsArray(host, port, endPoint, content, 30.seconds)
+ }
+
private def concurrentSyncPost(host: String, port: Int, endPoint: String,
contents: Seq[JsValue])(
implicit logging: Logging,
as: ActorSystem): Seq[(Int, Option[JsObject])] = {
diff --git a/tests/src/test/scala/actionContainers/BasicActionRunnerTests.scala
b/tests/src/test/scala/actionContainers/BasicActionRunnerTests.scala
index 547ceebf6..17727704e 100644
--- a/tests/src/test/scala/actionContainers/BasicActionRunnerTests.scala
+++ b/tests/src/test/scala/actionContainers/BasicActionRunnerTests.scala
@@ -173,7 +173,7 @@ trait BasicActionRunnerTests extends
ActionProxyContainerTestUtils {
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."))))
+ out should be(Some(JsObject("error" -> JsString("The action did not
return a dictionary or array."))))
}
checkStreams(out, err, {
diff --git a/tests/src/test/scala/common/WskTestHelpers.scala
b/tests/src/test/scala/common/WskTestHelpers.scala
index 419fc673a..69b01c6f4 100644
--- a/tests/src/test/scala/common/WskTestHelpers.scala
+++ b/tests/src/test/scala/common/WskTestHelpers.scala
@@ -76,7 +76,7 @@ object FullyQualifiedNames {
* @param status a string used to indicate the status of the action
* @param success a boolean value used to indicate whether the action is
executed successfully or not
*/
-case class ActivationResponse(result: Option[JsObject], status: String,
success: Boolean)
+case class ActivationResponse(result: Option[JsValue], status: String,
success: Boolean)
object ActivationResponse extends DefaultJsonProtocol {
implicit val serdes = jsonFormat3(ActivationResponse.apply)
diff --git a/tests/src/test/scala/invokerShoot/ShootInvokerTests.scala
b/tests/src/test/scala/invokerShoot/ShootInvokerTests.scala
index 4330695f5..28d7dea47 100644
--- a/tests/src/test/scala/invokerShoot/ShootInvokerTests.scala
+++ b/tests/src/test/scala/invokerShoot/ShootInvokerTests.scala
@@ -307,7 +307,7 @@ class ShootInvokerTests extends TestHelpers with
WskTestHelpers with JsHelpers w
val run = wsk.action.invoke(name, Map("payload" -> "google.com".toJson))
withActivation(wsk.activation, run) { activation =>
val result = activation.response.result.get
- result.getFields("stdout", "code") match {
+ result.asJsObject.getFields("stdout", "code") match {
case Seq(JsString(stdout), JsNumber(code)) =>
stdout should not include "bytes from"
code.intValue should not be 0
diff --git
a/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskRestBasicUsageTests.scala
b/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskRestBasicUsageTests.scala
index 6e0cbb04f..8df5ad7ba 100644
---
a/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskRestBasicUsageTests.scala
+++
b/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskRestBasicUsageTests.scala
@@ -224,7 +224,7 @@ class WskRestBasicUsageTests extends TestHelpers with
WskTestHelpers with WskAct
withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
val response = activation.response
- response.result.get.fields("error") shouldBe
Messages.abnormalInitialization.toJson
+ response.result.get.asJsObject.fields("error") shouldBe
Messages.abnormalInitialization.toJson
response.status shouldBe
ActivationResponse.messageForCode(ActivationResponse.DeveloperError)
}
}
@@ -238,7 +238,7 @@ class WskRestBasicUsageTests extends TestHelpers with
WskTestHelpers with WskAct
withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
val response = activation.response
- response.result.get.fields("error") shouldBe
Messages.timedoutActivation(3 seconds, true).toJson
+ response.result.get.asJsObject.fields("error") shouldBe
Messages.timedoutActivation(3 seconds, true).toJson
response.status shouldBe
ActivationResponse.messageForCode(ActivationResponse.DeveloperError)
}
}
@@ -252,7 +252,7 @@ class WskRestBasicUsageTests extends TestHelpers with
WskTestHelpers with WskAct
withActivation(wsk.activation, wsk.action.invoke(name)) { activation =>
val response = activation.response
- response.result.get.fields("error") shouldBe
Messages.abnormalRun.toJson
+ response.result.get.asJsObject.fields("error") shouldBe
Messages.abnormalRun.toJson
response.status shouldBe
ActivationResponse.messageForCode(ActivationResponse.DeveloperError)
}
}
@@ -326,7 +326,7 @@ class WskRestBasicUsageTests extends TestHelpers with
WskTestHelpers with WskAct
val run = wsk.action.invoke(name)
withActivation(wsk.activation, run) { activation =>
activation.response.status shouldBe
ActivationResponse.messageForCode(ActivationResponse.DeveloperError)
- activation.response.result.get
+ activation.response.result.get.asJsObject
.fields("error") shouldBe s"Failed to pull container image
'$containerName'.".toJson
activation.annotations shouldBe defined
val limits =
activation.annotations.get.filter(_.fields("key").convertTo[String] == "limits")
@@ -775,7 +775,7 @@ class WskRestBasicUsageTests extends TestHelpers with
WskTestHelpers with WskAct
withActivation(wsk.activation, ruleActivation.activationId) {
actionActivation =>
actionActivation.response.result match {
case Some(result) =>
- result.fields.get("args") map { headers =>
+ result.asJsObject.fields.get("args") map { headers =>
headers.asJsObject.fields.get("__ow_headers") map { params =>
params.asJsObject.fields.get("foo") map { foo =>
foo shouldBe JsString("Bar")
diff --git
a/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerProxyTests.scala
b/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerProxyTests.scala
index 193f9f66a..1b4567a2d 100644
---
a/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerProxyTests.scala
+++
b/tests/src/test/scala/org/apache/openwhisk/core/containerpool/test/ContainerProxyTests.scala
@@ -1338,7 +1338,7 @@ class ContainerProxyTests
timeout) {
val container = new TestContainer {
override def run(
- parameters: JsObject,
+ parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
@@ -1519,7 +1519,7 @@ class ContainerProxyTests
timeout) {
val container = new TestContainer {
override def run(
- parameters: JsObject,
+ parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
@@ -1688,7 +1688,7 @@ class ContainerProxyTests
it should "resend the job to the parent if /run fails connection after
Paused -> Running" in within(timeout) {
val container = new TestContainer {
override def run(
- parameters: JsObject,
+ parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
@@ -1740,7 +1740,7 @@ class ContainerProxyTests
it should "resend the job to the parent if /run fails connection after Ready
-> Running" in within(timeout) {
val container = new TestContainer {
override def run(
- parameters: JsObject,
+ parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
@@ -2160,7 +2160,7 @@ class ContainerProxyTests
initPromise.map(_.future).getOrElse(Future.successful(initInterval))
}
override def run(
- parameters: JsObject,
+ parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
diff --git
a/tests/src/test/scala/org/apache/openwhisk/core/containerpool/v2/test/FunctionPullingContainerProxyTests.scala
b/tests/src/test/scala/org/apache/openwhisk/core/containerpool/v2/test/FunctionPullingContainerProxyTests.scala
index e2e553e78..8f6561218 100644
---
a/tests/src/test/scala/org/apache/openwhisk/core/containerpool/v2/test/FunctionPullingContainerProxyTests.scala
+++
b/tests/src/test/scala/org/apache/openwhisk/core/containerpool/v2/test/FunctionPullingContainerProxyTests.scala
@@ -1737,7 +1737,7 @@ class FunctionPullingContainerProxyTests
val dataManagementService = TestProbe()
val container = new TestContainer {
- override def run(parameters: JsObject,
+ override def run(parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
@@ -1810,7 +1810,7 @@ class FunctionPullingContainerProxyTests
val dataManagementService = TestProbe()
val container = new TestContainer {
- override def run(parameters: JsObject,
+ override def run(parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
@@ -2418,7 +2418,7 @@ class FunctionPullingContainerProxyTests
val dataManagementService = TestProbe()
val container = new TestContainer {
- override def run(parameters: JsObject,
+ override def run(parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
@@ -2816,7 +2816,7 @@ class FunctionPullingContainerProxyTests
}
override def run(
- parameters: JsObject,
+ parameters: JsValue,
environment: JsObject,
timeout: FiniteDuration,
concurrent: Int,
diff --git
a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ConductorsApiTests.scala
b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ConductorsApiTests.scala
index 1645b887e..7c87e1121 100644
---
a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ConductorsApiTests.scala
+++
b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ConductorsApiTests.scala
@@ -322,10 +322,16 @@ class ConductorsApiTests extends ControllerTestCommon
with WhiskActionsApi {
class FakeLoadBalancerService(config: WhiskConfig)(implicit ec:
ExecutionContext)
extends DegenerateLoadBalancerService(config) {
- private def respond(action: ExecutableWhiskActionMetaData, msg:
ActivationMessage, result: JsObject) = {
- val response =
- if (result.fields.get("error") isDefined)
ActivationResponse(ActivationResponse.ApplicationError, Some(result))
- else ActivationResponse.success(Some(result))
+ private def respond(action: ExecutableWhiskActionMetaData, msg:
ActivationMessage, result: JsValue) = {
+ val response = {
+ result match {
+ case JsObject(fields) =>
+ if (fields.get("error") isDefined)
+ ActivationResponse(ActivationResponse.ApplicationError,
Some(result))
+ else ActivationResponse.success(Some(result))
+ case _ => ActivationResponse.success(Some(result))
+ }
+ }
val start = Instant.now
WhiskActivation(
action.namespace,
@@ -345,26 +351,39 @@ class ConductorsApiTests extends ControllerTestCommon
with WhiskActionsApi {
case `echo` => // echo action
Future(Right(respond(action, msg, args)))
case `conductor` => // see tests/dat/actions/conductor.js
- val result =
- if (args.fields.get("error") isDefined) args
- else {
- val action = args.fields.get("action") map { action =>
- Map("action" -> action)
- } getOrElse Map.empty
- val state = args.fields.get("state") map { state =>
- Map("state" -> state)
- } getOrElse Map.empty
- val wrappedParams = args.fields.getOrElse("params",
JsObject.empty).asJsObject.fields
- val escapedParams = args.fields - "action" - "state" -
"params"
- val params = Map("params" -> JsObject(wrappedParams ++
escapedParams))
- JsObject(params ++ action ++ state)
+ val result = {
+ args match {
+ case JsObject(fields) =>
+ if (fields.get("error") isDefined) args
+ else {
+ val action = fields.get("action") map { action =>
+ Map("action" -> action)
+ } getOrElse Map.empty
+ val state = fields.get("state") map { state =>
+ Map("state" -> state)
+ } getOrElse Map.empty
+ val wrappedParams = fields.getOrElse("params",
JsObject.empty).asJsObject.fields
+ val escapedParams = fields - "action" - "state" -
"params"
+ val params = Map("params" -> JsObject(wrappedParams ++
escapedParams))
+ JsObject(params ++ action ++ state)
+ }
+ case _ => JsObject.empty
}
+
+ }
Future(Right(respond(action, msg, result)))
case `step` => // see tests/dat/actions/step.js
- val result = args.fields.get("n") map { n =>
- JsObject("n" -> (n.convertTo[BigDecimal] + 1).toJson)
- } getOrElse {
- JsObject("error" -> "missing parameter".toJson)
+ val result = {
+ args match {
+ case JsObject(fields) =>
+ fields.get("n") map { n =>
+ JsObject("n" -> (n.convertTo[BigDecimal] + 1).toJson)
+ } getOrElse {
+ JsObject("error" -> "missing parameter".toJson)
+ }
+ case _ => JsObject.empty
+ }
+
}
Future(Right(respond(action, msg, result)))
case _ =>
diff --git
a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/WebActionsApiTests.scala
b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/WebActionsApiTests.scala
index ddf7e3827..f0eb668df 100644
---
a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/WebActionsApiTests.scala
+++
b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/WebActionsApiTests.scala
@@ -261,7 +261,7 @@ trait WebActionsApiBaseTests extends ControllerTestCommon
with BeforeAndAfterEac
override protected[controller] def invokeAction(
user: Identity,
action: WhiskActionMetaData,
- payload: Option[JsObject],
+ payload: Option[JsValue],
waitForResponse: Option[FiniteDuration],
cause: Option[ActivationId])(implicit transid: TransactionId):
Future[Either[ActivationId, WhiskActivation]] = {
invocationCount = invocationCount + 1
@@ -271,10 +271,14 @@ trait WebActionsApiBaseTests extends ControllerTestCommon
with BeforeAndAfterEac
// 1. the package name for the action (to confirm that this resolved to
systemId)
// 2. the action name (to confirm that this resolved to the expected
action)
// 3. the payload received by the action which consists of the
action.params + payload
+ val content = payload match {
+ case Some(JsObject(fields)) =>
action.parameters.merge(Some(JsObject(fields)))
+ case _ => Some(action.parameters.toJsObject)
+ }
val result = actionResult getOrElse JsObject(
"pkg" -> action.namespace.toJson,
"action" -> action.name.toJson,
- "content" -> action.parameters.merge(payload).get)
+ "content" -> content.get)
val activation = WhiskActivation(
action.namespace,
diff --git
a/tests/src/test/scala/org/apache/openwhisk/core/limits/ActionLimitsTests.scala
b/tests/src/test/scala/org/apache/openwhisk/core/limits/ActionLimitsTests.scala
index 3617d0c3f..989096e55 100644
---
a/tests/src/test/scala/org/apache/openwhisk/core/limits/ActionLimitsTests.scala
+++
b/tests/src/test/scala/org/apache/openwhisk/core/limits/ActionLimitsTests.scala
@@ -225,7 +225,7 @@ class ActionLimitsTests extends TestHelpers with
WskTestHelpers with WskActorSys
withActivation(wsk.activation, run) { result =>
withClue("Activation result not as expected:") {
result.response.status shouldBe
ActivationResponse.messageForCode(ActivationResponse.DeveloperError)
- result.response.result.get.fields("error") shouldBe {
+ result.response.result.get.asJsObject.fields("error") shouldBe {
Messages.timedoutActivation(allowedActionDuration, init =
false).toJson
}
result.duration.toInt should be >=
allowedActionDuration.toMillis.toInt
@@ -314,7 +314,7 @@ class ActionLimitsTests extends TestHelpers with
WskTestHelpers with WskActorSys
val response = activation.response
response.success shouldBe false
response.status shouldBe
ActivationResponse.messageForCode(ActivationResponse.ApplicationError)
- val msg =
response.result.get.fields(ActivationResponse.ERROR_FIELD).convertTo[String]
+ val msg =
response.result.get.asJsObject.fields(ActivationResponse.ERROR_FIELD).convertTo[String]
val expected = Messages.truncatedResponse((allowedSize + 10).B,
allowedSize.B)
withClue(s"is: ${msg.take(expected.length)}\nexpected: $expected") {
msg.startsWith(expected) shouldBe true
@@ -404,7 +404,7 @@ class ActionLimitsTests extends TestHelpers with
WskTestHelpers with WskActorSys
withActivation(wsk.activation, run) { activation =>
activation.response.success shouldBe false
- val error = activation.response.result.get.fields("error").asJsObject
+ val error =
activation.response.result.get.asJsObject.fields("error").asJsObject
error.fields("filesToOpen") shouldBe (openFileLimit + 1).toJson
error.fields("message") shouldBe {
@@ -471,7 +471,7 @@ class ActionLimitsTests extends TestHelpers with
WskTestHelpers with WskActorSys
val payload = MemoryLimit.MIN_MEMORY.toMB * 2
val run = wsk.action.invoke(name, Map("payload" -> payload.toJson))
withActivation(wsk.activation, run) {
- _.response.result.get.fields("error") shouldBe
Messages.memoryExhausted.toJson
+ _.response.result.get.asJsObject.fields("error") shouldBe
Messages.memoryExhausted.toJson
}
}
@@ -494,7 +494,7 @@ class ActionLimitsTests extends TestHelpers with
WskTestHelpers with WskActorSys
withActivation(wsk.activation, run) { result =>
withClue("Activation result not as expected:") {
result.response.status shouldBe
ActivationResponse.messageForCode(ActivationResponse.DeveloperError)
- result.response.result.get
+ result.response.result.get.asJsObject
.fields("error") shouldBe
Messages.timedoutActivation(allowedActionDuration, init = false).toJson
val logs = result.logs.get
logs.last should include(Messages.logWarningDeveloperError)
diff --git a/tests/src/test/scala/system/basic/WskActionTests.scala
b/tests/src/test/scala/system/basic/WskActionTests.scala
index 4b4d60376..a7a5cd89a 100644
--- a/tests/src/test/scala/system/basic/WskActionTests.scala
+++ b/tests/src/test/scala/system/basic/WskActionTests.scala
@@ -313,7 +313,7 @@ class WskActionTests extends TestHelpers with
WskTestHelpers with JsHelpers with
val run = wsk.action.invoke(name, Map("payload" -> "google.com".toJson))
withActivation(wsk.activation, run) { activation =>
val result = activation.response.result.get
- result.getFields("stdout", "code") match {
+ result.asJsObject.getFields("stdout", "code") match {
case Seq(JsString(stdout), JsNumber(code)) =>
stdout should not include "bytes from"
code.intValue should not be 0
@@ -392,4 +392,18 @@ class WskActionTests extends TestHelpers with
WskTestHelpers with JsHelpers with
}
}
+ it should "invoke an action with a array result" in
withAssetCleaner(wskprops) { (wp, assetHelper) =>
+ val name = "helloArray"
+ assetHelper.withCleaner(wsk.action, name) { (action, _) =>
+ action.create(name,
Some(TestUtils.getTestActionFilename("helloArray.js")))
+ }
+
+ val run = wsk.action.invoke(name)
+ withActivation(wsk.activation, run) { activation =>
+ activation.response.status shouldBe "success"
+ activation.response.result shouldBe Some(
+ JsArray(JsObject("key1" -> JsString("value1")), JsObject("key2" ->
JsString("value2"))))
+ }
+ }
+
}
diff --git a/tests/src/test/scala/system/basic/WskConductorTests.scala
b/tests/src/test/scala/system/basic/WskConductorTests.scala
index c45f053fe..7f4406d24 100644
--- a/tests/src/test/scala/system/basic/WskConductorTests.scala
+++ b/tests/src/test/scala/system/basic/WskConductorTests.scala
@@ -102,7 +102,7 @@ class WskConductorTests extends TestHelpers with
WskTestHelpers with JsHelpers w
wsk.action.invoke(echo, Map("payload" -> testString.toJson, "action"
-> invalid.toJson))
withActivation(wsk.activation, invalidrun) { activation =>
activation.response.status shouldBe "application error"
- activation.response.result.get.fields.get("error") shouldBe Some(
+ activation.response.result.get.asJsObject.fields.get("error") shouldBe
Some(
JsString(compositionComponentInvalid(JsString(invalid))))
checkConductorLogsAndAnnotations(activation, 2) // echo
}
@@ -113,7 +113,7 @@ class WskConductorTests extends TestHelpers with
WskTestHelpers with JsHelpers w
withActivation(wsk.activation, undefinedrun) { activation =>
activation.response.status shouldBe "application error"
- activation.response.result.get.fields.get("error") shouldBe Some(
+ activation.response.result.get.asJsObject.fields.get("error") shouldBe
Some(
JsString(compositionComponentNotFound(s"$namespace/$missing")))
checkConductorLogsAndAnnotations(activation, 2) // echo
}
diff --git a/tests/src/test/scala/system/basic/WskSequenceTests.scala
b/tests/src/test/scala/system/basic/WskSequenceTests.scala
index 73728b92e..d89017662 100644
--- a/tests/src/test/scala/system/basic/WskSequenceTests.scala
+++ b/tests/src/test/scala/system/basic/WskSequenceTests.scala
@@ -73,9 +73,9 @@ class WskSequenceTests extends TestHelpers with
WskTestHelpers with StreamLoggin
checkSequenceLogsAndAnnotations(activation, 4) // 4 activations in
this sequence
activation.cause shouldBe None // topmost sequence
val result = activation.response.result.get
- result.fields.get("payload") shouldBe defined
- result.fields.get("length") should not be defined
- result.fields.get("lines") shouldBe Some(JsArray(Vector(now.toJson)))
+ result.asJsObject.fields.get("payload") shouldBe defined
+ result.asJsObject.fields.get("length") should not be defined
+ result.asJsObject.fields.get("lines") shouldBe
Some(JsArray(Vector(now.toJson)))
}
// update action sequence and run it with normal payload
@@ -90,8 +90,8 @@ class WskSequenceTests extends TestHelpers with
WskTestHelpers with StreamLoggin
withActivation(wsk.activation, secondrun, totalWait = 2 *
allowedActionDuration) { activation =>
checkSequenceLogsAndAnnotations(activation, 2) // 2 activations in
this sequence
val result = activation.response.result.get
- result.fields.get("length") shouldBe Some(2.toJson)
- result.fields.get("lines") shouldBe Some(args.sortWith(_.compareTo(_)
< 0).toArray.toJson)
+ result.asJsObject.fields.get("length") shouldBe Some(2.toJson)
+ result.asJsObject.fields.get("lines") shouldBe
Some(args.sortWith(_.compareTo(_) < 0).toArray.toJson)
}
// run sequence with error in the payload
@@ -102,8 +102,8 @@ class WskSequenceTests extends TestHelpers with
WskTestHelpers with StreamLoggin
withActivation(wsk.activation, thirdrun, totalWait = 2 *
allowedActionDuration) { activation =>
checkSequenceLogsAndAnnotations(activation, 2) // 2 activations in
this sequence
val result = activation.response.result.get
- result.fields.get("length") shouldBe Some(2.toJson)
- result.fields.get("lines") shouldBe Some(args.sortWith(_.compareTo(_)
< 0).toArray.toJson)
+ result.asJsObject.fields.get("length") shouldBe Some(2.toJson)
+ result.asJsObject.fields.get("lines") shouldBe
Some(args.sortWith(_.compareTo(_) < 0).toArray.toJson)
}
}
@@ -141,9 +141,9 @@ class WskSequenceTests extends TestHelpers with
WskTestHelpers with StreamLoggin
checkSequenceLogsAndAnnotations(activation, 3) // 3 activations in this
sequence
activation.cause shouldBe None // topmost sequence
val result = activation.response.result.get
- result.fields.get("payload") shouldBe defined
- result.fields.get("length") should not be defined
- result.fields.get("lines") shouldBe Some(JsArray(Vector(now.toJson)))
+ result.asJsObject.fields.get("payload") shouldBe defined
+ result.asJsObject.fields.get("length") should not be defined
+ result.asJsObject.fields.get("lines") shouldBe
Some(JsArray(Vector(now.toJson)))
}
}
@@ -186,7 +186,7 @@ class WskSequenceTests extends TestHelpers with
WskTestHelpers with StreamLoggin
withActivation(wsk.activation, run, totalWait = 2 * allowedActionDuration)
{ activation =>
checkSequenceLogsAndAnnotations(activation, 3) // 3 activations in this
sequence
val result = activation.response.result.get
- result.fields.get("payload") shouldBe Some(argsJson)
+ result.asJsObject.fields.get("payload") shouldBe Some(argsJson)
}
// update x with limit echo
val limit: Int = {
@@ -203,7 +203,7 @@ class WskSequenceTests extends TestHelpers with
WskTestHelpers with StreamLoggin
activation.response.status shouldBe ("application error")
checkSequenceLogsAndAnnotations(activation, 2)
val result = activation.response.result.get
- result.fields.get("error") shouldBe Some(JsString(sequenceIsTooLong))
+ result.asJsObject.fields.get("error") shouldBe
Some(JsString(sequenceIsTooLong))
// check that inner sequence had only (limit - 1) activations
val innerSeq = activation.logs.get(1) // the id of the inner sequence
activation
val getInnerSeq = wsk.activation.get(Some(innerSeq))
@@ -541,6 +541,31 @@ class WskSequenceTests extends TestHelpers with
WskTestHelpers with StreamLoggin
}
}
+ it should "invoke a sequence which supports array result" in
withAssetCleaner(wskprops) { (wp, assetHelper) =>
+ val name = "sequence-array"
+ val actions = Seq("split-array", "sort-array")
+ for (actionName <- actions) {
+ val file = TestUtils.getTestActionFilename(s"$actionName.js")
+ assetHelper.withCleaner(wsk.action, actionName) { (action, _) =>
+ action.create(name = actionName, artifact = Some(file), timeout =
Some(allowedActionDuration))
+ }
+ }
+
+ assetHelper.withCleaner(wsk.action, name) {
+ val sequence = actions.mkString(",")
+ (action, _) =>
+ action.create(name, Some(sequence), kind = Some("sequence"), timeout =
Some(allowedActionDuration))
+ }
+
+ val args = Array("bbb", "aaa", "ccc")
+ val run = wsk.action.invoke(name, Map("payload" ->
args.mkString("\n").toJson))
+ withActivation(wsk.activation, run, totalWait = 2 * allowedActionDuration)
{ activation =>
+ checkSequenceLogsAndAnnotations(activation, 2) // 2 activations in this
sequence
+ activation.cause shouldBe None // topmost sequence
+ activation.response.result shouldBe Some(JsArray(JsString("aaa"),
JsString("bbb"), JsString("ccc")))
+ }
+ }
+
/**
* checks the result of an echo sequence connected to a trigger through a
rule
* @param triggerFireRun the run result of firing the trigger
@@ -553,7 +578,7 @@ class WskSequenceTests extends TestHelpers with
WskTestHelpers with StreamLoggin
withActivation(wsk.activation, ruleActivation.activationId) {
actionActivation =>
actionActivation.response.result match {
case Some(result) =>
- val (_, part2) = result.fields partition (p => p._1 ==
"__ow_headers") // excluding headers
+ val (_, part2) = result.asJsObject.fields partition (p => p._1 ==
"__ow_headers") // excluding headers
JsObject(part2) shouldBe triggerPayload
case others =>
fail(s"no result found: $others")
diff --git a/tests/src/test/scala/system/basic/WskUnicodeTests.scala
b/tests/src/test/scala/system/basic/WskUnicodeTests.scala
index a217b9a1b..67a2a749d 100644
--- a/tests/src/test/scala/system/basic/WskUnicodeTests.scala
+++ b/tests/src/test/scala/system/basic/WskUnicodeTests.scala
@@ -88,8 +88,8 @@ class WskUnicodeTests extends TestHelpers with WskTestHelpers
with JsHelpers wit
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("❄
☃ ❄")))
+ response.result.get.asJsObject.fields.get("error") shouldBe empty
+ response.result.get.asJsObject.fields.get("winter") should
be(Some(JsString("❄ ☃ ❄")))
activation.logs.toList.flatten.mkString(" ") should include("❄ ☃ ❄")
}