Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
tatipamu committed Dec 4, 2015
2 parents 9b702b9 + 71092a3 commit b0ff606
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 4 deletions.
95 changes: 95 additions & 0 deletions atest/header_filter.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
*** Settings ***
Test Setup Setup up server for test
Test Teardown Reset Rammbock
Library Rammbock
Resource Protocols.robot
Default Tags regression

*** Test Cases ***
String message field matching the header filter
Define and send example message
Receive example message matching filter

String message field not matching the header filter
Define and send example message
Run keyword and expect error * timed out Receive example message with filter value NOT MATCHING

String message field not matching the header filter with unicode value
Define and send example message
Run keyword and expect error * timed out Receive example message with filter value देवनागरी

String message field matching the header filter with regexp
Define and send example message
Receive example message with filter value REGEXP:.*message

String message field not matching the header filter with regexp
Define and send example message
Run keyword and expect error * timed out Receive example message with filter value REGEXP:.*invalid

String message field matching the header filter with invalid regexp
Define and send example message
Run keyword and expect error Invalid RegEx * Receive example message with filter value REGEXP:**

String message field matching the regexp
Define and send example message
Receive example message with regexp value REGEXP:.*message

String message field not matching the regexp
Define and send example message
Run keyword and expect error * does not match the RegEx * Receive example message with regexp value REGEXP:.*invalid

Comparing string message field with an invalid regexp
Define and send example message
Run keyword and expect error Invalid RegEx * Receive example message with regexp value REGEXP:[0-9]++

Comparing string message field with a blank regexp
Define and send example message
Receive example message with regexp value REGEXP:

Comparing string message field with a invalid ending regexp
Define and send example message
Run keyword and expect error Invalid RegEx * Receive example message with regexp value REGEXP:[]

Comparing integer message field with a regexp
Define and send example message
New message exMessage StringInHeader header:integer_field:REGEXP:.*
Run keyword and expect error * can not be matched to regular expression pattern * Server receives message

String message field matching the header filter with multiple messages in stream
Define and send multiple messages
Receive example message with filter value REGEXP:^first

*** Keywords ***
Setup up server for test
Define a protocol with string field in header
Setup UDP server and client protocol=StringInHeader

Define a protocol with string field in header
New protocol StringInHeader
Uint 1 integer_field
Chars * string_field terminator=0x00
End protocol

Define and send example message
New message exMessage StringInHeader header:string_field:match string message header:integer_field:10
Client sends message

Define and send multiple messages
New message exMessage1 StringInHeader header:string_field:first string message header:integer_field:10
Client sends message
New message exMessage2 StringInHeader header:string_field:second string message header:integer_field:10
Client sends message

Receive example message matching filter
New message exMessage StringInHeader header:string_field:match string message
Server receives message header_filter=string_field

Receive example message with filter value
[arguments] ${value}
New message exMessage StringInHeader header:string_field:${value}
Server receives message header_filter=string_field

Receive example message with regexp value
[arguments] ${value}
New message exMessage StringInHeader header:string_field:${value}
Server receives message
26 changes: 25 additions & 1 deletion atest/structured_messages/conditional.robot
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ Validating simple conditional message has element fails
Client sends message condition:1
Run keyword and expect error Value of field* Server receives message condition:1 conditional.element:52


Validating Multiple Conditions using Logical Expressions
Conditional message with Logical Expressions
Client sends message
${msg}= Server receives message
Should be true ${msg.conditional2.exists}
Should be equal as integers ${msg.conditional2.element} 24
Should not be true ${msg.conditional3.exists}
Run keyword and expect error Resolving variable* Should be equal as integers ${msg.conditional3.element} 1

*** Keywords ***
Conditional message
Expand All @@ -46,3 +53,20 @@ Conditional message
Conditional struct.cond_in_struct == 5 conditional2
u8 element 24
end conditional

Conditional message with Logical Expressions
New message ConditionalExampleLogical Example header:messageType:0xb0b1
u8 condition 0
New Struct elem struct
u8 cond_in_struct 5
u8 another_field 4
End Struct
Conditional condition == 1 conditional
u8 element 42
end conditional
Conditional struct.cond_in_struct == 5 && struct.another_field == 4 conditional2
u8 element 24
end conditional
Conditional struct.cond_in_struct == 1 || struct.another_field != 4 conditional3
u8 element 1
end conditional
31 changes: 30 additions & 1 deletion src/Rammbock/condition_parser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,35 @@
class ConditionParser(object):

def __init__(self, condition):
import re
logicals = re.split('(&&|\|\|)', condition)
self.conditions = self._get_individual_conditions(logicals)

class ConditionParser(object):
def _get_individual_conditions(self, logicals):
conditions = []
for element in logicals:
if element in ('&&', '||'):
conditions.append(element)
else:
conditions.append(ExpressionEvaluator(element))
return conditions

def evaluate(self, msg_fields):
status = True
operator = '&&'
for condition in self.conditions:
if condition in ('&&', '||'):
operator = condition
else:
evaluated = condition.evaluate(msg_fields)
if operator == '&&':
status = status and evaluated
else:
status = status or evaluated
return status


class ExpressionEvaluator(object):

def __init__(self, condition):
if '==' in condition:
Expand Down
8 changes: 8 additions & 0 deletions src/Rammbock/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,8 @@ def chars(self, length, name, value=None, terminator=None):
length of value and decoded as all available bytes.
`value` is optional.
`value` could be either a "String" or a "Regular Expression" and
if it is a Regular Expression it must be prefixed by 'REGEXP:'.
Examples:
| chars | 16 | field | Hello World! |
Expand All @@ -840,6 +842,7 @@ def chars(self, length, name, value=None, terminator=None):
| chars | charLength | field |
| chars | * | field | Hello World! |
| chars | * | field | REGEXP:^{[a-zA-Z ]+}$ |
"""

self._add_field(Char(length, name, value, terminator))
Expand Down Expand Up @@ -1150,11 +1153,16 @@ def _name_and_value(self, separator, parameter):

def conditional(self, condition, name):
"""Defines a 'condition' when conditional element of 'name' exists if `condition` is true.
`condition` can contain multiple conditions combined together using Logical Expressions(&&,||).
Example:
| Conditional | mycondition == 1 | foo |
| u8 | myelement | 42 |
| End conditional |
| Conditional | condition1 == 1 && condition2 != 2 | bar |
| u8 | myelement | 8 |
| End condtional |
"""
self._message_stack.append(ConditionalTemplate(condition, name, self._current_container))

Expand Down
15 changes: 13 additions & 2 deletions src/Rammbock/templates/message_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
import time
import threading
import traceback
import re

from Rammbock.logger import logger
from Rammbock.binary_tools import to_bin
from Rammbock.binary_tools import to_bin, to_int
from Rammbock.synchronization import LOCK


Expand Down Expand Up @@ -94,11 +95,21 @@ def _to_msg(self, template, header, pdu_bytes):
return msg

def _matches(self, header, fields, header_filter):
# FIXME: Matching should not be assuming matching string presentation
if header_filter:
if header_filter not in fields:
raise AssertionError('Trying to filter messages by header field %s, but no value has been set for %s' %
(header_filter, header_filter))
field = header[header_filter]
if field._type == 'chars':
if fields[header_filter].startswith('REGEXP:'):
try:
regexp = fields[header_filter].split(':')[1].strip()
return bool(re.match(regexp, field.ascii))
except re.error as e:
raise Exception("Invalid RegEx Error : " + str(e))
return field.ascii == fields[header_filter]
if field._type == 'uint':
return field.uint == to_int(fields[header_filter])
if header[header_filter].bytes != to_bin(fields[header_filter]):
return False
return True
Expand Down
17 changes: 17 additions & 0 deletions src/Rammbock/templates/primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,14 @@ def validate(self, parent, paramdict, name=None):
e.args = ('Validating {}:{} failed. {}.\n Did you set default value as numeric object instead of string?'
.format(name, forced_value, e.args[0]),)
raise e
if forced_value.startswith('REGEXP'):
return self._validate_regexp(forced_value, value, field)
return self._validate_exact_match(forced_value, value, field)

def _validate_regexp(self, forced_pattern, value, field):
return ["Value of field '%s' can not be matched to regular expression pattern '%s'" %
(field._get_recursive_name(), forced_pattern)]

def _validate_pattern(self, forced_pattern, value, field):
if self._validate_or(forced_pattern, value, field):
return []
Expand Down Expand Up @@ -215,6 +221,17 @@ def _prepare_data(self, data):
return data[0:data.index(self._terminator) + len(self._terminator)]
return data

def _validate_regexp(self, forced_pattern, value, field):
try:
regexp = forced_pattern.split(':')[1].strip()
if bool(re.match(regexp, field.ascii)):
return []
else:
return ['Value of field %s does not match the RegEx %s!=%s' %
(field._get_recursive_name(), self._default_presentation_format(value), forced_pattern)]
except re.error as e:
raise Exception("Invalid RegEx Error : " + str(e))


class Binary(_TemplateField):

Expand Down
19 changes: 19 additions & 0 deletions utest/test_condition_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,22 @@ def test_evaluate_unsupported_operator(self):

def test_failing_evaluate(self):
self.condition_evaluate_exception('mycondition != 1', {'foo': 0})

def test_evaluate_or_conditional(self):
values = {'foo': 2, 'bar': 3}
self.condition('foo == 2 || bar != 2', values, True)
self.condition('foo != 2 || bar != 2', values, True)
self.condition('foo != 2 || bar != 3', values, False)
self.condition('foo == 2 || bar != 3', values, True)

def test_evaluate_and_conditional(self):
values = {'foo': 2, 'bar': 3}
self.condition('foo == 2 && bar == 3', values, True)
self.condition('foo != 1 && bar == 2', values, False)
self.condition('foo == 3 && bar != 1', values, False)
self.condition('foo == 1 && bar == 0', values, False)

def test_evaluate_multiple_and_conditions(self):
values = {'foo': 2, 'bar': 3}
self.condition('foo == 2 && bar == 3 && foo == 2', values, True)
self.condition('foo == 1 || bar == 2 || foo == 4', values, False)
7 changes: 7 additions & 0 deletions utest/test_templates/test_primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def test_validate_uint(self):
self._should_pass(UInt(2, 'field', '0x04').validate({'field': field}, {}))
self._should_pass(UInt(2, 'field', '0x0004').validate({'field': field}, {}))
self._should_pass(UInt(2, 'field', '(0|4)').validate({'field': field}, {}))
self._should_fail(UInt(2, 'field', 'REGEXP:.*').validate({'field': field}, {}), 1)

def test_validate_int(self):
field = Field('int', 'field', to_bin('0xffb8'))
Expand All @@ -154,11 +155,17 @@ def test_validate_int(self):
self._should_pass(Int(2, 'field', '-0x48').validate({'field': field}, {}))
self._should_pass(Int(2, 'field', '-0x0048').validate({'field': field}, {}))
self._should_pass(Int(2, 'field', '(0|-72)').validate({'field': field}, {}))
self._should_fail(Int(2, 'field', 'REGEXP:.*').validate({'field': field}, {}), 1)

def test_validate_chars(self):
field = Field('chars', 'field', 'foo\x00\x00')
field_regEx = Field('chars', 'field', '{ Message In Braces }')
self._should_pass(Char(5, 'field', 'foo').validate({'field': field}, {}))
self._should_pass(Char(5, 'field', '(what|foo|bar)').validate({'field': field}, {}))
self._should_pass(Char(5, 'field', 'REGEXP:^{[a-zA-Z ]+}$').validate({'field': field_regEx}, {}))
self._should_pass(Char(5, 'field', 'REGEXP:^foo').validate({'field': field}, {}))
self._should_pass(Char(5, 'field', 'REGEXP:').validate({'field': field}, {}))
self._should_fail(Char(5, 'field', 'REGEXP:^abc').validate({'field': field}, {}), 1)

def _should_pass(self, validation):
self.assertEquals(validation, [])
Expand Down

0 comments on commit b0ff606

Please sign in to comment.