def _init_caches(self): self._protocol_in_progress = None self._protocols = {} self._servers = _NamedCache('server') self._clients = _NamedCache('client') self._message_stack = [] self._header_values = {} self._field_values = {} self._message_sequence = MessageSequence() self._message_templates = {}
def _init_caches(self): self._protocol_in_progress = None self._protocols = {} self._servers = _NamedCache('server', "No servers defined!") self._clients = _NamedCache('client', "No clients defined!") self._message_stack = [] self._header_values = {} self._field_values = {} self._message_sequence = MessageSequence() self._message_templates = {}
class RammbockCore(object): ROBOT_LIBRARY_SCOPE = 'GLOBAL' def __init__(self): self._init_caches() def _init_caches(self): self._protocol_in_progress = None self._protocols = {} self._servers = _NamedCache('server') self._clients = _NamedCache('client') self._message_stack = [] self._header_values = {} self._field_values = {} self._message_sequence = MessageSequence() self._message_templates = {} @property def _current_container(self): return self._message_stack[-1] def reset_rammbock(self): """Closes all connections, deletes all servers, clients, and protocols. You should call this method before exiting your test run. This will close all the connections and the ports will therefore be available for reuse faster. """ for client in self._clients: client.close() for server in self._servers: server.close() self._init_caches() def clear_message_streams(self): """ Resets streams and sockets of incoming messages. You can use this method to reuse the same connections for several consecutive test cases. """ for client in self._clients: client.empty() for server in self._servers: server.empty() def new_protocol(self, protocol_name): """Start defining a new protocol template. All messages sent and received from a connection that uses a protocol have to conform to this protocol template. """ if self._protocol_in_progress: raise Exception( 'Can not start a new protocol definition in middle of old.') if protocol_name in self._protocols: raise Exception('Protocol %s already defined' % protocol_name) self._init_new_message_stack(Protocol(protocol_name)) self._protocol_in_progress = True def end_protocol(self): """End protocol definition.""" protocol = self._get_message_template() self._protocols[protocol.name] = protocol self._protocol_in_progress = False def start_udp_server(self, ip, port, name=None, timeout=None, protocol=None): """Starts a new UDP server to given `ip` and `port`. Server can be given a `name`, default `timeout` and a `protocol`. Examples: | Start UDP server | 10.10.10.2 | 53 | | Start UDP server | 10.10.10.2 | 53 | Server1 | | Start UDP server | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start UDP server | 10.10.10.2 | 53 | timeout=5 | """ self._start_server(UDPServer, ip, port, name, timeout, protocol) def start_tcp_server(self, ip, port, name=None, timeout=None, protocol=None): """Starts a new TCP server to given `ip` and `port`. Server can be given a `name`, default `timeout` and a `protocol`. Notice that you have to use `Accept Connection` keyword for server to receive connections. Examples: | Start TCP server | 10.10.10.2 | 53 | | Start TCP server | 10.10.10.2 | 53 | Server1 | | Start TCP server | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start TCP server | 10.10.10.2 | 53 | timeout=5 | """ self._start_server(TCPServer, ip, port, name, timeout, protocol) def start_sctp_server(self, ip, port, name=None, timeout=None, protocol=None): """Starts a new STCP server to given `ip` and `port`. pysctp (https://github.com/philpraxis/pysctp) need to be installed your system. Server can be given a `name`, default `timeout` and a `protocol`. Notice that you have to use `Accept Connection` keyword for server to receive connections. Examples: | Start STCP server | 10.10.10.2 | 53 | | Start STCP server | 10.10.10.2 | 53 | Server1 | | Start STCP server | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start STCP server | 10.10.10.2 | 53 | timeout=5 | """ self._start_server(SCTPServer, ip, port, name, timeout, protocol) def _start_server(self, server_class, ip, port, name=None, timeout=None, protocol=None): protocol = self._get_protocol(protocol) server = server_class(ip=ip, port=port, timeout=timeout, protocol=protocol) return self._servers.add(server, name) def start_udp_client(self, ip=None, port=None, name=None, timeout=None, protocol=None): """Starts a new UDP client. Client can be optionally given `ip` and `port` to bind to, as well as `name`, default `timeout` and a `protocol`. You should use `Connect` keyword to connect client to a host. Examples: | Start UDP client | | Start UDP client | name=Client1 | protocol=GTPV2 | | Start UDP client | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start UDP client | timeout=5 | """ self._start_client(UDPClient, ip, port, name, timeout, protocol) def start_tcp_client(self, ip=None, port=None, name=None, timeout=None, protocol=None): """Starts a new TCP client. Client can be optionally given `ip` and `port` to bind to, as well as `name`, default `timeout` and a `protocol`. You should use `Connect` keyword to connect client to a host. Examples: | Start TCP client | | Start TCP client | name=Client1 | protocol=GTPV2 | | Start TCP client | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start TCP client | timeout=5 | """ self._start_client(TCPClient, ip, port, name, timeout, protocol) def start_sctp_client(self, ip=None, port=None, name=None, timeout=None, protocol=None): """Starts a new SCTP client. Client can be optionally given `ip` and `port` to bind to, as well as `name`, default `timeout` and a `protocol`. You should use `Connect` keyword to connect client to a host. Examples: | Start TCP client | | Start TCP client | name=Client1 | protocol=GTPV2 | | Start TCP client | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start TCP client | timeout=5 | """ self._start_client(SCTPClient, ip, port, name, timeout, protocol) def _start_client(self, client_class, ip=None, port=None, name=None, timeout=None, protocol=None): protocol = self._get_protocol(protocol) client = client_class(timeout=timeout, protocol=protocol) if ip or port: client.set_own_ip_and_port(ip=ip, port=port) return self._clients.add(client, name) def _get_protocol(self, protocol): try: protocol = self._protocols[protocol] if protocol else None except KeyError: raise Exception("No protocol '%s' defined!" % protocol) return protocol def get_client_protocol(self, name=None): """Returns name of the protocol client uses or empty if client does not use a protocol. """ return self._clients.get(name).protocol_name or '' def accept_connection(self, name=None, alias=None): """Accepts a connection to server identified by `name` or the latest server if `name` is empty. If given an `alias`, the connection is named and can be later referenced with that name. Examples: | Accept connection | | Accept connection | Server1 | my_connection | """ server = self._servers.get(name) server.accept_connection(alias) def connect(self, host, port, name=None): """Connects a client to given `host` and `port`. If client `name` is not given then connects the latest client. Examples: | Connect | 127.0.0.1 | 8080 | | Connect | 127.0.0.1 | 8080 | Client1 | """ client = self._clients.get(name) client.connect_to(host, port) def _register_send(self, sender, label, name, connection=None): self._message_sequence.send(name, sender.get_own_address(), sender.get_peer_address(alias=connection), sender.protocol_name, label) def _register_receive(self, receiver, label, name, error='', connection=None): self._message_sequence.receive( name, receiver.get_own_address(), receiver.get_peer_address(alias=connection), receiver.protocol_name, label, error) def client_sends_binary(self, message, name=None, label=None): """Send raw binary `message`. If client `name` is not given, uses the latest client. Optional message `label` is shown on logs. Examples: | Client sends binary | Hello! | | Client sends binary | ${some binary} | Client1 | label=DebugMessage | """ client, name = self._clients.get_with_name(name) client.send(message) self._register_send(client, label, name) # FIXME: support "send to" somehow. A new keyword? def server_sends_binary(self, message, name=None, connection=None, label=None): """Send raw binary `message`. If server `name` is not given, uses the latest server. Optional message `label` is shown on logs. Examples: | Server sends binary | Hello! | | Server sends binary | ${some binary} | Server1 | label=DebugMessage | | Server sends binary | ${some binary} | connection=my_connection | """ server, name = self._servers.get_with_name(name) server.send(message, alias=connection) self._register_send(server, label, name, connection=connection) def client_receives_binary(self, name=None, timeout=None, label=None): """Receive raw binary message. If client `name` is not given, uses the latest client. Optional message `label` is shown on logs. Examples: | ${binary} = | Client receives binary | | ${binary} = | Client receives binary | Client1 | timeout=5 | """ client, name = self._clients.get_with_name(name) msg = client.receive(timeout=timeout) self._register_receive(client, label, name) return msg def server_receives_binary(self, name=None, timeout=None, connection=None, label=None): """Receive raw binary message. If server `name` is not given, uses the latest server. Optional message `label` is shown on logs. Examples: | ${binary} = | Server receives binary | | ${binary} = | Server receives binary | Server1 | connection=my_connection | timeout=5 | """ return self.server_receives_binary_from(name, timeout, connection=connection, label=label)[0] def server_receives_binary_from(self, name=None, timeout=None, connection=None, label=None): """Receive raw binary message. Returns message, ip, and port. If server `name` is not given, uses the latest server. Optional message `label` is shown on logs. Examples: | ${binary} | ${ip} | ${port} = | Server receives binary from | | ${binary} | ${ip} | ${port} = | Server receives binary from | Server1 | connection=my_connection | timeout=5 | """ server, name = self._servers.get_with_name(name) msg, ip, port = server.receive_from(timeout=timeout, alias=connection) self._register_receive(server, label, name, connection=connection) return msg, ip, port def _init_new_message_stack(self, message, fields=None, header_fields=None): self._field_values = fields if fields else {} self._header_values = header_fields if header_fields else {} self._message_stack = [message] def new_message(self, message_name, protocol=None, *parameters): """Define a new message template with `message_name`. `protocol` has to be defined earlier with `Start Protocol Description`. Optional parameters are default values for message header separated with colon. Examples: | New message | MyMessage | MyProtocol | header_field:value | """ proto = self._get_protocol(protocol) if not proto: raise Exception( "Protocol not defined! Please define a protocol before creating a message!" ) if self._protocol_in_progress: raise Exception( "Protocol definition in progress. Please finish it before starting to define a message." ) configs, fields, header_fields = self._parse_parameters(parameters) self._raise_error_if_configs_or_fields(configs, fields, 'New message') self._init_new_message_stack( MessageTemplate(message_name, proto, header_fields)) def _raise_error_if_configs_or_fields(self, configs, fields, function): if configs or fields: raise AssertionError('Cannot set configs or pdu fields in %s' % function) def save_template(self, name): """Save a message template for later use with `Load template`. """ template = self._get_message_template() template.set_as_saved() self._message_templates[name] = (template, self._field_values) def load_template(self, name, *parameters): """Load a message template saved with `Save template`. Optional parameters are default values for message header separated with colon. Examples: | Load Template | MyMessage | header_field:value | """ configs, fields, header_fields = self._parse_parameters(parameters) self._raise_error_if_configs_or_fields(configs, fields, 'Load template') template, fields = self._message_templates[name] self._init_new_message_stack(template, fields, header_fields) def get_message(self, *parameters): """Get encoded message. * Send Message -keywords are convenience methods, that will call this to get the message object and then send it. Optional parameters are message field values separated with colon. Examples: | ${msg} = | Get message | | ${msg} = | Get message | field_name:value | """ _, message_fields, header_fields = self._get_parameters_with_defaults( parameters) return self._encode_message(message_fields, header_fields) def _encode_message(self, message_fields, header_fields): msg = self._get_message_template().encode(message_fields, header_fields) logger.debug('%s' % repr(msg)) return msg def _get_message_template(self): if len(self._message_stack) != 1: raise Exception( 'Message definition not complete. %s not completed.' % self._current_container.name) return self._message_stack[0] def client_sends_message(self, *parameters): """Send a message defined with `New Message`. Optional parameters are client `name` separated with equals and message field values separated with colon. Protocol header values can be set with syntax header:header_field_name:value. Examples: | Client sends message | | Client sends message | field_name:value | field_name2:value | | Client sends message | name=Client1 | header:message_code:0x32 | """ self._send_message(self.client_sends_binary, parameters) # FIXME: support "send to" somehow. A new keyword? def server_sends_message(self, *parameters): """Send a message defined with `New Message`. Optional parameters are server `name` and possible `connection` alias separated with equals and message field values separated with colon. Protocol header values can be set with syntax header:header_field_name:value. Examples: | Server sends message | | Server sends message | field_name:value | field_name2:value | | Server sends message | name=Server1 | connection=my_connection | header:message_code:0x32 | """ self._send_message(self.server_sends_binary, parameters) def _send_message(self, callback, parameters): configs, message_fields, header_fields = self._get_parameters_with_defaults( parameters) msg = self._encode_message(message_fields, header_fields) callback(msg._raw, label=self._current_container.name, **configs) def client_receives_message(self, *parameters): """Receive a message with template defined using `New Message` and validate field values. Message template has to be defined with `New Message` before calling this. Optional parameters are client `name` and possible `timeout` separated with equals and message field values for validation separated with colon. Examples: | ${msg} = | Client receives message | | ${msg} = | Client receives message | name=Client1 | timeout=5 | | ${msg} = | Client receives message | message_field:(0|1) | """ with self._receive(self._clients, *parameters) as (msg, message_fields, header_fields): self._validate_message(msg, message_fields, header_fields) return msg def client_receives_without_validation(self, *parameters): """Receive a message with template defined using `New Message`. Message template has to be defined with `New Message` before calling this. Optional parameters are client `name` and possible `timeout` separated with equals. Examples: | ${msg} = | Client receives without validation | | ${msg} = | Client receives without validation | name=Client1 | timeout=5 | """ with self._receive(self._clients, *parameters) as (msg, _, _): return msg def server_receives_message(self, *parameters): """Receive a message with template defined using `New Message` and validate field values. Message template has to be defined with `New Message` before calling this. Optional parameters are server `name`, `connection` alias and possible `timeout` separated with equals and message field values for validation separated with colon. Examples: | ${msg} = | Server receives message | | ${msg} = | Server receives message | name=Server1 | alias=my_connection | timeout=5 | | ${msg} = | Server receives message | message_field:(0|1) | """ with self._receive(self._servers, *parameters) as (msg, message_fields, header_fields): self._validate_message(msg, message_fields, header_fields) return msg def server_receives_without_validation(self, *parameters): """Receive a message with template defined using `New Message`. Message template has to be defined with `New Message` before calling this. Optional parameters are server `name` and possible `timeout` separated with equals. Examples: | ${msg} = | Server receives without validation | | ${msg} = | Server receives without validation | name=Server1 | alias=my_connection | timeout=5 | """ with self._receive(self._servers, *parameters) as (msg, _, _): return msg def validate_message(self, msg, *parameters): """Validates given message using template defined with `New Message` and field values given as optional arguments. Examples: | Validate message | ${msg} | | Validate message | ${msg} | status:0 | """ _, message_fields, header_fields = self._get_parameters_with_defaults( parameters) self._validate_message(msg, message_fields, header_fields) def _validate_message(self, msg, message_fields, header_fields): errors = self._get_message_template().validate(msg, message_fields, header_fields) if errors: logger.info("Validation failed for %s" % repr(msg)) logger.info('\n'.join(errors)) raise AssertionError(errors[0]) @contextmanager def _receive(self, nodes, *parameters): configs, message_fields, header_fields = self._get_parameters_with_defaults( parameters) node, name = nodes.get_with_name(configs.pop('name', None)) msg = node.get_message(self._get_message_template(), **configs) try: yield msg, message_fields, header_fields self._register_receive(node, self._current_container.name, name) logger.debug("Received %s" % repr(msg)) except AssertionError, e: self._register_receive(node, self._current_container.name, name, error=e.args[0]) raise e
class RammbockCore(object): ROBOT_LIBRARY_SCOPE = 'GLOBAL' def __init__(self): self._init_caches() def _init_caches(self): self._protocol_in_progress = None self._protocols = {} self._servers = _NamedCache('server', "No servers defined!") self._clients = _NamedCache('client', "No clients defined!") self._message_stack = [] self._header_values = {} self._field_values = {} self._message_sequence = MessageSequence() self._message_templates = {} @property def _current_container(self): return self._message_stack[-1] def reset_rammbock(self): """Closes all connections, deletes all servers, clients, and protocols. You should call this method before exiting your test run. This will close all the connections and the ports will therefore be available for reuse faster. """ for client in self._clients: client.close() for server in self._servers: server.close() self._init_caches() def clear_message_streams(self): """ Resets streams and sockets of incoming messages. You can use this method to reuse the same connections for several consecutive test cases. """ for client in self._clients: client.empty() for server in self._servers: server.empty() def new_protocol(self, protocol_name): """Start defining a new protocol template. All messages sent and received from a connection that uses a protocol have to conform to this protocol template. """ if self._protocol_in_progress: raise Exception('Can not start a new protocol definition in middle of old.') if protocol_name in self._protocols: raise Exception('Protocol %s already defined' % protocol_name) self._init_new_message_stack(Protocol(protocol_name)) self._protocol_in_progress = True def end_protocol(self): """End protocol definition.""" protocol = self._get_message_template() self._protocols[protocol.name] = protocol self._protocol_in_progress = False def start_udp_server(self, ip, port, name=None, timeout=None, protocol=None): """Starts a new UDP server to given `ip` and `port`. Server can be given a `name`, default `timeout` and a `protocol`. Examples: | Start UDP server | 10.10.10.2 | 53 | | Start UDP server | 10.10.10.2 | 53 | Server1 | | Start UDP server | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start UDP server | 10.10.10.2 | 53 | timeout=5 | """ self._start_server(UDPServer, ip, port, name, timeout, protocol) def start_tcp_server(self, ip, port, name=None, timeout=None, protocol=None): """Starts a new TCP server to given `ip` and `port`. Server can be given a `name`, default `timeout` and a `protocol`. Notice that you have to use `Accept Connection` keyword for server to receive connections. Examples: | Start TCP server | 10.10.10.2 | 53 | | Start TCP server | 10.10.10.2 | 53 | Server1 | | Start TCP server | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start TCP server | 10.10.10.2 | 53 | timeout=5 | """ self._start_server(TCPServer, ip, port, name, timeout, protocol) def start_sctp_server(self, ip, port, name=None, timeout=None, protocol=None): """Starts a new STCP server to given `ip` and `port`. pysctp (https://github.com/philpraxis/pysctp) need to be installed your system. Server can be given a `name`, default `timeout` and a `protocol`. Notice that you have to use `Accept Connection` keyword for server to receive connections. Examples: | Start STCP server | 10.10.10.2 | 53 | | Start STCP server | 10.10.10.2 | 53 | Server1 | | Start STCP server | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start STCP server | 10.10.10.2 | 53 | timeout=5 | """ self._start_server(SCTPServer, ip, port, name, timeout, protocol) def _start_server(self, server_class, ip, port, name=None, timeout=None, protocol=None): protocol = self._get_protocol(protocol) server = server_class(ip=ip, port=port, timeout=timeout, protocol=protocol) return self._servers.add(server, name) def start_udp_client(self, ip=None, port=None, name=None, timeout=None, protocol=None): """Starts a new UDP client. Client can be optionally given `ip` and `port` to bind to, as well as `name`, default `timeout` and a `protocol`. You should use `Connect` keyword to connect client to a host. Examples: | Start UDP client | | Start UDP client | name=Client1 | protocol=GTPV2 | | Start UDP client | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start UDP client | timeout=5 | """ self._start_client(UDPClient, ip, port, name, timeout, protocol) def start_tcp_client(self, ip=None, port=None, name=None, timeout=None, protocol=None): """Starts a new TCP client. Client can be optionally given `ip` and `port` to bind to, as well as `name`, default `timeout` and a `protocol`. You should use `Connect` keyword to connect client to a host. Examples: | Start TCP client | | Start TCP client | name=Client1 | protocol=GTPV2 | | Start TCP client | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start TCP client | timeout=5 | """ self._start_client(TCPClient, ip, port, name, timeout, protocol) def start_sctp_client(self, ip=None, port=None, name=None, timeout=None, protocol=None): """Starts a new SCTP client. Client can be optionally given `ip` and `port` to bind to, as well as `name`, default `timeout` and a `protocol`. You should use `Connect` keyword to connect client to a host. Examples: | Start TCP client | | Start TCP client | name=Client1 | protocol=GTPV2 | | Start TCP client | 10.10.10.2 | 53 | name=Server1 | protocol=GTPV2 | | Start TCP client | timeout=5 | """ self._start_client(SCTPClient, ip, port, name, timeout, protocol) def _start_client(self, client_class, ip=None, port=None, name=None, timeout=None, protocol=None): protocol = self._get_protocol(protocol) client = client_class(timeout=timeout, protocol=protocol) if ip or port: client.set_own_ip_and_port(ip=ip, port=port) return self._clients.add(client, name) def _get_protocol(self, protocol): try: protocol = self._protocols[protocol] if protocol else None except KeyError: raise Exception("No protocol '%s' defined!" % protocol) return protocol def get_client_protocol(self, name=None): """Returns name of the protocol client uses or empty if client does not use a protocol. """ return self._clients.get(name).protocol_name or '' def accept_connection(self, name=None, alias=None): """Accepts a connection to server identified by `name` or the latest server if `name` is empty. If given an `alias`, the connection is named and can be later referenced with that name. Examples: | Accept connection | | Accept connection | Server1 | my_connection | """ server = self._servers.get(name) server.accept_connection(alias) def connect(self, host, port, name=None): """Connects a client to given `host` and `port`. If client `name` is not given then connects the latest client. Examples: | Connect | 127.0.0.1 | 8080 | | Connect | 127.0.0.1 | 8080 | Client1 | """ client = self._clients.get(name) client.connect_to(host, port) def _register_send(self, sender, label, name, connection=None): self._message_sequence.send(name, sender.get_own_address(), sender.get_peer_address(alias=connection), sender.protocol_name, label) def _register_receive(self, receiver, label, name, error='', connection=None): self._message_sequence.receive(name, receiver.get_own_address(), receiver.get_peer_address(alias=connection), receiver.protocol_name, label, error) def client_sends_binary(self, message, name=None, label=None): """Send raw binary `message`. If client `name` is not given, uses the latest client. Optional message `label` is shown on logs. Examples: | Client sends binary | Hello! | | Client sends binary | ${some binary} | Client1 | label=DebugMessage | """ client, name = self._clients.get_with_name(name) client.send(message) self._register_send(client, label, name) # FIXME: support "send to" somehow. A new keyword? def server_sends_binary(self, message, name=None, connection=None, label=None): """Send raw binary `message`. If server `name` is not given, uses the latest server. Optional message `label` is shown on logs. Examples: | Server sends binary | Hello! | | Server sends binary | ${some binary} | Server1 | label=DebugMessage | | Server sends binary | ${some binary} | connection=my_connection | """ server, name = self._servers.get_with_name(name) server.send(message, alias=connection) self._register_send(server, label, name, connection=connection) def client_receives_binary(self, name=None, timeout=None, label=None): """Receive raw binary message. If client `name` is not given, uses the latest client. Optional message `label` is shown on logs. Examples: | ${binary} = | Client receives binary | | ${binary} = | Client receives binary | Client1 | timeout=5 | """ client, name = self._clients.get_with_name(name) msg = client.receive(timeout=timeout) self._register_receive(client, label, name) return msg def server_receives_binary(self, name=None, timeout=None, connection=None, label=None): """Receive raw binary message. If server `name` is not given, uses the latest server. Optional message `label` is shown on logs. Examples: | ${binary} = | Server receives binary | | ${binary} = | Server receives binary | Server1 | connection=my_connection | timeout=5 | """ return self.server_receives_binary_from(name, timeout, connection=connection, label=label)[0] def server_receives_binary_from(self, name=None, timeout=None, connection=None, label=None): """Receive raw binary message. Returns message, ip, and port. If server `name` is not given, uses the latest server. Optional message `label` is shown on logs. Examples: | ${binary} | ${ip} | ${port} = | Server receives binary from | | ${binary} | ${ip} | ${port} = | Server receives binary from | Server1 | connection=my_connection | timeout=5 | """ server, name = self._servers.get_with_name(name) msg, ip, port = server.receive_from(timeout=timeout, alias=connection) self._register_receive(server, label, name, connection=connection) return msg, ip, port def _init_new_message_stack(self, message, fields=None, header_fields=None): self._field_values = fields if fields else {} self._header_values = header_fields if header_fields else {} self._message_stack = [message] def new_message(self, message_name, protocol=None, *parameters): """Define a new message template with `message_name`. `protocol` has to be defined earlier with `Start Protocol Description`. Optional parameters are default values for message header separated with colon. Examples: | New message | MyMessage | MyProtocol | header_field:value | """ proto = self._get_protocol(protocol) if not proto: raise Exception("Protocol not defined! Please define a protocol before creating a message!") if self._protocol_in_progress: raise Exception("Protocol definition in progress. Please finish it before starting to define a message.") configs, fields, header_fields = self._parse_parameters(parameters) self._raise_error_if_configs_or_fields(configs, fields, 'New message') self._init_new_message_stack(MessageTemplate(message_name, proto, header_fields)) def _raise_error_if_configs_or_fields(self, configs, fields, function): if configs or fields: raise AssertionError('Cannot set configs or pdu fields in %s' % function) def save_template(self, name): """Save a message template for later use with `Load template`. """ template = self._get_message_template() template.set_as_saved() self._message_templates[name] = (template, self._field_values) def load_template(self, name, *parameters): """Load a message template saved with `Save template`. Optional parameters are default values for message header separated with colon. Examples: | Load Template | MyMessage | header_field:value | """ template, fields, header_fields = self._set_templates_fields_and_header_fields(name, parameters) self._init_new_message_stack(template, fields, header_fields) def load_copy_of_template(self, name, *parameters): """Load a copy of message template saved with `Save template` when originally saved values need to be preserved from test to test. Optional parameters are default values for message header separated with colon. Examples: | Load Copy Of Template | MyMessage | header_field:value | """ template, fields, header_fields = self._set_templates_fields_and_header_fields(name, parameters) copy_of_template = copy.deepcopy(template) copy_of_fields = copy.deepcopy(fields) self._init_new_message_stack(copy_of_template, copy_of_fields, header_fields) def _set_templates_fields_and_header_fields(self, name, parameters): configs, fields, header_fields = self._parse_parameters(parameters) self._raise_error_if_configs_or_fields(configs, fields, 'Load template') template, fields = self._message_templates[name] return template, fields, header_fields def get_message(self, *parameters): """Get encoded message. * Send Message -keywords are convenience methods, that will call this to get the message object and then send it. Optional parameters are message field values separated with colon. Examples: | ${msg} = | Get message | | ${msg} = | Get message | field_name:value | """ _, message_fields, header_fields = self._get_parameters_with_defaults(parameters) return self._encode_message(message_fields, header_fields) def _encode_message(self, message_fields, header_fields): msg = self._get_message_template().encode(message_fields, header_fields) logger.debug('%s' % repr(msg)) return msg def _get_message_template(self): if len(self._message_stack) != 1: raise Exception('Message definition not complete. %s not completed.' % self._current_container.name) return self._message_stack[0] def client_sends_message(self, *parameters): """Send a message defined with `New Message`. Optional parameters are client `name` separated with equals and message field values separated with colon. Protocol header values can be set with syntax header:header_field_name:value. Examples: | Client sends message | | Client sends message | field_name:value | field_name2:value | | Client sends message | name=Client1 | header:message_code:0x32 | """ self._send_message(self.client_sends_binary, parameters) # FIXME: support "send to" somehow. A new keyword? def server_sends_message(self, *parameters): """Send a message defined with `New Message`. Optional parameters are server `name` and possible `connection` alias separated with equals and message field values separated with colon. Protocol header values can be set with syntax header:header_field_name:value. Examples: | Server sends message | | Server sends message | field_name:value | field_name2:value | | Server sends message | name=Server1 | connection=my_connection | header:message_code:0x32 | """ self._send_message(self.server_sends_binary, parameters) def _send_message(self, callback, parameters): configs, message_fields, header_fields = self._get_parameters_with_defaults(parameters) msg = self._encode_message(message_fields, header_fields) callback(msg._raw, label=self._current_container.name, **configs) def client_receives_message(self, *parameters): """Receive a message with template defined using `New Message` and validate field values. Message template has to be defined with `New Message` before calling this. Optional parameters are client `name` and possible `timeout` separated with equals and message field values for validation separated with colon. Examples: | ${msg} = | Client receives message | | ${msg} = | Client receives message | name=Client1 | timeout=5 | | ${msg} = | Client receives message | message_field:(0|1) | """ with self._receive(self._clients, *parameters) as (msg, message_fields, header_fields): self._validate_message(msg, message_fields, header_fields) return msg def client_receives_without_validation(self, *parameters): """Receive a message with template defined using `New Message`. Message template has to be defined with `New Message` before calling this. Optional parameters are client `name` and possible `timeout` separated with equals. Examples: | ${msg} = | Client receives without validation | | ${msg} = | Client receives without validation | name=Client1 | timeout=5 | """ with self._receive(self._clients, *parameters) as (msg, _, _): return msg def server_receives_message(self, *parameters): """Receive a message with template defined using `New Message` and validate field values. Message template has to be defined with `New Message` before calling this. Optional parameters are server `name`, `connection` alias and possible `timeout` separated with equals and message field values for validation separated with colon. Examples: | ${msg} = | Server receives message | | ${msg} = | Server receives message | name=Server1 | alias=my_connection | timeout=5 | | ${msg} = | Server receives message | message_field:(0|1) | """ with self._receive(self._servers, *parameters) as (msg, message_fields, header_fields): self._validate_message(msg, message_fields, header_fields) return msg def server_receives_without_validation(self, *parameters): """Receive a message with template defined using `New Message`. Message template has to be defined with `New Message` before calling this. Optional parameters are server `name` and possible `timeout` separated with equals. Examples: | ${msg} = | Server receives without validation | | ${msg} = | Server receives without validation | name=Server1 | alias=my_connection | timeout=5 | """ with self._receive(self._servers, *parameters) as (msg, _, _): return msg def validate_message(self, msg, *parameters): """Validates given message using template defined with `New Message` and field values given as optional arguments. Examples: | Validate message | ${msg} | | Validate message | ${msg} | status:0 | """ _, message_fields, header_fields = self._get_parameters_with_defaults(parameters) self._validate_message(msg, message_fields, header_fields) def _validate_message(self, msg, message_fields, header_fields): errors = self._get_message_template().validate(msg, message_fields, header_fields) if errors: logger.info("Validation failed for %s" % repr(msg)) logger.info('\n'.join(errors)) raise AssertionError(errors[0]) @contextmanager def _receive(self, nodes, *parameters): configs, message_fields, header_fields = self._get_parameters_with_defaults(parameters) node, name = nodes.get_with_name(configs.pop('name', None)) msg = node.get_message(self._get_message_template(), **configs) try: yield msg, message_fields, header_fields self._register_receive(node, self._current_container.name, name) logger.debug("Received %s" % repr(msg)) except AssertionError, e: self._register_receive(node, self._current_container.name, name, error=e.args[0]) raise e