diff --git a/.github/workflows/tck-test.yml b/.github/workflows/tck-test.yml index 5e418413..6bc98756 100644 --- a/.github/workflows/tck-test.yml +++ b/.github/workflows/tck-test.yml @@ -2,9 +2,9 @@ name: TCK Tests on: push: - branches: [ "main" ] + branches: [ "main", "test_patrick_changes" ] pull_request: - branches: [ "main" ] + branches: [ "main", "test_patrick_changes" ] permissions: contents: read @@ -88,7 +88,6 @@ jobs: echo "Running Test: $filename" behave --format json --outfile "./reports/${filename}.json" --format html --outfile "./reports/${filename}.html" "$full_path" echo "Finished Test: $filename" - sleep 70 done - name: Get Behave Scripts uses: actions/github-script@v6 diff --git a/dispatcher/dispatcher.py b/dispatcher/dispatcher.py index f743f305..b2dbc800 100644 --- a/dispatcher/dispatcher.py +++ b/dispatcher/dispatcher.py @@ -27,7 +27,9 @@ import logging import selectors import socket -from threading import Thread, Lock +import sys +from threading import Lock +from typing import Set logging.basicConfig(format='%(levelname)s| %(filename)s:%(lineno)s %(message)s') logger = logging.getLogger('File:Line# Debugger') @@ -38,20 +40,23 @@ class Dispatcher: """ - Dispatcher class handles incoming connections and forwards messages - to all connected up-clients. - """ + Dispatcher class handles incoming connections and forwards messages + to all connected up-clients. + """ def __init__(self): """ Initialize the Dispatcher class. """ self.selector = selectors.DefaultSelector() - self.connected_sockets = set() + self.connected_sockets: Set[socket.socket] = set() self.lock = Lock() + self.server = None # Create server socket self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if sys.platform != "win32": + self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server.bind(DISPATCHER_ADDR) self.server.listen(100) self.server.setblocking(False) @@ -60,13 +65,17 @@ def __init__(self): # Register server socket for accepting connections self.selector.register(self.server, selectors.EVENT_READ, self._accept_client_conn) + + # Cleanup essentials + self.dispatcher_exit = False - def _accept_client_conn(self, server): + def _accept_client_conn(self, server: socket.socket): """ Callback function for accepting up-client connections. :param server: The server socket. """ + up_client_socket, _ = server.accept() logger.info(f'accepted conn. {up_client_socket.getpeername()}') @@ -76,7 +85,7 @@ def _accept_client_conn(self, server): # Register socket for receiving data self.selector.register(up_client_socket, selectors.EVENT_READ, self._receive_from_up_client) - def _receive_from_up_client(self, up_client_socket): + def _receive_from_up_client(self, up_client_socket: socket.socket): """ Callback function for receiving data from up-client sockets. @@ -84,61 +93,68 @@ def _receive_from_up_client(self, up_client_socket): """ try: - recv_data = up_client_socket.recv(BYTES_MSG_LENGTH) + recv_data: bytes = up_client_socket.recv(BYTES_MSG_LENGTH) - if not recv_data or recv_data == b"": - self._close_socket(up_client_socket) + if recv_data == b"": + self._close_connected_socket(up_client_socket) return logger.info(f"received data: {recv_data}") - self._flood_to_sockets(up_client_socket, recv_data) + self._flood_to_sockets(recv_data) except: logger.error("Received error while reading data from up-client") - self._close_socket(up_client_socket) + self._close_connected_socket(up_client_socket) - def _flood_to_sockets(self, sender_socket, data): + def _flood_to_sockets(self, data: bytes): """ Flood data from a sender socket to all other connected sockets. :param sender_socket: The socket from which the data is being sent. :param data: The data to be sent. """ - with self.lock: - for up_client_socket in self.connected_sockets.copy(): # copy() to avoid RuntimeError - # if up_client_socket != sender_socket: - try: - up_client_socket.sendall(data) - except ConnectionAbortedError as e: - logger.error(f"Error sending data to {up_client_socket.getpeername()}: {e}") - self._close_socket(up_client_socket) + # for up_client_socket in self.connected_sockets.copy(): # copy() to avoid RuntimeError + for up_client_socket in self.connected_sockets: + try: + up_client_socket.sendall(data) + except ConnectionAbortedError as e: + logger.error(f"Error sending data to {up_client_socket.getpeername()}: {e}") + self._close_connected_socket(up_client_socket) def listen_for_client_connections(self): """ Start listening for client connections and handle events. """ - while True: - events = self.selector.select() + while not self.dispatcher_exit: + events = self.selector.select(timeout=0) for key, _ in events: callback = key.data callback(key.fileobj) - def _close_socket(self, up_client_socket): + def _close_connected_socket(self, up_client_socket: socket.socket): """ Close a client socket and unregister it from the selector. :param up_client_socket: The client socket to be closed. """ logger.info(f"closing socket {up_client_socket.getpeername()}") + with self.lock: + self.connected_sockets.remove(up_client_socket) + + self.selector.unregister(up_client_socket) + up_client_socket.close() + + def close(self): + self.dispatcher_exit = True + for utransport_socket in self.connected_sockets.copy(): + self._close_connected_socket(utransport_socket) + # Close server socket try: - with self.lock: - self.connected_sockets.remove(up_client_socket) - self.selector.unregister(up_client_socket) - up_client_socket.close() - except: - pass - - -if __name__ == '__main__': - dispatcher = Dispatcher() - thread = Thread(target=dispatcher.listen_for_client_connections) - thread.start() + self.selector.unregister(self.server) + self.server.close() + logger.info("Server socket closed!") + except Exception as e: + logger.error(f"Error closing server socket: {e}") + + # Close selector + self.selector.close() + logger.info("Dispatcher closed!") diff --git a/test_agent/java/src/main/java/org/eclipse/uprotocol/ProtoConverter.java b/test_agent/java/src/main/java/org/eclipse/uprotocol/ProtoConverter.java index 9b6e9f84..b3b184dd 100644 --- a/test_agent/java/src/main/java/org/eclipse/uprotocol/ProtoConverter.java +++ b/test_agent/java/src/main/java/org/eclipse/uprotocol/ProtoConverter.java @@ -24,18 +24,24 @@ package org.eclipse.uprotocol; -import com.google.gson.Gson; import com.google.protobuf.ByteString; import com.google.protobuf.Descriptors; -import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; -import com.google.protobuf.util.JsonFormat; - +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.logging.Logger; -public class ProtoConverter { +import org.json.JSONArray; +import org.json.JSONObject; +import java.nio.charset.StandardCharsets; + +public class ProtoConverter { + public static Message dictToProto(Map parentJsonObj, Message.Builder parentProtoObj) { populateFields(parentJsonObj, parentProtoObj); return parentProtoObj.build(); @@ -49,7 +55,7 @@ private static void populateFields(Map jsonObj, Message.Builder if (fieldDescriptor != null) { if (value instanceof String && ((String) value).startsWith("BYTES:")) { - String byteString = ((String) value).substring(7); // Remove 'BYTES:' prefix + String byteString = ((String) value).substring(6); // Remove 'BYTES:' prefix ByteString byteValue = ByteString.copyFromUtf8(byteString); protoObj.setField(fieldDescriptor, byteValue); } else { @@ -95,23 +101,104 @@ private static void setFieldValue(Message.Builder protoObj, Descriptors.FieldDes } break; default: - // Handle other types as needed break; } } + public static JSONObject convertMessageToJSON(Message message) { + JSONObject result = new JSONObject(); + + List allFields = message.getDescriptorForType().getFields(); + for (FieldDescriptor field : allFields) { + String fieldName = field.getName(); + Object defaultOrSetValue = message.getField(field); + Object value = getattr(message, field, defaultOrSetValue); + + if (value instanceof byte[]) { + value = new String((byte[]) value, StandardCharsets.UTF_8); + } + + if (value instanceof Message) { + result.put(fieldName, convertMessageToJSON((Message) value)); + } + else if (field.isRepeated()) { + JSONArray repeated = new JSONArray(); + for(Object subMsg: (List) value) { + if (subMsg instanceof Message) { + repeated.put( convertMessageToJSON((Message) subMsg) ); + } + else{ + repeated.put(subMsg); + } + } + result.put(fieldName, repeated); + + } + else if (field.isRequired() || field.isOptional()) { + result.put(fieldName, value); + } + } + + return result; + } + public static Map convertMessageToMap(Message message) { - Map map; - JsonFormat.Printer printer = JsonFormat.printer().includingDefaultValueFields().preservingProtoFieldNames(); - Gson gson = new Gson(); - - try { - String jsonString = printer.print(message); - map = gson.fromJson(jsonString, Map.class); - } catch (InvalidProtocolBufferException ex) { - map = new HashMap<>(); + Map result = new HashMap<>(); + + List allFields = message.getDescriptorForType().getFields(); + for (FieldDescriptor field : allFields) { + String fieldName = field.getName(); + Object defaultOrSetValue = message.getField(field); + Object value = getattr(message, field, defaultOrSetValue); + if (value instanceof EnumValueDescriptor) { + value = ((EnumValueDescriptor) value).getNumber(); + } + + if (value instanceof ByteString) { + value = ((ByteString) value).toStringUtf8(); + } + + + if (value instanceof Message) { + result.put(fieldName, convertMessageToMap((Message) value)); + } + else if (field.isRepeated()) { + List repeated = new ArrayList<>(); + for(Object subMsg: (List) value) { + if (subMsg instanceof Message) { + repeated.add( convertMessageToMap((Message) subMsg) ); + } + else{ + repeated.add(subMsg); + } + } + result.put(fieldName, repeated); + + } + else if (field.isRequired() || field.isOptional()) { + result.put(fieldName, value); + } + } + + return result; + } + + public static Object getattr(Message message, FieldDescriptor field, Object defaultValue) { + try { + Map fields2Values = message.getAllFields(); + Object value = fields2Values.get(field); + + if (value == null) { + return defaultValue; + } + else { + return value; + } + + }catch (Exception e) { + System.out.println(e); + return defaultValue; } - return map; } diff --git a/test_agent/java/src/main/java/org/eclipse/uprotocol/TestAgent.java b/test_agent/java/src/main/java/org/eclipse/uprotocol/TestAgent.java index 98221f8f..aae49120 100644 --- a/test_agent/java/src/main/java/org/eclipse/uprotocol/TestAgent.java +++ b/test_agent/java/src/main/java/org/eclipse/uprotocol/TestAgent.java @@ -34,6 +34,7 @@ import org.eclipse.uprotocol.uri.validator.UriValidator; import org.eclipse.uprotocol.validation.ValidationResult; import org.eclipse.uprotocol.uuid.serializer.LongUuidSerializer; +import org.eclipse.uprotocol.uuid.factory.UuidFactory; import org.eclipse.uprotocol.v1.*; import org.json.JSONObject; @@ -85,7 +86,8 @@ public static void processMessage(Map jsonData) throws IOExcepti if (actionHandlers.containsKey(action)) { UStatus status = (UStatus) actionHandlers.get(action).handle(jsonData); if (status != null) { - sendToTestManager(status, action); + String testID = (String) jsonData.get("test_id"); + sendToTestManager(status, action, testID); } } } @@ -93,9 +95,19 @@ public static void processMessage(Map jsonData) throws IOExcepti private static void sendToTestManager(Message proto, String action) { // Create a new dictionary JSONObject responseDict = new JSONObject(); + responseDict.put("data", ProtoConverter.convertMessageToMap(proto)); writeDataToTMSocket(responseDict, action); } + + private static void sendToTestManager(Message proto, String action, String received_test_id) { + // Create a new dictionary + JSONObject responseDict = new JSONObject(); + responseDict.put("data", ProtoConverter.convertMessageToMap(proto)); + responseDict.put("test_id", received_test_id); + + writeDataToTMSocket(responseDict, action); + } private static void writeDataToTMSocket(JSONObject responseDict, String action) { responseDict.put("action", action); @@ -113,8 +125,9 @@ private static void writeDataToTMSocket(JSONObject responseDict, String action) } } - + private static void sendToTestManager(Object json, String action) { + // Object json should be str or dict // Create a new dictionary JSONObject responseDict = new JSONObject(); responseDict.put("data", json); @@ -122,10 +135,22 @@ private static void sendToTestManager(Object json, String action) { } + private static void sendToTestManager(Object json, String action, String received_test_id) { + // Create a new dictionary + JSONObject responseDict = new JSONObject(); + responseDict.put("data", json); + responseDict.put("test_id", received_test_id); + + writeDataToTMSocket(responseDict, action); + + } + private static UStatus handleSendCommand(Map jsonData) { UMessage uMessage = (UMessage) ProtoConverter.dictToProto((Map) jsonData.get("data"), UMessage.newBuilder()); - return transport.send(uMessage); + UAttributes uAttributesWithId = uMessage.getAttributes().toBuilder().setId(UuidFactory.Factories.UPROTOCOL.factory().create()).build(); + UMessage uMessageWithId = uMessage.toBuilder().setAttributes(uAttributesWithId).build(); + return transport.send(uMessageWithId); } private static UStatus handleRegisterListenerCommand(Map jsonData) { @@ -146,21 +171,25 @@ private static Object handleInvokeMethodCommand(Map jsonData) { UPayload.newBuilder()); CompletionStage responseFuture = transport.invokeMethod(uri, payload, CallOptions.newBuilder().setTtl(10000).build()); - responseFuture.whenComplete( - (responseMessage, exception) -> sendToTestManager(responseMessage, Constant.RESPONSE_RPC)); + responseFuture.whenComplete((responseMessage, exception) -> { + sendToTestManager(responseMessage, Constant.INVOKE_METHOD_COMMAND, (String) jsonData.get("test_id")); + }); return null; } private static Object handleSerializeUriCommand(Map jsonData) { Map data = (Map) jsonData.get("data"); UUri uri = (UUri) ProtoConverter.dictToProto(data, UUri.newBuilder()); - sendToTestManager(LongUriSerializer.instance().serialize(uri), Constant.SERIALIZE_URI); + String serializedUuri = LongUriSerializer.instance().serialize(uri); + String testID = (String) jsonData.get("test_id"); + sendToTestManager(serializedUuri, Constant.SERIALIZE_URI, testID); return null; } private static Object handleDeserializeUriCommand(Map jsonData) { - sendToTestManager(LongUriSerializer.instance().deserialize(jsonData.get("data").toString()), - Constant.DESERIALIZE_URI); + UUri uri = LongUriSerializer.instance().deserialize(jsonData.get("data").toString()); + String testID = (String) jsonData.get("test_id"); + sendToTestManager(uri, Constant.DESERIALIZE_URI, testID); return null; } @@ -205,11 +234,13 @@ private static Object handleValidateUriCommand(Map jsonData) { ValidationResult status = validatorFunc.apply(uri); String result = status.isSuccess() ? "True" : "False"; String message = status.getMessage(); - sendToTestManager(Map.of("result", result, "message", message), Constant.VALIDATE_URI); + String testID = (String) jsonData.get("test_id"); + sendToTestManager(Map.of("result", result, "message", message), Constant.VALIDATE_URI, testID); } else if (validatorFuncBool != null) { Boolean status = validatorFuncBool.apply(uri); String result = status ? "True" : "False"; - sendToTestManager(Map.of("result", result, "message", ""), Constant.VALIDATE_URI); + String testID = (String) jsonData.get("test_id"); + sendToTestManager(Map.of("result", result, "message", ""), Constant.VALIDATE_URI, testID); } return null; @@ -218,18 +249,22 @@ private static Object handleValidateUriCommand(Map jsonData) { private static Object handleSerializeUuidCommand(Map jsonData) { Map data = (Map) jsonData.get("data"); UUID uuid = (UUID) ProtoConverter.dictToProto(data, UUID.newBuilder()); - sendToTestManager(LongUuidSerializer.instance().serialize(uuid), Constant.SERIALIZE_UUID); + String serializedUUid = LongUuidSerializer.instance().serialize(uuid); + String testID = (String) jsonData.get("test_id"); + sendToTestManager(serializedUUid, Constant.SERIALIZE_UUID, testID); return null; } private static Object handleDeserializeUuidCommand(Map jsonData) { - sendToTestManager(LongUuidSerializer.instance().deserialize(jsonData.get("data").toString()), - Constant.DESERIALIZE_UUID); + + UUID uuid = LongUuidSerializer.instance().deserialize(jsonData.get("data").toString()); + String testID = (String) jsonData.get("test_id"); + sendToTestManager(uuid, Constant.DESERIALIZE_UUID, testID); return null; } private static void handleOnReceive(UMessage uMessage) { - logger.info("Java on_receive called"); + logger.info("Java on_receive called: " + uMessage); if (uMessage.getAttributes().getType().equals(UMessageType.UMESSAGE_TYPE_REQUEST)) { UAttributes reqAttributes = uMessage.getAttributes(); UAttributes uAttributes = UAttributesBuilder.response(reqAttributes.getSink(), reqAttributes.getSource(), @@ -265,10 +300,8 @@ public static void receiveFromTM() { if (readSize < 1) { return; } - logger.info("Received data from test manager: " + readSize); String jsonData = new String(buffer, 0, readSize); - logger.info("Received data from test manager: " + jsonData); // Parse the JSON string using Gson Map jsonMap = gson.fromJson(jsonData, Map.class); diff --git a/test_agent/python/testagent.py b/test_agent/python/testagent.py index b39bad54..a17fcb05 100644 --- a/test_agent/python/testagent.py +++ b/test_agent/python/testagent.py @@ -23,27 +23,30 @@ # SPDX-License-Identifier: Apache-2.0 # # ------------------------------------------------------------------------- +from concurrent.futures import Future import json import logging import socket import sys from threading import Thread +from typing import Any, Dict, List, Union from google.protobuf import any_pb2 -from google.protobuf.json_format import MessageToDict +from google.protobuf.message import Message +from google.protobuf.descriptor import FieldDescriptor from google.protobuf.wrappers_pb2 import StringValue from uprotocol.proto.uattributes_pb2 import UPriority, UMessageType, CallOptions from uprotocol.proto.umessage_pb2 import UMessage from uprotocol.proto.upayload_pb2 import UPayload, UPayloadFormat +from uprotocol.proto.ustatus_pb2 import UStatus from uprotocol.proto.uri_pb2 import UUri from uprotocol.proto.uuid_pb2 import UUID from uprotocol.transport.builder.uattributesbuilder import UAttributesBuilder from uprotocol.transport.ulistener import UListener from uprotocol.uri.serializer.longuriserializer import LongUriSerializer -from uprotocol.uri.serializer.microuriserializer import MicroUriSerializer -from uprotocol.uri.validator.urivalidator import UriValidator -from uprotocol.validation.validationresult import ValidationResult from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer +from uprotocol.uuid.factory.uuidfactory import Factories +from uprotocol.uri.validator.urivalidator import UriValidator import constants as CONSTANTS @@ -60,8 +63,6 @@ class SocketUListener(UListener): def on_receive(self, umsg: UMessage) -> None: logger.info("Listener received") if umsg.attributes.type == UMessageType.UMESSAGE_TYPE_REQUEST: - logger.info("REQUEST RECEIVED") - # send hardcoded response attributes = UAttributesBuilder.response(umsg.attributes.sink, umsg.attributes.source, UPriority.UPRIORITY_CS4, umsg.attributes.id).build() any_obj = any_pb2.Any() @@ -72,12 +73,48 @@ def on_receive(self, umsg: UMessage) -> None: else: send_to_test_manager(umsg, CONSTANTS.RESPONSE_ON_RECEIVE) +def message_to_dict(message: Message) -> Dict[str, Any]: + """Converts protobuf Message to Dict and keeping respective data types + + Args: + message (Message): protobuf Message + + Returns: + Dict[str, Any]: Dict/JSON version of the Message + """ + result: Dict[str, Any] = {} + + all_fields: List[FieldDescriptor] = message.DESCRIPTOR.fields + for field in all_fields: + + value = getattr(message, field.name, field.default_value) + if isinstance(value, bytes): + value: str = value.decode() + + if hasattr(value, 'DESCRIPTOR'): + result[field.name] = message_to_dict(value) + elif field.label == FieldDescriptor.LABEL_REPEATED: + repeated = [] + for sub_msg in value: + if hasattr(sub_msg, 'DESCRIPTOR'): + repeated.append(message_to_dict(sub_msg)) + else: + repeated.append(value) + result[field.name] = repeated -def send_to_test_manager(response, action): + elif field.label == FieldDescriptor.LABEL_REQUIRED or field.label == FieldDescriptor.LABEL_OPTIONAL: + result[field.name] = value + + return result + + +def send_to_test_manager(response: Union[Message, str, dict], action: str, received_test_id: str = ""): if not isinstance(response, (dict, str)): - response = MessageToDict(response, including_default_value_fields=True, preserving_proto_field_name=True) - # Create a new dictionary - response_dict = {'data': response, 'action': action, 'ue': 'python'} + # converts protobuf to dict + response = message_to_dict(response) + + # Create response as json/dict + response_dict = {'data': response, 'action': action, 'ue': 'python', 'test_id': received_test_id} response_dict = json.dumps(response_dict).encode('utf-8') ta_socket.sendall(response_dict) logger.info(f"Sent to TM {response_dict}") @@ -85,16 +122,16 @@ def send_to_test_manager(response, action): def dict_to_proto(parent_json_obj, parent_proto_obj): def populate_fields(json_obj, proto_obj): - for key, value in json_obj.items(): + for field_name, value in json_obj.items(): if 'BYTES:' in value: value = value.replace('BYTES:', '') value = value.encode('utf-8') - if hasattr(proto_obj, key): + if hasattr(proto_obj, field_name): if isinstance(value, dict): # Recursively update the nested message object - populate_fields(value, getattr(proto_obj, key)) + populate_fields(value, getattr(proto_obj, field_name)) else: - field_type = type(getattr(proto_obj, key)) + field_type = type(getattr(proto_obj, field_name)) try: if field_type == int: value = int(value) @@ -102,7 +139,7 @@ def populate_fields(json_obj, proto_obj): value = float(value) except: pass - setattr(proto_obj, key, value) + setattr(proto_obj, field_name, value) return proto_obj populate_fields(parent_json_obj, parent_proto_obj) @@ -112,12 +149,14 @@ def populate_fields(json_obj, proto_obj): def handle_send_command(json_msg): umsg = dict_to_proto(json_msg["data"], UMessage()) + umsg.attributes.id.CopyFrom(Factories.UPROTOCOL.create()) return transport.send(umsg) -def handle_register_listener_command(json_msg): +def handle_register_listener_command(json_msg) -> UStatus: uri = dict_to_proto(json_msg["data"], UUri()) - return transport.register_listener(uri, listener) + status: UStatus = transport.register_listener(uri, listener) + return status def handle_unregister_listener_command(json_msg): @@ -128,22 +167,36 @@ def handle_unregister_listener_command(json_msg): def handle_invoke_method_command(json_msg): uri = dict_to_proto(json_msg["data"], UUri()) payload = dict_to_proto(json_msg["data"]["payload"], UPayload()) - res_future = transport.invoke_method(uri, payload, CallOptions(ttl=10000)) + res_future: Future = transport.invoke_method(uri, payload, CallOptions(ttl=10000)) def handle_response(message): - message = message.result() - send_to_test_manager(message, CONSTANTS.RESPONSE_RPC) + message: Message = message.result() + send_to_test_manager(message, CONSTANTS.INVOKE_METHOD_COMMAND, received_test_id=json_msg["test_id"]) res_future.add_done_callback(handle_response) -def handle_uri_serialize_command(json_msg): - uri = dict_to_proto(json_msg["data"], UUri()) - send_to_test_manager(LongUriSerializer().serialize(uri), CONSTANTS.SERIALIZE_URI) +def send_longserialize_uuri(json_msg: Dict[str, Any]): + uri: UUri = dict_to_proto(json_msg["data"], UUri()) + serialized_uuri: str = LongUriSerializer().serialize(uri) + send_to_test_manager(serialized_uuri, CONSTANTS.SERIALIZE_URI, received_test_id=json_msg["test_id"]) -def handle_uri_deserialize_command(json_msg): - send_to_test_manager(LongUriSerializer().deserialize(json_msg["data"]), CONSTANTS.DESERIALIZE_URI) +def send_longdeserialize_uri(json_msg: Dict[str, Any]): + uuri: UUri = LongUriSerializer().deserialize(json_msg["data"]) + send_to_test_manager(uuri, CONSTANTS.DESERIALIZE_URI, received_test_id=json_msg["test_id"]) + + +def send_longdeserialize_uuid(json_msg: Dict[str, Any]): + uuid: UUID = LongUuidSerializer().deserialize(json_msg["data"]) + send_to_test_manager(uuid, CONSTANTS.DESERIALIZE_UUID, received_test_id=json_msg["test_id"]) + + +def send_longserialize_uuid(json_msg: Dict[str, Any]): + uuid: UUID = dict_to_proto(json_msg["data"], UUID()) + serialized_uuid: str = LongUuidSerializer().serialize(uuid) + send_to_test_manager(serialized_uuid, CONSTANTS.SERIALIZE_UUID, received_test_id=json_msg["test_id"]) + def handle_uri_validate_command(json_msg): val_type = json_msg["data"]["type"] @@ -169,39 +222,30 @@ def handle_uri_validate_command(json_msg): else: result = str(status.is_success()) message = status.get_message() - send_to_test_manager({"result": result, "message": message}, CONSTANTS.VALIDATE_URI) - - - -def handle_uuid_deserialize_command(json_msg): - send_to_test_manager(LongUuidSerializer().deserialize(json_msg["data"]), CONSTANTS.DESERIALIZE_UUID) - - -def handle_uuid_serialize_command(json_msg): - uri = dict_to_proto(json_msg["data"], UUID()) - send_to_test_manager(LongUuidSerializer().serialize(uri), CONSTANTS.SERIALIZE_UUID) + send_to_test_manager({"result": result, "message": message}, CONSTANTS.VALIDATE_URI, received_test_id=json_msg["test_id"]) action_handlers = {CONSTANTS.SEND_COMMAND: handle_send_command, CONSTANTS.REGISTER_LISTENER_COMMAND: handle_register_listener_command, CONSTANTS.UNREGISTER_LISTENER_COMMAND: handle_unregister_listener_command, CONSTANTS.INVOKE_METHOD_COMMAND: handle_invoke_method_command, - CONSTANTS.SERIALIZE_URI: handle_uri_serialize_command, - CONSTANTS.DESERIALIZE_URI: handle_uri_deserialize_command, + CONSTANTS.SERIALIZE_URI: send_longserialize_uuri, + CONSTANTS.DESERIALIZE_URI: send_longdeserialize_uri, + CONSTANTS.SERIALIZE_UUID: send_longserialize_uuid, + CONSTANTS.DESERIALIZE_UUID: send_longdeserialize_uuid, CONSTANTS.VALIDATE_URI: handle_uri_validate_command, - CONSTANTS.SERIALIZE_UUID: handle_uuid_serialize_command, - CONSTANTS.DESERIALIZE_UUID: handle_uuid_deserialize_command } def process_message(json_data): - action = json_data["action"] + action: str = json_data["action"] status = None if action in action_handlers: - status = action_handlers[action](json_data) + status: UStatus = action_handlers[action](json_data) + # For UTransport interface methods if status is not None: - send_to_test_manager(status, action) + send_to_test_manager(status, action, received_test_id=json_data["test_id"]) def receive_from_tm(): diff --git a/test_manager/features/environment.py b/test_manager/features/environment.py index edf6e2e1..132ab351 100644 --- a/test_manager/features/environment.py +++ b/test_manager/features/environment.py @@ -43,6 +43,10 @@ JAVA_TA_PATH = "/test_agent/java/target/tck-test-agent-java-jar-with-dependencies.jar" DISPATCHER_PATH = "/dispatcher/dispatcher.py" +repo = git.Repo('.', search_parent_directories=True) +sys.path.append(repo.working_tree_dir) + +from dispatcher.dispatcher import Dispatcher def create_command(filepath_from_root_repo: str) -> List[str]: command: List[str] = [] @@ -83,19 +87,21 @@ def before_all(context): """ context.on_receive_msg = {} context.on_receive_rpc_response = {} - context.on_receive_validation_result = {} - context.on_receive_validation_msg = {} loggerutils.setup_logging() loggerutils.setup_formatted_logging(context) - command = create_command(DISPATCHER_PATH) - process: subprocess.Popen = create_subprocess(command) - context.dispatcher_process = process + context.logger.info("Creating Dispatcher...") + + dispatcher = Dispatcher() + thread = Thread(target=dispatcher.listen_for_client_connections) + thread.start() + context.dispatcher = dispatcher + context.logger.info("Created Dispatcher...") time.sleep(5) test_manager = TestManager(context, "127.0.0.5", 12345) - thread = Thread(target=test_manager.listen_for_client_connections) + thread = Thread(target=test_manager.listen_for_incoming_events) thread.start() context.tm = test_manager @@ -119,18 +125,16 @@ def after_all(context: Context): context.status_json = None context.on_receive_msg = {} context.on_receive_rpc_response = {} - context.on_receive_validation_result = {} - context.on_receive_validation_msg = {} context.on_receive_serialized_uri = None context.on_receive_deserialized_uri = None context.on_receive_serialized_uuid = None context.on_receive_deserialized_uuid = None - context.tm.close_socket(sdk="python") - context.tm.close_socket(sdk="java") + context.tm.close_test_agent("python") + context.tm.close_test_agent("java") context.tm.close() - + context.dispatcher.close() + context.logger.info("Closed All Test Agents and Test Manager...") try: - context.dispatcher_process.terminate() context.java_ta_process.terminate() context.python_ta_process.terminate() except Exception as e: diff --git a/test_manager/features/steps/tck_step_implementations.py b/test_manager/features/steps/tck_step_implementations.py index 63af55fc..ae8baf3c 100644 --- a/test_manager/features/steps/tck_step_implementations.py +++ b/test_manager/features/steps/tck_step_implementations.py @@ -25,7 +25,10 @@ # ------------------------------------------------------------------------- import base64 import codecs +from typing import Any, Dict +from uprotocol.proto.ustatus_pb2 import UCode import time +import re from behave import when, then, given from behave.runner import Context @@ -35,24 +38,8 @@ @given(u'"{sdk_name}" creates data for "{command}"') @when(u'"{sdk_name}" creates data for "{command}"') def create_sdk_data(context, sdk_name: str, command: str): - context.logger.info("Inside create register listener data") context.json_dict = {} context.status_json = None - if command == "send": - context.on_receive_msg.pop(sdk_name, None) - elif command == "invokemethod": - context.on_receive_rpc_response.pop(sdk_name, None) - elif command == "uri_serialize": - context.on_receive_serialized_uri = None - elif command == "uri_deserialize": - context.on_receive_deserialized_uri = None - elif command == "uri_validate": - context.on_receive_validation_result.pop(sdk_name, None) - context.on_receive_validation_msg.pop(sdk_name, None) - elif command == "uuid_serialize": - context.on_receive_serialized_uuid = None - elif command == "uuid_deserialize": - context.on_receive_deserialized_uuid = None while not context.tm.has_sdk_connection(sdk_name): continue @@ -61,69 +48,125 @@ def create_sdk_data(context, sdk_name: str, command: str): context.action = command -@then(u'the serialized uri received is "{expected_uri}"') -def serialized_uri_received(context, expected_uri): +@then(u'receives uri serialization "{expected_uri}"') +def serialized_uri_received(context, expected_uri: str): try: - rec_field_value = context.on_receive_serialized_uri - assert_that(expected_uri, equal_to(rec_field_value)) + actual_uuid: str = context.response_data + assert_that(expected_uri, equal_to(actual_uuid)) except AssertionError as ae: raise AssertionError(f"Assertion error. Expected is {expected_uri} but " - f"received {context.on_receive_serialized_uri}") + f"received {actual_uuid}") except Exception as ae: raise ValueError(f"Expection occured. {ae}") -@then(u'the serialized uuid received is "{expected_uuid}"') -def serialized_uuid_received(context, expected_uuid): +@then(u'receives uuid serialization "{expected_uuid}"') +def serialized_uuid_received(context, expected_uuid: str): try: - rec_field_value = context.on_receive_serialized_uuid - assert_that(expected_uuid, equal_to(rec_field_value)) + actual_uuid: str = context.response_data + + assert_that(expected_uuid, equal_to(actual_uuid)) except AssertionError as ae: raise AssertionError(f"Assertion error. Expected is {expected_uuid} but " - f"received {context.on_receive_serialized_uuid}") + f"received {actual_uuid}") + except Exception as ae: + raise ValueError(f"Expection occured. {ae}") + +@then(u'receives validation result as "{expected_result}"') +def receive_validation_result(context, expected_result): + try: + expected_result = expected_result.strip() + actual_val_res = context.response_data["result"] + assert_that(expected_result, equal_to(actual_val_res)) + except AssertionError as ae: + raise AssertionError(f"Assertion error. Expected is {expected_result} but " + f"received {repr(actual_val_res)}") except Exception as ae: raise ValueError(f"Expection occured. {ae}") + +@then(u'receives validation message as "{expected_message}"') +def receive_validation_result(context, expected_message): + if expected_message == "none": + return + try: + expected_message = expected_message.strip() + actual_val_msg = context.response_data["message"] + assert_that(expected_message, equal_to(actual_val_msg)) + except AssertionError as ae: + raise AssertionError(f"Assertion error. Expected is {expected_message} but " + f"received {repr(actual_val_msg)}") + except Exception as ae: + raise ValueError(f"Expection occured. {ae}") -@when(u'sends a "{command}" request with the value "{serialized_uri}"') -def send_serialized_command(context, command, serialized_uri): - context.logger.info(f"Json request for {command} -> {serialized_uri}") - context.tm.receive_from_bdd(context.ue, command, serialized_uri) +@when(u'sends a "{command}" request with serialized input "{serialized}"') +def send_serialized_command(context, command: str, serialized: str): + context.logger.info(f"Json request for {command} -> {serialized}") + response_json: Dict[str, Any] = context.tm.request(context.ue, context.action, serialized) + context.logger.info(f"Response Json {command} -> {response_json}") + + if response_json is None: + raise AssertionError("Response from Test Manager is None") + elif 'data' not in response_json: + raise AssertionError("\"data\" field name doesn't exist on top response JSON level") + context.response_data = response_json['data'] @then(u'the deserialized uri received should have the following properties') def verify_uri_received_properties(context): - assert context.on_receive_deserialized_uri is not None - deserialized_uri_dict = flatten_dict(context.on_receive_deserialized_uri) - # Iterate over the rows of the table and verify the received properties + deserialized_uri: Dict[str, Any] = flatten_dict(context.response_data) + context.logger.info(f"deserialized_uri_dict -> {deserialized_uri}") + + # Iterate over the rows of the table and verify the received properties + int_type_fields = set(["entity.id", "entity.version_major", "entity.version_minor", 'resource.id']) + bytes_type_fields = set(["authority.id", "authority.ip"]) + try: for row in context.table: - field = row['Field'] - expected_value = row['Value'] + field: str = row['Field'] + expected_value: str = row['Value'] + context.logger.info(f"field {field}; {deserialized_uri[field]} vs. {expected_value}") if len(expected_value) > 0: - assert_that(deserialized_uri_dict[field], expected_value) + + if field in int_type_fields: + expected_value = int(expected_value) + elif field in bytes_type_fields: + expected_value: bytes = expected_value.encode() + deserialized_uri[field] = str(deserialized_uri[field]).encode() + assert_that(deserialized_uri[field], equal_to(expected_value)) + else: + assert_that(len(deserialized_uri[field]) > 0, equal_to(len(expected_value) > 0)) + except AssertionError as ae: raise AssertionError(f"Assertion error. {ae}") @then(u'the deserialized uuid received should have the following properties') def verify_uuid_received_properties(context): - assert context.on_receive_deserialized_uuid is not None - deserialized_uuid_dict = flatten_dict(context.on_receive_deserialized_uuid) + context.logger.info(f"deserialized context.response_data -> {context.response_data}") + + deserialized_uuid: Dict[str, int]= flatten_dict(context.response_data) + context.logger.info(f"deserialized_uuid_dict -> {deserialized_uuid}") + # Iterate over the rows of the table and verify the received properties + int_type_fields = set(["msb", "lsb"]) try: for row in context.table: field = row['Field'] expected_value = row['Value'] + assert_that(field in deserialized_uuid, equal_to(len(expected_value) > 0)) + if len(expected_value) > 0: - assert_that(deserialized_uuid_dict[field], expected_value) + if field in int_type_fields: + expected_value: int = int(expected_value) + assert_that(deserialized_uuid[field], equal_to(expected_value)) except AssertionError as ae: raise AssertionError(f"Assertion error. {ae}") @given(u'sets "{key}" to "{value}"') @when(u'sets "{key}" to "{value}"') -def set_key_to_val(context: Context, key: str, value: str): +def set_key_to_val(context: Context, key: str, value: str): if key not in context.json_dict: context.json_dict[key] = value @@ -135,7 +178,7 @@ def set_blank_key(context, key): @given(u'sets "{key}" to b"{value}"') @when(u'sets "{key}" to b"{value}"') -def set_key_to_bytes(context, key, value): +def set_key_to_bytes(context, key: str, value: str): if key not in context.json_dict: context.json_dict[key] = "BYTES:" + value @@ -145,88 +188,103 @@ def set_key_to_bytes(context, key, value): def send_command_request(context, command: str): context.json_dict = unflatten_dict(context.json_dict) context.logger.info(f"Json request for {command} -> {str(context.json_dict)}") - context.tm.receive_from_bdd(context.ue, context.action, context.json_dict) - - -@when(u'user waits "{sec}" second') -@then(u'user waits "{sec}" second') -def user_wait(context, sec): - time.sleep(int(sec)) + response_json: Dict[str, Any] = context.tm.request(context.ue, command, context.json_dict) + context.logger.info(f"Response Json {command} -> {response_json}") + context.response_data = response_json['data'] -@then(u'the status received with "{field}" is "{field_value}"') -def receive_status(context, field, field_value): +@then(u'the status received with "{field_name}" is "{expected_value}"') +def receive_status(context, field_name: str, expected_value: str): try: - rec_field_value = context.status_json[field] - assert_that(field_value, equal_to(rec_field_value)) + actual_value: str = context.response_data[field_name] + expected_value: int = getattr(UCode, expected_value) + assert_that(expected_value, equal_to(actual_value)) except AssertionError as ae: - raise AssertionError(f"Assertion error. Expected is {field_value} but " - f"received {context.status_json[field]}") + raise AssertionError(f"Assertion error. Expected is {expected_value} but " + f"received {context.response_data[field_name]}") except Exception as ae: raise ValueError(f"Expection occured. {ae}") -@then(u'"{sdk_name}" receives "{key}" as b"{value}"') -def receive_value_as_bytes(context, sdk_name, key, value): +@then(u'"{sender_sdk_name}" sends onreceive message with field "{field_name}" as b"{expected_value}"') +def receive_value_as_bytes(context, sender_sdk_name: str, field_name: str, expected_value: str): try: - value = value.strip() - val = access_nested_dict(context.on_receive_msg[sdk_name], key) - rec_field_value = base64.b64decode(val.encode('utf-8')) - assert rec_field_value.split(b'googleapis.com/')[1] == value.encode('utf-8').split(b'googleapis.com/')[1] - except KeyError as ke: - raise KeyError(f"Key error. {sdk_name} has not received topic update.") + expected_value = expected_value.strip() + context.logger.info(f"getting on_receive_msg from {sender_sdk_name}") + on_receive_msg: Dict[str, Any] = context.tm.get_onreceive(sender_sdk_name) + context.logger.info(f"got on_receive_msg: {on_receive_msg}") + val = access_nested_dict(on_receive_msg["data"], field_name) + context.logger.info(f"val {field_name}: {val}") + + rec_field_value = val.encode('utf-8') + assert rec_field_value.split(b'googleapis.com/')[1] == expected_value.encode('utf-8').split(b'googleapis.com/')[1] + except AssertionError as ae: - raise AssertionError(f"Assertion error. Expected is {value.encode('utf-8')} but " + raise AssertionError(f"Assertion error. Expected is {expected_value.encode('utf-8')} but " f"received {rec_field_value}") except Exception as ae: raise ValueError(f"Expection occured. {ae}") -@then(u'"{sdk_name}" receives rpc response having "{key}" as b"{value}"') -def receive_rpc_response_as_bytes(context, sdk_name, key, value): +@then(u'"{sdk_name}" receives data field "{field_name}" as b"{expected_value}"') +def receive_rpc_response_as_bytes(context, sdk_name, field_name: str, expected_value: str): try: - val = access_nested_dict(context.on_receive_rpc_response[sdk_name], key) - rec_field_value = base64.b64decode(val.encode('utf-8')) - print(rec_field_value) + actual_value: str = access_nested_dict(context.response_data, field_name) + actual_value: bytes = actual_value.encode('utf-8') + # Convert bytes to byte string with escape sequences - rec_field_value = codecs.encode(rec_field_value.decode('utf-8'), 'unicode_escape') - assert rec_field_value.split(b'googleapis.com/')[1] == value.encode('utf-8').split(b'googleapis.com/')[1] + actual_value = codecs.encode(actual_value.decode('utf-8'), 'unicode_escape') + assert actual_value.split(b'googleapis.com/')[1] == expected_value.encode('utf-8').split(b'googleapis.com/')[1] except KeyError as ke: raise KeyError(f"Key error. {sdk_name} has not received rpc response.") except AssertionError as ae: - raise AssertionError(f"Assertion error. Expected is {value.encode('utf-8')} but " - f"received {repr(rec_field_value)}") + raise AssertionError(f"Assertion error. Expected is {expected_value.encode('utf-8')} but " + f"received {repr(actual_value)}") except Exception as ae: raise ValueError(f"Expection occured. {ae}") -@then(u'"{sdk_name}" receives validation result as "{result}"') -def receive_validation_result(context, sdk_name, result): - try: - result = result.strip() - context.logger.info(context.on_receive_validation_result) - res = context.on_receive_validation_result[sdk_name] - assert result == res - except AssertionError as ae: - raise AssertionError(f"Assertion error. Expected is {result} but " - f"received {repr(res)}") - except Exception as ae: - raise ValueError(f"Expection occured. {ae}") + +def bytes_to_base64_str(b: bytes) -> str: + return base64.b64encode(b).decode("ascii") + +def base64_str_to_bytes(base64_str: str) -> bytes: + base64_bytes: bytes = base64_str.encode("ascii") + return base64.b64decode(base64_bytes) + +@then(u'receives micro serialized uri "{expected_bytes_as_base64_str}"') +def receive_micro_serialized_uuri(context, expected_bytes_as_base64_str: str): + if expected_bytes_as_base64_str == "": + expected_bytes_as_base64_str = "" + expected_bytes: bytes = base64_str_to_bytes(expected_bytes_as_base64_str) + context.logger.info(f"expected_bytes: {expected_bytes}") -@then(u'"{sdk_name}" receives validation message as "{message}"') -def receive_validation_result(context, sdk_name, message): - if message == "none": - return try: - message = message.strip() - msg = context.on_receive_validation_msg[sdk_name] - assert message == msg + actual_bytes_as_str: str = context.response_data + actual_bytes: bytes = actual_bytes_as_str.encode("iso-8859-1") + + context.logger.info(f"actual: {actual_bytes} | expect: {expected_bytes}") + assert_that(expected_bytes, equal_to(actual_bytes)) except AssertionError as ae: - raise AssertionError(f"Assertion error. Expected is {message} but " - f"received {repr(msg)}") + raise AssertionError(f"Assertion error. Expected is {expected_bytes} but " + f"received {actual_bytes}") except Exception as ae: raise ValueError(f"Expection occured. {ae}") +@when(u'sends a "{command}" request with micro serialized input "{micro_serialized_uri_as_base64_str}"') +def send_micro_serialized_command(context, command: str, micro_serialized_uri_as_base64_str: str): + if micro_serialized_uri_as_base64_str == "": + micro_serialized_uri_as_base64_str = "" + + micro_serialized_uri: bytes = base64_str_to_bytes(micro_serialized_uri_as_base64_str) + context.logger.info(f"Json request for {command} -> {micro_serialized_uri}") + + micro_serialized_uri_as_str = micro_serialized_uri.decode("iso-8859-1") + response_json: Dict[str, Any] = context.tm.request(context.ue, command, micro_serialized_uri_as_str) + + context.logger.info(f"Response Json {command} -> {response_json}") + context.response_data = response_json['data'] + def access_nested_dict(dictionary, keys): keys = keys.split('.') value = dictionary diff --git a/test_manager/features/tests/serializers/long_uri_deserializer.feature b/test_manager/features/tests/serializers/long_uri_deserializer.feature index 6b366a5b..a07415e0 100644 --- a/test_manager/features/tests/serializers/long_uri_deserializer.feature +++ b/test_manager/features/tests/serializers/long_uri_deserializer.feature @@ -27,8 +27,8 @@ Feature: Local and Remote URI de-serialization Scenario Outline: Testing the local uri deserializer Given "" creates data for "uri_deserialize" - When sends a "uri_deserialize" request with the value "" - And user waits "1" second + When sends a "uri_deserialize" request with serialized input "" + Then the deserialized uri received should have the following properties: | Field | Value | | entity.name | | @@ -36,58 +36,58 @@ Feature: Local and Remote URI de-serialization | resource.name | | | resource.instance | | | resource.message | | + Examples: | uE1 | entity_name | resource_name | resource_instance | resource_message | entity_version_major | serialized_uri | - | python | neelam | rpc | test | | | /neelam//rpc.test | - | python | neelam | | | | | /neelam | + | python | neelam | rpc | test | | 0 | /neelam//rpc.test | + | python | neelam | | | | 0 | /neelam | | python | neelam | | | | 1 | /neelam/1 | - | python | neelam | test | | | | /neelam//test | + | python | neelam | test | | | 0 | /neelam//test | | python | neelam | test | | | 1 | /neelam/1/test | - | python | neelam | test | front | | | /neelam//test.front | + | python | neelam | test | front | | 0 | /neelam//test.front | | python | neelam | test | front | | 1 | /neelam/1/test.front | - | python | neelam | test | front | Test | | /neelam//test.front#Test | + | python | neelam | test | front | Test | 0 | /neelam//test.front#Test | | python | neelam | test | front | Test | 1 | /neelam/1/test.front#Test | - | java | neelam | rpc | test | | | /neelam//rpc.test | - | java | neelam | | | | | /neelam | + | java | neelam | rpc | test | | 0 | /neelam//rpc.test | + | java | neelam | | | | 0 | /neelam | | java | neelam | | | | 1 | /neelam/1 | - | java | neelam | test | | | | /neelam//test | + | java | neelam | test | | | 0 | /neelam//test | | java | neelam | test | | | 1 | /neelam/1/test | - | java | neelam | test | front | | | /neelam//test.front | + | java | neelam | test | front | | 0 | /neelam//test.front | | java | neelam | test | front | | 1 | /neelam/1/test.front | - | java | neelam | test | front | Test | | /neelam//test.front#Test | + | java | neelam | test | front | Test | 0 | /neelam//test.front#Test | | java | neelam | test | front | Test | 1 | /neelam/1/test.front#Test | Scenario Outline: Testing the remote uri deserializer Given "" creates data for "uri_deserialize" - When sends a "uri_deserialize" request with the value "" - And user waits "1" second + When sends a "uri_deserialize" request with serialized input "" Then the deserialized uri received should have the following properties: | Field | Value | | authority.name | | - | entity.name | | | entity.version_major | | | resource.name | | | resource.instance | | | resource.message | | + Examples: | uE1 | authority_name | entity_name | resource_name | resource_instance | resource_message | entity_version_major | serialized_uri | - | python | vcu.my_car_vin | neelam | | | | | //vcu.my_car_vin/neelam | + | python | vcu.my_car_vin | neelam | | | | 0 | //vcu.my_car_vin/neelam | | python | vcu.my_car_vin | neelam | | | | 1 | //vcu.my_car_vin/neelam/1 | | python | vcu.my_car_vin | neelam | test | | | 1 | //vcu.my_car_vin/neelam/1/test | - | python | vcu.my_car_vin | neelam | test | | | | //vcu.my_car_vin/neelam//test | + | python | vcu.my_car_vin | neelam | test | | | 0 | //vcu.my_car_vin/neelam//test | | python | vcu.my_car_vin | neelam | test | front | | 1 | //vcu.my_car_vin/neelam/1/test.front | - | python | vcu.my_car_vin | neelam | test | front | | | //vcu.my_car_vin/neelam//test.front | + | python | vcu.my_car_vin | neelam | test | front | | 0 | //vcu.my_car_vin/neelam//test.front | | python | vcu.my_car_vin | neelam | test | front | Test | 1 | //vcu.my_car_vin/neelam/1/test.front#Test | - | python | vcu.my_car_vin | neelam | test | front | Test | | //vcu.my_car_vin/neelam//test.front#Test | - | python | vcu.my_car_vin | petapp | rpc | response | | | //vcu.my_car_vin/petapp//rpc.response | - | java | vcu.my_car_vin | neelam | | | | | //vcu.my_car_vin/neelam | + | python | vcu.my_car_vin | neelam | test | front | Test | 0 | //vcu.my_car_vin/neelam//test.front#Test | + | python | vcu.my_car_vin | petapp | rpc | response | | 0 | //vcu.my_car_vin/petapp//rpc.response | + | java | vcu.my_car_vin | neelam | | | | 0 | //vcu.my_car_vin/neelam | | java | vcu.my_car_vin | neelam | | | | 1 | //vcu.my_car_vin/neelam/1 | | java | vcu.my_car_vin | neelam | test | | | 1 | //vcu.my_car_vin/neelam/1/test | - | java | vcu.my_car_vin | neelam | test | | | | //vcu.my_car_vin/neelam//test | + | java | vcu.my_car_vin | neelam | test | | | 0 | //vcu.my_car_vin/neelam//test | | java | vcu.my_car_vin | neelam | test | front | | 1 | //vcu.my_car_vin/neelam/1/test.front | - | java | vcu.my_car_vin | neelam | test | front | | | //vcu.my_car_vin/neelam//test.front | + | java | vcu.my_car_vin | neelam | test | front | | 0 | //vcu.my_car_vin/neelam//test.front | | java | vcu.my_car_vin | neelam | test | front | Test | 1 | //vcu.my_car_vin/neelam/1/test.front#Test | - | java | vcu.my_car_vin | neelam | test | front | Test | | //vcu.my_car_vin/neelam//test.front#Test | - | java | vcu.my_car_vin | petapp | rpc | response | | | //vcu.my_car_vin/petapp//rpc.response | + | java | vcu.my_car_vin | neelam | test | front | Test | 0 | //vcu.my_car_vin/neelam//test.front#Test | + | java | vcu.my_car_vin | petapp | rpc | response | | 0 | //vcu.my_car_vin/petapp//rpc.response | diff --git a/test_manager/features/tests/serializers/long_uri_serializer.feature b/test_manager/features/tests/serializers/long_uri_serializer.feature index bccddb1b..37746226 100644 --- a/test_manager/features/tests/serializers/long_uri_serializer.feature +++ b/test_manager/features/tests/serializers/long_uri_serializer.feature @@ -32,28 +32,29 @@ Feature: Local and Remote URI serialization And sets "resource.name" to "" And sets "resource.instance" to "" And sets "resource.message" to "" + When sends "uri_serialize" request - And user waits "1" second - Then the serialized uri received is "" + Then receives uri serialization "" + Examples: | uE1 | entity_name | resource_name | resource_instance | resource_message | entity_version_major | expected_uri | - | python | neelam | rpc | test | | | /neelam//rpc.test | - | python | neelam | | | | | /neelam | + | python | neelam | rpc | test | | 0 | /neelam//rpc.test | + | python | neelam | | | | 0 | /neelam | | python | neelam | | | | 1 | /neelam/1 | - | python | neelam | test | | | | /neelam//test | + | python | neelam | test | | | 0 | /neelam//test | | python | neelam | test | | | 1 | /neelam/1/test | - | python | neelam | test | front | | | /neelam//test.front | + | python | neelam | test | front | | 0 | /neelam//test.front | | python | neelam | test | front | | 1 | /neelam/1/test.front | - | python | neelam | test | front | Test | | /neelam//test.front#Test | + | python | neelam | test | front | Test | 0 | /neelam//test.front#Test | | python | neelam | test | front | Test | 1 | /neelam/1/test.front#Test | - | java | neelam | rpc | test | | | /neelam//rpc.test | - | java | neelam | | | | | /neelam | + | java | neelam | rpc | test | | 0 | /neelam//rpc.test | + | java | neelam | | | | 0 | /neelam | | java | neelam | | | | 1 | /neelam/1 | - | java | neelam | test | | | | /neelam//test | + | java | neelam | test | | | 0 | /neelam//test | | java | neelam | test | | | 1 | /neelam/1/test | - | java | neelam | test | front | | | /neelam//test.front | + | java | neelam | test | front | | 0 | /neelam//test.front | | java | neelam | test | front | | 1 | /neelam/1/test.front | - | java | neelam | test | front | Test | | /neelam//test.front#Test | + | java | neelam | test | front | Test | 0 | /neelam//test.front#Test | | java | neelam | test | front | Test | 1 | /neelam/1/test.front#Test | Scenario Outline: Testing the remote uri serializer @@ -64,26 +65,27 @@ Feature: Local and Remote URI serialization And sets "resource.name" to "" And sets "resource.instance" to "" And sets "resource.message" to "" + When sends "uri_serialize" request - And user waits "1" second - Then the serialized uri received is "" + Then receives uri serialization "" + Examples: | uE1 | authority_name | entity_name | resource_name | resource_instance | resource_message | entity_version_major | expected_uri | - | python | vcu.my_car_vin | neelam | | | | | //vcu.my_car_vin/neelam | + | python | vcu.my_car_vin | neelam | | | | 0 | //vcu.my_car_vin/neelam | | python | vcu.my_car_vin | neelam | | | | 1 | //vcu.my_car_vin/neelam/1 | | python | vcu.my_car_vin | neelam | test | | | 1 | //vcu.my_car_vin/neelam/1/test | - | python | vcu.my_car_vin | neelam | test | | | | //vcu.my_car_vin/neelam//test | + | python | vcu.my_car_vin | neelam | test | | | 0 | //vcu.my_car_vin/neelam//test | | python | vcu.my_car_vin | neelam | test | front | | 1 | //vcu.my_car_vin/neelam/1/test.front | - | python | vcu.my_car_vin | neelam | test | front | | | //vcu.my_car_vin/neelam//test.front | + | python | vcu.my_car_vin | neelam | test | front | | 0 | //vcu.my_car_vin/neelam//test.front | | python | vcu.my_car_vin | neelam | test | front | Test | 1 | //vcu.my_car_vin/neelam/1/test.front#Test | - | python | vcu.my_car_vin | neelam | test | front | Test | | //vcu.my_car_vin/neelam//test.front#Test | - | python | vcu.my_car_vin | petapp | rpc | response | | | //vcu.my_car_vin/petapp//rpc.response | - | java | vcu.my_car_vin | neelam | | | | | //vcu.my_car_vin/neelam | + | python | vcu.my_car_vin | neelam | test | front | Test | 0 | //vcu.my_car_vin/neelam//test.front#Test | + | python | vcu.my_car_vin | petapp | rpc | response | | 0 | //vcu.my_car_vin/petapp//rpc.response | + | java | vcu.my_car_vin | neelam | | | | 0 | //vcu.my_car_vin/neelam | | java | vcu.my_car_vin | neelam | | | | 1 | //vcu.my_car_vin/neelam/1 | | java | vcu.my_car_vin | neelam | test | | | 1 | //vcu.my_car_vin/neelam/1/test | - | java | vcu.my_car_vin | neelam | test | | | | //vcu.my_car_vin/neelam//test | + | java | vcu.my_car_vin | neelam | test | | | 0 | //vcu.my_car_vin/neelam//test | | java | vcu.my_car_vin | neelam | test | front | | 1 | //vcu.my_car_vin/neelam/1/test.front | - | java | vcu.my_car_vin | neelam | test | front | | | //vcu.my_car_vin/neelam//test.front | + | java | vcu.my_car_vin | neelam | test | front | | 0 | //vcu.my_car_vin/neelam//test.front | | java | vcu.my_car_vin | neelam | test | front | Test | 1 | //vcu.my_car_vin/neelam/1/test.front#Test | - | java | vcu.my_car_vin | neelam | test | front | Test | | //vcu.my_car_vin/neelam//test.front#Test | - | java | vcu.my_car_vin | petapp | rpc | response | | | //vcu.my_car_vin/petapp//rpc.response | + | java | vcu.my_car_vin | neelam | test | front | Test | 0 | //vcu.my_car_vin/neelam//test.front#Test | + | java | vcu.my_car_vin | petapp | rpc | response | | 0 | //vcu.my_car_vin/petapp//rpc.response | diff --git a/test_manager/features/tests/serializers/long_uuid_deserializer.feature b/test_manager/features/tests/serializers/long_uuid_deserializer.feature index 612b1653..d3dc3784 100644 --- a/test_manager/features/tests/serializers/long_uuid_deserializer.feature +++ b/test_manager/features/tests/serializers/long_uuid_deserializer.feature @@ -27,8 +27,7 @@ Feature: UUID de-serialization Scenario Outline: Testing the long uuid deserializer Given "" creates data for "uuid_deserialize" - When sends a "uuid_deserialize" request with the value "" - And user waits "1" second + When sends a "uuid_deserialize" request with serialized input "" Then the deserialized uuid received should have the following properties: | Field | Value | | lsb | | @@ -37,4 +36,4 @@ Feature: UUID de-serialization Examples: | uE1 | lsb | msb | serialized_uuid | | python | 11155833020022798372 | 112128268635242497 | 018e5c10-f548-8001-9ad1-7b068c083824 | - | java | 11155833020022798372 | 112128268635242497 | 018e5c10-f548-8001-9ad1-7b068c083824 | + | java | -7290911053686753244 | 112128268635242497 | 018e5c10-f548-8001-9ad1-7b068c083824 | diff --git a/test_manager/features/tests/serializers/long_uuid_serializer.feature b/test_manager/features/tests/serializers/long_uuid_serializer.feature index 85dcf439..c6784e30 100644 --- a/test_manager/features/tests/serializers/long_uuid_serializer.feature +++ b/test_manager/features/tests/serializers/long_uuid_serializer.feature @@ -31,8 +31,8 @@ Feature: UUID serialization And sets "msb" to "" When sends "uuid_serialize" request - And user waits "1" second - Then the serialized uuid received is "" + Then receives uuid serialization "" + Examples: | uE1 | lsb | msb | expected_uuid | | python | 11155833020022798372 | 112128268635242497 | 018e5c10-f548-8001-9ad1-7b068c083824 | diff --git a/test_manager/features/tests/transport_rpc/register_and_invoke.feature b/test_manager/features/tests/transport_rpc/register_and_invoke.feature index dcbda0a3..c56874ef 100644 --- a/test_manager/features/tests/transport_rpc/register_and_invoke.feature +++ b/test_manager/features/tests/transport_rpc/register_and_invoke.feature @@ -32,31 +32,28 @@ Feature: Testing RPC Functionality And sets "resource.name" to "door" And sets "resource.instance" to "front_left" And sets "resource.message" to "Door" + When sends "registerlistener" request - And user waits "2" second Then the status received with "code" is "OK" -# + Given "" creates data for "invokemethod" And sets "entity.name" to "body.access" And sets "resource.name" to "door" And sets "resource.instance" to "front_left" And sets "resource.message" to "Door" - And sets "payload.format" to "UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY" And sets "payload.value" to b".type.googleapis.com/google.protobuf.Int32Value\x12\x02\x08\x03" When sends "invokemethod" request - And user waits "8" second - - Then "" receives rpc response having "payload.value" as b"\n/type.googleapis.com/google.protobuf.StringValue\x12\x14\n\x12SuccessRPCResponse" + Then "" receives data field "payload.value" as b"\n/type.googleapis.com/google.protobuf.StringValue\x12\x14\n\x12SuccessRPCResponse" Given "" creates data for "unregisterlistener" And sets "entity.name" to "body.access" And sets "resource.name" to "door" And sets "resource.instance" to "front_left" And sets "resource.message" to "Door" + When sends "unregisterlistener" request - And user waits "2" second Then the status received with "code" is "OK" Examples: diff --git a/test_manager/features/tests/transport_rpc/register_and_send.feature b/test_manager/features/tests/transport_rpc/register_and_send.feature index d35bad0c..7ae01d32 100644 --- a/test_manager/features/tests/transport_rpc/register_and_send.feature +++ b/test_manager/features/tests/transport_rpc/register_and_send.feature @@ -32,10 +32,10 @@ Feature: Testing Publish and Subscribe Functionality And sets "resource.name" to "door" And sets "resource.instance" to "front_left" And sets "resource.message" to "Door" + When sends "registerlistener" request - And user waits "3" second Then the status received with "code" is "OK" -# + When "" creates data for "send" And sets "attributes.source.entity.name" to "body.access" And sets "attributes.source.resource.name" to "door" @@ -45,13 +45,20 @@ Feature: Testing Publish and Subscribe Functionality And sets "attributes.type" to "UMESSAGE_TYPE_PUBLISH" And sets "payload.format" to "UPAYLOAD_FORMAT_PROTOBUF_WRAPPED_IN_ANY" And sets "payload.value" to b".type.googleapis.com/google.protobuf.Int32Value\x12\x02\x08\x03" - And sends "send" request - And user waits "3" second Then the status received with "code" is "OK" - And user waits "5" second - And "" receives "payload.value" as b"type.googleapis.com/google.protobuf.Int32Value\x12\x02\x08\x03" + And "" sends onreceive message with field "payload.value" as b"type.googleapis.com/google.protobuf.Int32Value\x12\x02\x08\x03" + + # Unregister in the end for cleanup + When "" creates data for "unregisterlistener" + And sets "entity.name" to "body.access" + And sets "resource.name" to "door" + And sets "resource.instance" to "front_left" + And sets "resource.message" to "Door" + And sends "unregisterlistener" request + + Then the status received with "code" is "OK" Examples: | uE1 | uE2 | diff --git a/test_manager/features/tests/transport_rpc/register_and_unregister.feature b/test_manager/features/tests/transport_rpc/register_and_unregister.feature index 81d41072..3c7cbdcc 100644 --- a/test_manager/features/tests/transport_rpc/register_and_unregister.feature +++ b/test_manager/features/tests/transport_rpc/register_and_unregister.feature @@ -33,7 +33,7 @@ Feature: Testing register and unregister And sets "resource.instance" to "front_left" And sets "resource.message" to "Door" When sends "registerlistener" request - And user waits "2" second + Then the status received with "code" is "OK" When "" creates data for "unregisterlistener" @@ -41,11 +41,28 @@ Feature: Testing register and unregister And sets "resource.name" to "door" And sets "resource.instance" to "front_left" And sets "resource.message" to "Door" - When sends "unregisterlistener" request - And user waits "2" second + And sends "unregisterlistener" request + Then the status received with "code" is "OK" Examples: | uE1 | | java | | python | + + + Scenario Outline: Test unregisterlistener when no entity is registered to any topic + Given "" creates data for "unregisterlistener" + And sets "entity.name" to "body.access" + And sets "resource.name" to "door" + And sets "resource.instance" to "front_left" + And sets "resource.message" to "Door" + + When sends "unregisterlistener" request + + Then the status received with "code" is "NOT_FOUND" + + Examples: + | uE1 | + | java | + | python | diff --git a/test_manager/features/tests/validators/uri_validator.feature b/test_manager/features/tests/validators/uri_validator.feature index c08df27a..51f320b7 100644 --- a/test_manager/features/tests/validators/uri_validator.feature +++ b/test_manager/features/tests/validators/uri_validator.feature @@ -27,93 +27,16 @@ Feature: URI Validation Scenario Outline: Validate different types of URIs - Given "" creates data for "uri_validate" - And sets "uri" to "" - And sets "type" to "" + Given "" creates data for "uri_validate" + And sets "uri" to "" + And sets "type" to "" + When sends "uri_validate" request - And user waits "1" second - Then "" receives validation result as "" - And "" receives validation message as "" + Then receives validation result as "" + And receives validation message as "" Examples: - |sdk_name| uri | validation_type | expected_status| expected_message | - | java | | uri | False | Uri is empty. | - | java | // | uri | False | Uri is empty. | - | java | /neelam | uri | True | none | - | java | neelam | uri | False | Uri is empty. | - | java | /neelam//rpc.echo | rpc_method | True | none | - | java | /neelam/echo | rpc_method | False | Uri is empty. | - | java | neelam | rpc_method | False | Uri is empty. | - | java | /neelam//rpc.response | rpc_response | True | none | - | java | neelam | rpc_response | False | Uri is empty. | - | java | /neelam//dummy.wrong | rpc_response | False | Invalid RPC response type. | - | java | //VCU.MY_CAR_VIN/body.access/1/door.front_left#Door | uri | True | none | - | java | //VCU.MY_CAR_VIN/body.access//door.front_left#Door | uri | True | none | - | java | /body.access/1/door.front_left#Door | uri | True | none | - | java | /body.access//door.front_left#Door | uri | True | none | - | java | : | uri | False | none | - | java | / | uri | False | none | - | java | // | uri | False | none | - | java | ///body.access/1/door.front_left#Door | uri | False | none | - | java | //VCU.myvin///door.front_left#Door | uri | False | none | - | java | /1/door.front_left#Door | uri | False | none | - | java | //VCU.myvin//1 | uri | False | none | - | java | //bo.cloud/petapp/1/rpc.response | rpc_method | True | none | - | java | //bo.cloud/petapp//rpc.response | rpc_method | True | none | - | java | /petapp/1/rpc.response | rpc_method | True | none | - | java | /petapp//rpc.response | rpc_method | True | none | - | java | : | rpc_method | False | none | - | java | /petapp/1/dog | rpc_method | False | none | - | java | //petapp/1/dog | rpc_method | False | none | - | java | // | rpc_method | False | none | - | java | ///body.access/1 | rpc_method | False | none | - | java | //VCU.myvin | rpc_method | False | none | - | java | /1 | rpc_method | False | none | - | java | //VCU.myvin//1 | rpc_method | False | none | - | java | //VCU.myvin/body.access/1/rpc.UpdateDoor | rpc_method | True | none | - | java | //VCU.myvin/body.access//rpc.UpdateDoor | rpc_method | True | none | - | java | /body.access/1/rpc.UpdateDoor | rpc_method | True | none | - | java | /body.access//rpc.UpdateDoor | rpc_method | True | none | - | java | ///body.access/1/rpc.UpdateDoor | rpc_method | False | none | - | java | /1/rpc.UpdateDoor | rpc_method | False | none | - | java | //VCU.myvin//1/rpc.UpdateDoor | rpc_method | False | none | - | java | /hartley | uri | True | none | - | java | /hartley// | uri | True | none | - | java | /hartley/0 | uri | True | none | - | java | /1 | uri | True | none | - | java | /body.access/1 | uri | True | none | - | java | /body.access/1/door.front_left#Door | uri | True | none | - | java | //vcu.vin/body.access/1/door.front_left#Door | uri | True | none | - | java | /body.access/1/rpc.OpenWindow | uri | True | none | - | java | /body.access/1/rpc.response | uri | True | none | - | java | | uri | False | Uri is empty. | - | java | : | uri | False | Uri is empty. | - | java | /// | uri | False | Uri is empty. | - | java | //// | uri | False | Uri is empty. | - | java | 1 | uri | False | Uri is empty. | - | java | a | uri | False | Uri is empty. | - | java | /petapp/1/rpc.OpenWindow | rpc_method | True | none | - | java | /petapp/1/rpc.response | rpc_method | True | none | - | java | /petapp/1/rpc.response | rpc_response | True | none | - | java | /petapp/1/rpc.OpenWindow | rpc_response | False | none | - | java | /petapp// | rpc_method | False | none | - | java | /petapp | rpc_method | False | none | - | java | /petapp/1/ | rpc_method | False | none | - | java | /petapp/1/rpc | rpc_method | False | none | - | java | /petapp/1/dummy | rpc_method | False | none | - | java | /petapp/1/rpc_dummy | rpc_method | False | none | - | java | | is_empty | True | none | - | java | /hartley/23/rpc.echo | is_resolved | False | none | - | java | | is_micro_form | False | none | - | java | /hartley/23/ | is_micro_form | False | none | - | java | | is_long_form_uuri | False | none | - | java | | is_long_form_uauthority | False | none | - | java | /hartley/23/ | is_long_form_uuri | False | none | - | java | //vcu.veh.gm.com/hartley/23/ | is_long_form_uuri | False | none | - | java | ///hartley/23/ | is_long_form_uuri | False | none | - | java | ///hartley/23/ | is_long_form_uauthority | False | none | - | java | | is_micro_form | False | none | - | java | ///hartley/23/ | is_micro_form | False | none | + | uE1 | uri | validation_type | expected_status| expected_message | | python | | uri | False | Uri is empty. | | python | // | uri | False | Uri is empty. | | python | /neelam | uri | True | none | @@ -190,4 +113,81 @@ Feature: URI Validation | python | ///hartley/23/ | is_long_form_uuri | False | none | | python | ///hartley/23/ | is_long_form_uauthority | False | none | | python | | is_micro_form | False | none | - | python | ///hartley/23/ | is_micro_form | False | none | \ No newline at end of file + | python | ///hartley/23/ | is_micro_form | False | none | + | java | | uri | False | Uri is empty. | + | java | // | uri | False | Uri is empty. | + | java | /neelam | uri | True | none | + | java | neelam | uri | False | Uri is empty. | + | java | /neelam//rpc.echo | rpc_method | True | none | + | java | /neelam/echo | rpc_method | False | Uri is empty. | + | java | neelam | rpc_method | False | Uri is empty. | + | java | /neelam//rpc.response | rpc_response | True | none | + | java | neelam | rpc_response | False | Uri is empty. | + | java | /neelam//dummy.wrong | rpc_response | False | Invalid RPC response type. | + | java | //VCU.MY_CAR_VIN/body.access/1/door.front_left#Door | uri | True | none | + | java | //VCU.MY_CAR_VIN/body.access//door.front_left#Door | uri | True | none | + | java | /body.access/1/door.front_left#Door | uri | True | none | + | java | /body.access//door.front_left#Door | uri | True | none | + | java | : | uri | False | none | + | java | / | uri | False | none | + | java | // | uri | False | none | + | java | ///body.access/1/door.front_left#Door | uri | False | none | + | java | //VCU.myvin///door.front_left#Door | uri | False | none | + | java | /1/door.front_left#Door | uri | False | none | + | java | //VCU.myvin//1 | uri | False | none | + | java | //bo.cloud/petapp/1/rpc.response | rpc_method | True | none | + | java | //bo.cloud/petapp//rpc.response | rpc_method | True | none | + | java | /petapp/1/rpc.response | rpc_method | True | none | + | java | /petapp//rpc.response | rpc_method | True | none | + | java | : | rpc_method | False | none | + | java | /petapp/1/dog | rpc_method | False | none | + | java | //petapp/1/dog | rpc_method | False | none | + | java | // | rpc_method | False | none | + | java | ///body.access/1 | rpc_method | False | none | + | java | //VCU.myvin | rpc_method | False | none | + | java | /1 | rpc_method | False | none | + | java | //VCU.myvin//1 | rpc_method | False | none | + | java | //VCU.myvin/body.access/1/rpc.UpdateDoor | rpc_method | True | none | + | java | //VCU.myvin/body.access//rpc.UpdateDoor | rpc_method | True | none | + | java | /body.access/1/rpc.UpdateDoor | rpc_method | True | none | + | java | /body.access//rpc.UpdateDoor | rpc_method | True | none | + | java | ///body.access/1/rpc.UpdateDoor | rpc_method | False | none | + | java | /1/rpc.UpdateDoor | rpc_method | False | none | + | java | //VCU.myvin//1/rpc.UpdateDoor | rpc_method | False | none | + | java | /hartley | uri | True | none | + | java | /hartley// | uri | True | none | + | java | /hartley/0 | uri | True | none | + | java | /1 | uri | True | none | + | java | /body.access/1 | uri | True | none | + | java | /body.access/1/door.front_left#Door | uri | True | none | + | java | //vcu.vin/body.access/1/door.front_left#Door | uri | True | none | + | java | /body.access/1/rpc.OpenWindow | uri | True | none | + | java | /body.access/1/rpc.response | uri | True | none | + | java | | uri | False | Uri is empty. | + | java | : | uri | False | Uri is empty. | + | java | /// | uri | False | Uri is empty. | + | java | //// | uri | False | Uri is empty. | + | java | 1 | uri | False | Uri is empty. | + | java | a | uri | False | Uri is empty. | + | java | /petapp/1/rpc.OpenWindow | rpc_method | True | none | + | java | /petapp/1/rpc.response | rpc_method | True | none | + | java | /petapp/1/rpc.response | rpc_response | True | none | + | java | /petapp/1/rpc.OpenWindow | rpc_response | False | none | + | java | /petapp// | rpc_method | False | none | + | java | /petapp | rpc_method | False | none | + | java | /petapp/1/ | rpc_method | False | none | + | java | /petapp/1/rpc | rpc_method | False | none | + | java | /petapp/1/dummy | rpc_method | False | none | + | java | /petapp/1/rpc_dummy | rpc_method | False | none | + | java | | is_empty | True | none | + | java | /hartley/23/rpc.echo | is_resolved | False | none | + | java | | is_micro_form | False | none | + | java | /hartley/23/ | is_micro_form | False | none | + | java | | is_long_form_uuri | False | none | + | java | | is_long_form_uauthority | False | none | + | java | /hartley/23/ | is_long_form_uuri | False | none | + | java | //vcu.veh.gm.com/hartley/23/ | is_long_form_uuri | False | none | + | java | ///hartley/23/ | is_long_form_uuri | False | none | + | java | ///hartley/23/ | is_long_form_uauthority | False | none | + | java | | is_micro_form | False | none | + | java | ///hartley/23/ | is_micro_form | False | none | \ No newline at end of file diff --git a/test_manager/testmanager.py b/test_manager/testmanager.py index 573046e9..ef5034ad 100644 --- a/test_manager/testmanager.py +++ b/test_manager/testmanager.py @@ -23,12 +23,17 @@ # SPDX-License-Identifier: Apache-2.0 # # ------------------------------------------------------------------------- +from collections import defaultdict, deque import json import logging import selectors import socket - +from typing import Any, Deque, Dict, Tuple +from typing import Any as AnyType from threading import Lock +import uuid +from multimethod import multimethod +import sys logging.basicConfig(format='%(levelname)s| %(filename)s:%(lineno)s %(message)s') logger = logging.getLogger('File:Line# Debugger') @@ -36,16 +41,107 @@ BYTES_MSG_LENGTH: int = 32767 -class TestManager: +def convert_json_to_jsonstring(j: Dict[str, AnyType]) -> str: + return json.dumps(j) + +def convert_str_to_bytes(string: str) -> bytes: + return str.encode(string) + +def send_socket_data(s: socket.socket, msg: bytes): + s.sendall(msg) + +def is_close_socket_signal(received_data: bytes) -> bool: + return received_data == b'' + + +class TestAgentConnectionDatabase: + def __init__(self) -> None: + self.test_agent_address_to_name: Dict[tuple[str, int], str] = defaultdict(str) + self.test_agent_name_to_address: Dict[str, socket.socket] = {} + self.lock = Lock() + + def add(self, test_agent_socket: socket.socket, test_agent_name: str): + test_agent_address: tuple[str, int] = test_agent_socket.getpeername() + + with self.lock: + self.test_agent_address_to_name[test_agent_address] = test_agent_name + self.test_agent_name_to_address[test_agent_name] = test_agent_socket + + @multimethod + def get(self, address: Tuple[str, int]) -> socket.socket: + test_agent_name: str = self.test_agent_address_to_name[address] + return self.test_agent_name_to_address[test_agent_name] + + @multimethod + def get(self, name: str) -> socket.socket: + return self.test_agent_name_to_address[name] + + def contains(self, test_agent_name: str): + return test_agent_name in self.test_agent_name_to_address + + @multimethod + def close(self, test_agent_name: str): + if test_agent_name is None or test_agent_name == "": + return + test_agent_socket: socket.socket = self.get(test_agent_name) + self.close(test_agent_socket) + + @multimethod + def close(self, test_agent_socket: socket.socket): + test_agent_address: tuple[str, int] = test_agent_socket.getpeername() + test_agent_name: str = self.test_agent_address_to_name.get(test_agent_address, None) + + if test_agent_name is None: + return + + with self.lock: + del self.test_agent_address_to_name[test_agent_address] + del self.test_agent_name_to_address[test_agent_name] + + test_agent_socket.close() + +class DictWithQueue: + def __init__(self) -> None: + self.key_to_queue: Dict[str, Deque[Dict[str, Any]]] = defaultdict(deque) + self.lock = Lock() + + def append(self, key: str, msg: Dict[str, Any]) -> None: + with self.lock: + self.key_to_queue[key].append(msg) + logger.info(f'self.key_to_queue append {self.key_to_queue}') + + def contains(self, key: str, inner_key: str, inner_expected_value: str) -> bool: + queue: Deque[Dict[str, Any]] = self.key_to_queue[key] + if len(queue) == 0: + return False + + response_json: Dict[str, Any] = queue[0] + incoming_req_id: str = response_json[inner_key] + + return incoming_req_id == inner_expected_value + + def popleft(self, key: str) -> Any: + with self.lock: + onreceive: Any = self.key_to_queue[key].popleft() + logger.info(f'self.key_to_queue popleft {onreceive["action"]} {self.key_to_queue}') + return onreceive + + +class TestManager: def __init__(self, bdd_context, ip_addr: str, port: int): self.exit_manager = False - self.selector = selectors.DefaultSelector() - self.connected_sockets = {} + self.socket_event_receiver = selectors.DefaultSelector() + self.connected_test_agent_sockets: Dict[str, socket.socket] = {} + self.test_agent_database = TestAgentConnectionDatabase() + self.action_type_to_response_queue = DictWithQueue() self.lock = Lock() self.bdd_context = bdd_context + # Create server socket self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if sys.platform != "win32": + self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server.bind((ip_addr, port)) self.server.listen(100) self.server.setblocking(False) @@ -53,9 +149,9 @@ def __init__(self, bdd_context, ip_addr: str, port: int): logger.info("TM server is running/listening") # Register server socket for accepting connections - self.selector.register(self.server, selectors.EVENT_READ, self._accept_client_conn) + self.socket_event_receiver.register(self.server, selectors.EVENT_READ, self._accept_client_conn) - def _accept_client_conn(self, server): + def _accept_client_conn(self, server: socket.socket): """ Callback function for accepting test agent connections. @@ -65,95 +161,103 @@ def _accept_client_conn(self, server): logger.info(f'accepted conn. {ta_socket.getpeername()}') # Register socket for receiving data - self.selector.register(ta_socket, selectors.EVENT_READ, self._receive_from_test_agent) + self.socket_event_receiver.register(ta_socket, selectors.EVENT_READ, self._receive_from_test_agent) - def _receive_from_test_agent(self, ta_socket): + def _receive_from_test_agent(self, test_agent: socket.socket): """ Callback function for receiving data from test agent sockets. :param ta_socket: The client socket. """ - recv_data = ta_socket.recv(BYTES_MSG_LENGTH) + recv_data = test_agent.recv(BYTES_MSG_LENGTH) - if not recv_data or recv_data == b'': + if is_close_socket_signal(recv_data): + self.close_test_agent(test_agent) return json_data = json.loads(recv_data.decode('utf-8')) logger.info('Received from test agent: %s', json_data) - self._process_message(json_data, ta_socket) - - def _process_message(self, json_data, ta_socket): - if json_data['action'] == 'initialize': - sdk: str = json_data['data']["SDK_name"].lower().strip() - with self.lock: - if sdk not in self.connected_sockets: - self.connected_sockets[sdk] = ta_socket - elif json_data['action'] in ['send', 'registerlistener', 'unregisterlistener']: - self.bdd_context.status_json = json_data['data'] - elif json_data['action'] == 'onreceive': - self.bdd_context.on_receive_msg[json_data['ue']] = json_data['data'] - elif json_data['action'] == 'rpcresponse': - self.bdd_context.on_receive_rpc_response[json_data['ue']] = json_data['data'] - elif json_data['action'] == 'uri_serialize': - self.bdd_context.on_receive_serialized_uri = json_data['data'] - elif json_data['action'] == 'uri_deserialize': - self.bdd_context.on_receive_deserialized_uri = json_data['data'] - elif json_data['action'] == 'uri_validate': - self.bdd_context.on_receive_validation_result[json_data['ue']] = json_data['data']['result'] - self.bdd_context.on_receive_validation_msg[json_data['ue']] = json_data['data']['message'] - elif json_data['action'] == 'uuid_serialize': - self.bdd_context.on_receive_serialized_uuid = json_data['data'] - elif json_data['action'] == 'uuid_deserialize': - self.bdd_context.on_receive_deserialized_uuid = json_data['data'] - - def close_socket(self, sdk=None, ta_socket=None): - if ta_socket is not None: - logger.info(f"closing socket {ta_socket.getpeername()}") - with self.lock: - for language, s in self.connected_sockets.items(): - if s == ta_socket: - del self.connected_sockets[language] - print(f"Socket associated with {language} removed.") - return - print("Socket not found in the connected sockets.") - self.selector.unregister(ta_socket) - ta_socket.close() - else: - logger.info(f"closing socket for {sdk}") - with self.lock: - if sdk in self.connected_sockets: - self.selector.unregister(self.connected_sockets[sdk]) - self.connected_sockets[sdk].close() - del self.connected_sockets[sdk] - print(f"Socket associated with {sdk} removed.") - return - print(f"{sdk} not found in the connected sockets.") - - def has_sdk_connection(self, sdk_name: str) -> bool: - return sdk_name in self.connected_sockets - - def listen_for_client_connections(self): + # self._process_message(json_data, test_agent) + self._process_receive_message(json_data,test_agent ) + + def _process_receive_message(self, response_json: Dict[str, Any], ta_socket: socket.socket): + if response_json['action'] == 'initialize': + test_agent_sdk: str = response_json['data']["SDK_name"].lower().strip() + self.test_agent_database.add(ta_socket, test_agent_sdk) + return + + action_type: str = response_json["action"] + self.action_type_to_response_queue.append(action_type, response_json) + + def has_sdk_connection(self, test_agent_name: str) -> bool: + return self.test_agent_database.contains(test_agent_name) + + def listen_for_incoming_events(self): + """ + Listens for Test Agent connections and messages, then creates a thread to start the init process + """ + while not self.exit_manager: # Wait until some registered file objects or sockets become ready, or the timeout expires. - events = self.selector.select(timeout=0) + events = self.socket_event_receiver.select(timeout=0) for key, mask in events: callback = key.data callback(key.fileobj) + + def request(self, test_agent_name: str, action: str, data: Dict[str, AnyType], payload: Dict[str, AnyType]=None): + """Sends a blocking request message to sdk Test Agent (ex: Java, Rust, C++ Test Agent) + """ + # Get Test Agent's socket + test_agent_name = test_agent_name.lower().strip() + test_agent_socket: socket.socket = self.test_agent_database.get(test_agent_name) - def receive_from_bdd(self, sdk_name, action, data, payload=None): - ta_socket: socket.socket = self.connected_sockets[sdk_name.lower().strip()] - - # Create a new dictionary - response_dict = {'data': data, 'action': action} + # Create a request json to send to specific Test Agent + test_id: str = str(uuid.uuid4()) + request_json = {'data': data, 'action': action, "test_id": test_id} if payload is not None: - response_dict['payload'] = payload - response_dict = json.dumps(response_dict).encode() - ta_socket.sendall(response_dict) - logger.info(f"Sent to TestAgent {response_dict}") + request_json['payload'] = payload + + # Pack json as binary + request_str: str = convert_json_to_jsonstring(request_json) + request_bytes: bytes = convert_str_to_bytes(request_str) + + send_socket_data(test_agent_socket, request_bytes) + logger.info(f"Sent to TestAgent{request_json}") + + # Wait until get response + logger.info(f"Waiting test_id {test_id}") + while not self.action_type_to_response_queue.contains(action, "test_id", test_id): + pass + logger.info(f"Received test_id {test_id}") + # Get response + response_json: Dict[str, Any] = self.action_type_to_response_queue.popleft(action) + return response_json + + def _wait_for_onreceive(self, test_agent_name: str): + while not self.action_type_to_response_queue.contains('onreceive', 'ue', test_agent_name): + pass + + def get_onreceive(self, test_agent_name: str) -> Dict[str, Any]: + self._wait_for_onreceive(test_agent_name) + + return self.action_type_to_response_queue.popleft('onreceive') + + @multimethod + def close_test_agent(self, test_agent_socket: socket.socket): + # Stop monitoring socket/fileobj. A file object shall be unregistered prior to being closed. + self.socket_event_receiver.unregister(test_agent_socket) + self.test_agent_database.close(test_agent_socket) + + @multimethod + def close_test_agent(self, test_agent_name: str): + if self.test_agent_database.contains(test_agent_name): + test_agent: socket.socket = self.test_agent_database.get(test_agent_name) + self.close_test_agent(test_agent) + def close(self): """Close the selector / test manager's server, BUT need to free its individual SDK TA connections using self.close_ta(sdk) first """ self.exit_manager = True - self.selector.close() + self.socket_event_receiver.close() self.server.close() diff --git a/up_client_socket/python/socket_transport.py b/up_client_socket/python/socket_transport.py index 9f0dd0ed..73aff24b 100644 --- a/up_client_socket/python/socket_transport.py +++ b/up_client_socket/python/socket_transport.py @@ -42,6 +42,7 @@ from uprotocol.transport.ulistener import UListener from uprotocol.transport.utransport import UTransport from uprotocol.uri.factory.uresource_builder import UResourceBuilder +from uprotocol.uri.serializer.longuriserializer import LongUriSerializer from uprotocol.uri.validator.urivalidator import UriValidator from uprotocol.uuid.serializer.longuuidserializer import LongUuidSerializer