diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5241e2f --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,38 @@ +name: Testing Interoperability +run-name: ${{ github.actor }} is testing out GitHub Actions 🚀 +on: workflow_dispatch +jobs: + Testing_Interoperability: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Downloads assets + uses: robinraju/release-downloader@v1.7 + with: + latest: true + fileName: "*" + - name: Unzip + run: unzip '*.zip' -d executables + - name: Run Interoperability script + run: | + python3 -m venv .venv + source .venv/bin/activate + pip install -r requirements.txt + cd executables + for i in ./* ; \ + do for j in ./*; \ + do python3 ./../interoperability_report.py -P $i -S $j -o=./../junit_interoperability_report.xml; \ + done; \ + done + - name: XUnit Viewer + id: xunit-viewer + uses: AutoModality/action-xunit-viewer@v1 + with: + results: ./junit_interoperability_report.xml + - name: Attach the report + if: always() + uses: actions/upload-artifact@v1 + with: + name: report + path: ./index.html diff --git a/.gitignore b/.gitignore index ee8803b..d8d7ff5 100644 --- a/.gitignore +++ b/.gitignore @@ -33,10 +33,21 @@ shape_main .depend.* GNUmakefile* -#Bin and Object directories +# Bin and Object directories bin/ objs/ # Other *~ GeneratedCode + +# Python files +.venv/ +*.pyc + +# Generated files +srcCxx/shape.* +srcCxx/shapePlugin.* +srcCxx/shapeSupport.* + +.vscode/ diff --git a/README.md b/README.md index b69b828..87f5333 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,95 @@ # dds-rtps + Validation of interoperability of products compliant with [OMG DDS-RTPS standard](https://www.omg.org/spec/DDSI-RTPS/). This is considered one of the core [DDS Specifications](https://www.dds-foundation.org/omg-dds-standard/). See https://www.dds-foundation.org/ for an overview of DDS. The executables found on the [release tab of this repository](https://github.com/omg-dds/dds-rtps/releases) test discovery, DDS Topic and QoS matching, and interoperability for different QoS settings. The goal is to validate that the implementations perform these functions in compliance with OMG DDS-RTPS standard and can interoperate with each other. + +# Interoperability Automatic Tests + +The script `interoperability_report.py` generates automatically +the verification between two executables of these interoperability tests. +The tests that the script runs must be defined previosly (for example +`test_suite.py`). +Once the script finishes, it generates a report with the result +of the interoperability tests between both executables. + +## Options of interoperability_report + +The `interoperability_report.py` may configure the following options: + +``` +$ python3 interoperability_report.py -h + +usage: interoperability_report.py [-h] -P publisher_name -S subscriber_name + [-v] [-f {junit,csv,xlxs}] [-o filename] +Interoperability Test +optional arguments: + -h, --help show this help message and exit +general options: + -P publisher_name, --publisher publisher_name + Publisher Shape Application + -S subscriber_name, --subscriber subscriber_name + Subscriber Shape Application +optional parameters: + -v, --verbose Print more information to stdout. +output options: + -f {junit,csv,xlxs}, --output-format {junit,csv,xlxs} + Output format. + -o filename, --output-name filename + Report filename. +``` + +**NOTE**: The option `-f` only supports junit. + +### Example of use interoperability_report + +This is an example that runs the `interoperability_report.py` +with the test suite `test_suite.py` + +``` +$ python3 interoperability_report.py -P -S +``` + +This generates a report file in JUnit (xml) with the name of both executables +used, the date and the time in which it was generated. For example: +`--20230117-16_49_42.xml` + +## Requirements + +- Python 3.8+ +- Create and enable a virtual environment (installing requirements) + +## Using virtual environments + +The build will be done using virtual environments, you should create and +activate the virtual environment and then install all dependencies. This can be +done by following these steps: + +### Create virtual environment + +In Linux® systems, you may need to install the corresponding python venv +package: + +``` +sudo apt install python3.8-venv +``` + +To create the virtual environment: + +``` +python3 -m venv .venv +``` + +### Activate virtual environment + +``` +source .venv/bin/activate +``` + +### Install requirements + +This step is only required the first time or when the requirements change: + +``` +pip install -r requirements.txt +``` diff --git a/interoperability_report.py b/interoperability_report.py new file mode 100644 index 0000000..24f620f --- /dev/null +++ b/interoperability_report.py @@ -0,0 +1,946 @@ +#!/usr/bin/python + +import time +import re +import pexpect +import argparse +from junitparser import TestCase, TestSuite, JUnitXml, Attr, Failure +from multiprocessing import Process, Queue, Manager, Event +from datetime import datetime +import tempfile +from os.path import exists + +from rtps_test_utilities import ReturnCode +from test_suite import rtps_test_suite_1 + +def log_message(message, verbosity): + if verbosity: + print(message) + +def run_subscriber_shape_main( + name_executable: str, + parameters: str, + test_name: str, + produced_code: "list[int]", + produced_code_index: int, + samples_sent: Queue, + verbosity: bool, + timeout: int, + file: tempfile.TemporaryFile, + subscriber_finished: Event, + publisher_finished: Event): + + """ This function runs the subscriber application with + the specified parameters. Then it saves the + return code in the variable produced_code. + + name_executable <>: name of the shape_main application to run + as a Subscriber + parameters <>: shape_main application parameter list + test_name <>: name of the test that is being tested + produced_code <>: this variable will be overwritten with + the obtained ReturnCode + produced_code_index <>: index of the produced_code list + where the ReturnCode is saved + samples_sent <>: this variable contains the samples + the Publisher sends + verbosity <>: print debug information + timeout <>: time pexpect waits until it matches a pattern + file <>: temporal file to save shape_main application output + subscriber_finished <>: object event from multiprocessing + that is set when the subscriber is finished + publisher_finished <>: object event from multiprocessing + that is set when the publisher is finished + + The function runs the Shape Application as a Subscriber + with the parameters defined. + The Subscriber Shape Application follows the next steps: + * The topic is created + * The Data Reader is created + * The Data Reader matches with a Data Writer + * The Data Reader detects the Data Writer as alive + * The Data Reader receives data + + If the Shape Application passes one step, it prints a specific + string pattern. This function matches that pattern and and waits + for the next input string from the Shape Application. If the + ShapeApplication stops at some step, it prints an error message. + When this function matches an error string (or doesn't match + an expected pattern in the specified timeout), + the corresponding ReturnCode is saved in + produced_code[produced_code_index] and the process finishes. + + """ + # Step 1 : run the executable + log_message('Running Shape Application Subscriber', verbosity) + child_sub = pexpect.spawnu(f'{name_executable} {parameters}') + child_sub.logfile = file + + # Step 2 : Check if the topic is created + log_message('S: Waiting for topic creation', verbosity) + index = child_sub.expect( + [ + 'Create topic:', # index = 0 + pexpect.TIMEOUT, # index = 1 + 'please specify topic name', # index = 2 + 'unrecognized value', # index = 3 + pexpect.EOF # index = 4 + ], + timeout + ) + + if index == 1 or index == 2 or index == 4: + produced_code[produced_code_index] = ReturnCode.TOPIC_NOT_CREATED + elif index == 3: + produced_code[produced_code_index] = ReturnCode.UNRECOGNIZED_VALUE + elif index == 0: + # Step 3 : Check if the reader is created + log_message('S: Waiting for DR creation', verbosity) + index = child_sub.expect( + [ + 'Create reader for topic:', # index = 0 + pexpect.TIMEOUT, # index = 1 + 'failed to create content filtered topic' # index = 2 + ], + timeout + ) + + if index == 1: + produced_code[produced_code_index] = ReturnCode.READER_NOT_CREATED + elif index == 2: + produced_code[produced_code_index] = ReturnCode.FILTER_NOT_CREATED + elif index == 0: + # Step 4 : Check if the reader matches the writer + log_message('S: Waiting for DW matching', verbosity) + index = child_sub.expect( + [ + 'on_subscription_matched()', # index = 0 + pexpect.TIMEOUT, # index = 1 + 'on_requested_incompatible_qos()' # index = 2 + ], + timeout + ) + + if index == 1: + produced_code[produced_code_index] = ReturnCode.WRITER_NOT_MATCHED + elif index == 2: + produced_code[produced_code_index] = ReturnCode.INCOMPATIBLE_QOS + elif index == 0: + # Step 5: Check if the reader detects the writer as alive + log_message('S: Waiting for detecting DW alive', verbosity) + index = child_sub.expect( + [ + 'on_liveliness_changed()', # index = 0 + pexpect.TIMEOUT # index = 1 + ], + timeout + ) + + if index == 1: + produced_code[produced_code_index] = ReturnCode.WRITER_NOT_ALIVE + elif index == 0: + #Step 6 : Check if the reader receives the samples + log_message('S: Waiting for receiving samples', verbosity) + index = child_sub.expect( + [ + '\[[0-9][0-9]\]', # index = 0 + pexpect.TIMEOUT # index = 1 + ], + timeout + ) + + if index == 1: + produced_code[produced_code_index] = ReturnCode.DATA_NOT_RECEIVED + elif index == 0: + # This test checks that data is received in the right order + if test_name == 'Test_Reliability_4': + for x in range(0, 3, 1): + sub_string = re.search('[0-9]{3} [0-9]{3}', + child_sub.before) + if samples_sent.get() == sub_string.group(0): + produced_code[produced_code_index] = ReturnCode.OK + else: + produced_code[produced_code_index] = ReturnCode.DATA_NOT_CORRECT + break + log_message('S: Waiting for receiving samples', + verbosity) + child_sub.expect( + [ + '\[[0-9][0-9]\]', # index = 0 + pexpect.TIMEOUT # index = 1 + ], + timeout + ) + # Two Publishers and One Subscriber to test that if + # each one has a different color, the ownership strength + # does not matter + elif test_name == 'Test_Ownership_3': + red_received = False + blue_received = False + produced_code[produced_code_index] = ReturnCode.RECEIVING_FROM_ONE + for x in range(0,100,1): + sub_string_red = re.search('RED', + child_sub.before) + sub_string_blue = re.search('BLUE', + child_sub.before) + + if sub_string_red != None: + red_received = True + + if sub_string_blue != None: + blue_received = True + + if blue_received and red_received: + produced_code[produced_code_index] = ReturnCode.RECEIVING_FROM_BOTH + break + log_message('S: Waiting for receiving samples', + verbosity) + child_sub.expect( + [ + '\[[0-9][0-9]\]', # index = 0 + pexpect.TIMEOUT # index = 1 + ], + timeout + ) + # Two Publishers and One Subscriber to test that + # the Subscriber only receives samples from + # the Publisher with the greatest ownership + elif test_name == 'Test_Ownership_4': + first_received = False + second_received = False + list_data_received_second = [] + for x in range(0,80,1): + sub_string = re.search('[0-9]{3} [0-9]{3}', + child_sub.before) + try: + list_data_received_second.append(samples_sent.get(True, 5)) + except: + break; + if sub_string.group(0) not in list_data_received_second: + first_received = True + elif sub_string.group(0) in list_data_received_second \ + and first_received: + second_received = True + produced_code[produced_code_index] = ReturnCode.RECEIVING_FROM_BOTH + log_message('S: Waiting for receiving samples', + verbosity) + child_sub.expect( + [ + '\[[0-9][0-9]\]', # index = 0 + pexpect.TIMEOUT # index = 1 + ], + timeout + ) + + if second_received == False: + produced_code[produced_code_index] = ReturnCode.RECEIVING_FROM_ONE + + else: + produced_code[produced_code_index] = ReturnCode.OK + + subscriber_finished.set() # set subscriber as finished + log_message('S: Waiting for Publisher to finish', verbosity) + publisher_finished.wait() # wait for publisher to finish + return + + +def run_publisher_shape_main( + name_executable: str, + parameters: str, + test_name: str, + produced_code: "list[int]", + produced_code_index: int, + samples_sent: Queue, + verbosity: bool, + timeout: int, + file: tempfile.TemporaryFile, + subscriber_finished: Event, + publisher_finished: Event): + + """ This function runs the publisher application with + the specified parameters. Then it saves the + return code in the variable produced_code. + + name_executable: <> name of the shape_main application to run + as a Publisher + parameters <>: shape_main application parameter list + test_name <>: name of the test that is being tested + produced_code <>: this variable will be overwritten with + the obtained ReturnCode + produced_code_index <>: index of the produced_code list + where the ReturnCode is saved + samples_sent <>: this variable contains the samples + the Publisher sends + verbosity <>: print debug information + timeout <>: time pexpect waits until it matches a pattern + file <>: temporal file to save shape_main application output + subscriber_finished <>: object event from multiprocessing + that is set when the subscriber is finished + publisher_finished <>: object event from multiprocessing + that is set when the publisher is finished + + The function runs the Shape Application as a Publisher + with the parameters defined. + The Publisher Shape Application follows the next steps: + * The topic is created + * The Data Writer is created + * The Data Writer matches with a Data Reader + * The Data Writer sends data + + If the Shape Application passes one step, it prints a specific + string pattern. This function matches that pattern and and waits + for the next input string from the Shape Application. If the + ShapeApplication stops at some step, it prints an error message. + When this function matches an error string (or doesn't match + an expected pattern in the specified timeout), + the corresponding ReturnCode is saved in + produced_code[produced_code_index] and the process finishes. + """ + + # Step 1 : run the executable + log_message('Running Shape Application Publisher', verbosity) + child_pub = pexpect.spawnu(f'{name_executable} {parameters}') + child_pub.logfile = file + + # Step 2 : Check if the topic is created + log_message('P: Waiting for topic creation', verbosity) + index = child_pub.expect( + [ + 'Create topic:', # index == 0 + pexpect.TIMEOUT, # index == 1 + 'please specify topic name', # index == 2 + 'unrecognized value', # index == 3 + pexpect.EOF # index == 4 + ], + timeout + ) + + if index == 1 or index == 2 or index == 4: + produced_code[produced_code_index] = ReturnCode.TOPIC_NOT_CREATED + elif index == 3: + produced_code[produced_code_index] = ReturnCode.UNRECOGNIZED_VALUE + elif index == 0: + # Step 3 : Check if the writer is created + log_message('P: Waiting for DW creation', verbosity) + index = child_pub.expect( + [ + 'Create writer for topic', # index = 0 + pexpect.TIMEOUT # index = 1 + ], + timeout + ) + if index == 1: + produced_code[produced_code_index] = ReturnCode.WRITER_NOT_CREATED + elif index == 0: + # Step 4 : Check if the writer matches the reader + log_message('P: Waiting for DR matching', verbosity) + index = child_pub.expect( + [ + 'on_publication_matched()', # index = 0 + pexpect.TIMEOUT, # index = 1 + 'on_offered_incompatible_qos' # index = 2 + ], + timeout + ) + if index == 1: + produced_code[produced_code_index] = ReturnCode.READER_NOT_MATCHED + elif index == 2: + produced_code[produced_code_index] = ReturnCode.INCOMPATIBLE_QOS + elif index == 0: + if '-w' in parameters: + #Step 5: Check if the writer sends the samples + log_message('P: Waiting for sending samples', verbosity) + index = child_pub.expect( + [ + '\[[0-9][0-9]\]', # index = 0 + pexpect.TIMEOUT # index = 1 + ], + timeout + ) + if index == 0: + produced_code[produced_code_index] = ReturnCode.OK + # With these tests we check if we receive the data correctly, + # in order to do it we are saving the samples sent + if test_name == 'Test_Reliability_4' \ + or test_name == 'Test_Ownership_4': + for x in range(0, 80 ,1): + pub_string = re.search('[0-9]{3} [0-9]{3}', + child_pub.before ) + samples_sent.put(pub_string.group(0)) + log_message('P: Waiting for sending samples', + verbosity) + child_pub.expect([ + '\[[0-9][0-9]\]', # index = 0 + pexpect.TIMEOUT # index = 1 + ], + timeout + ) + + elif index == 1: + produced_code[produced_code_index] = ReturnCode.DATA_NOT_SENT + else: + produced_code[produced_code_index] = ReturnCode.OK + + log_message('P: Waiting for Subscriber to finish', verbosity) + subscriber_finished.wait() # wait for subscriber to finish + publisher_finished.set() # set publisher as finished + return + +def run_test( + name_executable_pub: str, + name_executable_sub: str, + test_case: TestCase, + param_pub: str, + param_sub: str, + expected_code_pub: ReturnCode, + expected_code_sub: ReturnCode, + verbosity: bool, + timeout: int): + + """ Run the Publisher and the Subscriber and check the ReturnCode + + name_executable_pub <>: name of the shape_main application to run + as a Publisher + name_executable_sub <>: name of the shape_main application to run + as a Subscriber + test_case <>: testCase object to test + param_pub <>: shape_main application publisher parameter list + param_sub <>: shape_main application subscriber parameter list + expected_code_pub <>: ReturnCode the Publisher would obtain + in a non error situation + expected_code_sub <>: ReturnCode the Subscriber would obtain + in a non error situation + verbosity <>: print debug information + timeout <>: time pexpect waits until it matches a pattern + + The function runs two different processes: one publisher + application and one subscriber application. + Then it checks that the code obtained is the expected one. + """ + log_message(f'run_test parameters: \ + name_executable_pub: {name_executable_pub} \ + name_executable_sub: {name_executable_sub} \ + test_case: {test_case.name} \ + param_pub: {param_pub} \ + param_sub: {param_sub} \ + expected_code_pub: {expected_code_pub} \ + expected_code_sub: {expected_code_sub} \ + verbosity: {verbosity} \ + timeout: {timeout}', + verbosity) + + manager = Manager() + # used for storing the obtained ReturnCode + # (from Publisher and Subscriber) + return_code = manager.list(range(2)) + data = Queue() # used for storing the samples + + subscriber_finished = Event() + publisher_finished = Event() + + log_message('Creating temporary files', verbosity) + file_publisher = tempfile.TemporaryFile(mode='w+t') + file_subscriber = tempfile.TemporaryFile(mode='w+t') + + # Manager is a shared memory section where both processes can access. + # return_code is a list of two elements where the different processes + # (publisher and subscriber applications) copy their ReturnCode. + # These ReturnCodes are identified by the index within the list, + # every index identifies one application. Therefore, only one application + # must modifies one element of the list. + # Once both processes are finished, the list contains the ReturnCode + # in the corresponding index. This index is set manually and we need it + # in order to use it later. + # Example: + # Processes: + # - Publisher Process (produced_code_index = 1) + # - Subscriber Process (produced_code_index = 0) + # Code contains: + # - return_code[1] contains Publisher Application ReturnCode + # - return_code[0] contains Subscriber Application ReturnCode + publisher_index = 1 + subscriber_index = 0 + + log_message('Assigning tasks to processes', verbosity) + pub = Process(target=run_publisher_shape_main, + kwargs={ + 'name_executable':name_executable_pub, + 'parameters':param_pub, + 'test_name':test_case.name, + 'produced_code':return_code, + 'produced_code_index':publisher_index, + 'samples_sent':data, + 'verbosity':verbosity, + 'timeout':timeout, + 'file':file_publisher, + 'subscriber_finished':subscriber_finished, + 'publisher_finished':publisher_finished + }) + sub = Process(target=run_subscriber_shape_main, + kwargs={ + 'name_executable':name_executable_sub, + 'parameters':param_sub, + 'test_name':test_case.name, + 'produced_code':return_code, + 'produced_code_index':subscriber_index, + 'samples_sent':data, + 'verbosity':verbosity, + 'timeout':timeout, + 'file':file_subscriber, + 'subscriber_finished':subscriber_finished, + 'publisher_finished':publisher_finished + }) + + log_message('Running Subscriber process', verbosity) + sub.start() + log_message('Running Publisher process', verbosity) + pub.start() + # Wait until these processes finish + sub.join() + pub.join() + + log_message('Reading information from temporary files', verbosity) + file_publisher.seek(0) + file_subscriber.seek(0) + information_publisher = file_publisher.read() + information_subscriber = file_subscriber.read() + + test_case.param_pub = param_pub + test_case.param_sub = param_sub + + # code[1] contains shape_main publisher application ReturnCode + # and code[0] the shape_main subscriber application ReturnCode. + if expected_code_pub == return_code[publisher_index] and expected_code_sub == return_code[subscriber_index]: + print (f'{test_case.name} : Ok') + + else: + print(f'Error in : {test_case.name}') + print(f'Publisher expected code: {expected_code_pub}; \ + Code found: {return_code[publisher_index].name}') + print(f'Subscriber expected code: {expected_code_sub}; \ + Code found: {return_code[subscriber_index].name}') + log_message(f'\nInformation about the Publisher:\n \ + {information_publisher} \ + \nInformation about the Subscriber:\n \ + {information_subscriber}', verbosity) + + additional_info_pub = information_publisher.replace('\n', '
') + additional_info_sub = information_subscriber.replace('\n', '
') + test_case.result = [Failure(f' \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
\ + Expected CodeCode Produced
Publisher{expected_code_pub.name}{return_code[publisher_index].name}
Subscriber{expected_code_sub.name}{return_code[subscriber_index].name}
\ + Information Publisher: \ +
{additional_info_pub}
\ + Information Subscriber: \ +
{additional_info_sub}')] + + file_publisher.close() + file_subscriber.close() + + +def run_test_pub_pub_sub( + name_executable_pub: str, + name_executable_sub: str, + test_case: TestCase, + param_pub1: str, + param_pub2: str, + param_sub: str, + expected_code_pub1: ReturnCode, + expected_code_pub2: ReturnCode, + expected_code_sub: ReturnCode, + verbosity: bool, + timeout: int): + + """ Run two Publisher and one Subscriber and check the ReturnCode + + name_executable_pub <>: name of the shape_main application to run + as a Publisher + name_executable_sub <>: name of the shape_main application to run + as a Subscriber + test_case <>: testCase object to test + param_pub1 <>: shape_main application publisher 1 parameter list + param_pub2 <>: shape_main application publisher 2 parameter list + param_sub <>: shape_main application subscriber parameter list + expected_code_pub1 <>: ReturnCode the Publisher 1 would obtain + in a non error situation + expected_code_pub2 <>: ReturnCode the Publisher 2 would obtain + in a non error situation + expected_code_sub <>: ReturnCode the Subscriber would obtain + in a non error situation + verbosity <>: print debug information + timeout <>: time pexpect waits until it matches a pattern + + This function runs three different processes: two publisher + applications and one subscriber application. + Then it checks that the code obtained is the expected one. + """ + log_message(f'run_test parameters: \ + name_executable_pub: {name_executable_pub} \ + name_executable_sub: {name_executable_sub} \ + test_case: {test_case.name} \ + param_pub1: {param_pub1} \ + param_pub2: {param_pub2} \ + param_sub: {param_sub} \ + expected_code_pub1: {expected_code_pub1} \ + expected_code_pub2: {expected_code_pub2} \ + expected_code_sub: {expected_code_sub} \ + verbosity: {verbosity} \ + timeout: {timeout}', + verbosity) + + manager = Manager() + # used for storing the obtained ReturnCode + # (from Publisher 1, Publisher 2 and Subscriber) + code = manager.list(range(3)) + data = Queue() # used for storing the samples + + subscriber_finished = Event() + publisher_finished = Event() + + log_message('Creating temporary files', verbosity) + file_subscriber = tempfile.TemporaryFile(mode='w+t') + file_publisher1 = tempfile.TemporaryFile(mode='w+t') + file_publisher2 = tempfile.TemporaryFile(mode='w+t') + + # Manager is a shared memory section where the three processes can access. + # return_code is a list of three elements where the different processes + # (publisher 1, publisher 2 and subscriber applications) copy their ReturnCode. + # These ReturnCodes are identified by the index within the list, + # every index identifies one application. Therefore, only one application + # must modifies one element of the list. + # Once both processes are finished, the list contains the ReturnCode + # in the corresponding index. This index is set manually and we need it + # in order to use it later. + # Example: + # Processes: + # - Publisher 1 Process (produced_code_index = 1) + # - Publisher 2 Process (produced_code_index = 2) + # - Subscriber Process (produced_code_index = 0) + # Code contains: + # - return_code[1] contains Publisher 1 Application ReturnCode + # - return_code[2] contains Publisher 2 Application ReturnCode + # - return_code[0] contains Subscriber Application ReturnCode + publisher1_index = 1 + publisher2_index = 2 + subscriber_index = 0 + log_message('Assigning tasks to processes', verbosity) + if test_case.name == 'Test_Ownership_3': + pub1 = Process(target=run_publisher_shape_main, + kwargs={ + 'name_executable':name_executable_pub, + 'parameters':param_pub1, + 'test_name':test_case.name, + 'produced_code':code, + 'produced_code_index':publisher1_index, + 'samples_sent':data, + 'verbosity':verbosity, + 'timeout':timeout, + 'file':file_publisher1, + 'subscriber_finished':subscriber_finished, + 'publisher_finished':publisher_finished + }) + pub2 = Process(target=run_publisher_shape_main, + kwargs={ + 'name_executable':name_executable_pub, + 'parameters':param_pub2, + 'test_name':test_case.name, + 'produced_code':code, + 'produced_code_index':publisher2_index, + 'samples_sent':data, + 'verbosity':verbosity, + 'timeout':timeout, + 'file':file_publisher2, + 'subscriber_finished':subscriber_finished, + 'publisher_finished':publisher_finished + }) + sub = Process(target=run_subscriber_shape_main, + kwargs={ + 'name_executable':name_executable_sub, + 'parameters':param_sub, + 'test_name':test_case.name, + 'produced_code':code, + 'produced_code_index':subscriber_index, + 'samples_sent':data, + 'verbosity':verbosity, + 'timeout':timeout, + 'file':file_subscriber, + 'subscriber_finished':subscriber_finished, + 'publisher_finished':publisher_finished + }) + + if test_case.name == 'Test_Ownership_4': + pub1 = Process(target=run_publisher_shape_main, + kwargs={ + 'name_executable':name_executable_pub, + 'parameters':param_pub1, + 'test_name':test_case.name, + 'produced_code':code, + 'produced_code_index':publisher1_index, + 'samples_sent':Queue(), + 'verbosity':verbosity, + 'timeout':timeout, + 'file':file_publisher1, + 'subscriber_finished':subscriber_finished, + 'publisher_finished':publisher_finished + }) + pub2 = Process(target=run_publisher_shape_main, + kwargs={ + 'name_executable':name_executable_pub, + 'parameters':param_pub2, + 'test_name':test_case.name, + 'produced_code':code, + 'produced_code_index':publisher2_index, + 'samples_sent':data, + 'verbosity':verbosity, + 'timeout':timeout, + 'file':file_publisher2, + 'subscriber_finished':subscriber_finished, + 'publisher_finished':publisher_finished + }) + sub = Process(target=run_subscriber_shape_main, + kwargs={ + 'name_executable':name_executable_sub, + 'parameters':param_sub, + 'test_name':test_case.name, + 'produced_code':code, + 'produced_code_index':subscriber_index, + 'samples_sent':data, + 'verbosity':verbosity, + 'timeout':timeout, + 'file':file_subscriber, + 'subscriber_finished':subscriber_finished, + 'publisher_finished':publisher_finished + }) + + log_message('Running Subscriber process', verbosity) + sub.start() + log_message('Running Publisher 1 process', verbosity) + pub1.start() + time.sleep(1) # used to make the two Publishers have different seeds + # to generate the samples + log_message('Running Publisher 2 process', verbosity) + pub2.start() + + sub.join() + pub1.join() + pub2.join() + + log_message('Reading information from temporary files', verbosity) + file_publisher1.seek(0) + file_publisher2.seek(0) + file_subscriber.seek(0) + information_publisher1 = file_publisher1.read() + information_publisher2 = file_publisher2.read() + information_subscriber = file_subscriber.read() + + test_case.param_pub1 = param_pub1 + test_case.param_pub2 = param_pub2 + test_case.param_sub = param_sub + + # code[1] contains shape_main publisher 1 application ReturnCode, + # code[2] the shape_main publisher 2 application ReturnCode + # and code[0] the shape_main subscriber application ReturnCode. + if expected_code_pub1 == code[publisher1_index] and expected_code_sub == code[subscriber_index] \ + and expected_code_pub2 == code[publisher2_index]: + print (f'{test_case.name} : Ok') + + else: + print(f'Error in : {test_case.name}') + print(f'Publisher 1 expected code: {expected_code_pub1}; \ + Code found: {code[publisher1_index].name}') + print(f'Publisher 2 expected code: {expected_code_pub2}; \ + Code found: {code[publisher2_index].name}') + print(f'Subscriber expected code: {expected_code_sub}; \ + Code found: {code[subscriber_index].name}') + log_message(f'\nInformation about the Publisher 1:\n \ + {information_publisher1} \ + \nInformation about the Publisher 2:\n \ + {information_publisher2} \ + \nInformation about the Subscriber:\n \ + {information_subscriber}', verbosity) + + additional_info_pub1 = information_publisher1.replace('\n', '
') + additional_info_pub2 = information_publisher2.replace('\n', '
') + additional_info_sub = information_subscriber.replace('\n', '
') + + test_case.result = [Failure(f' \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
\ + Expected CodeCode Produced
Publisher 1{expected_code_pub1.name}{code[publisher1_index].name}
Publisher 2{expected_code_pub2.name}{code[publisher2_index].name}
Subscriber{expected_code_sub.name}{code[subscriber_index].name}
\ + Information Publisher 1: \ +
{additional_info_pub1}
\ + Information Publisher 2: \ +
{additional_info_pub2}
\ + Information Subscriber: \ +
{additional_info_sub}')] + + file_subscriber.close() + file_publisher1.close() + file_publisher2.close() + +class Arguments: + def parser(): + parser = argparse.ArgumentParser( + description='Interoperability Test', + add_help=True) + + gen_opts = parser.add_argument_group(title='general options') + gen_opts.add_argument('-P', '--publisher', + default=None, + required=True, + type=str, + metavar='publisher_name', + help='Publisher Shape Application') + gen_opts.add_argument('-S', '--subscriber', + default=None, + required=True, + type=str, + metavar='subscriber_name', + help='Subscriber Shape Application') + + optional = parser.add_argument_group(title='optional parameters') + optional.add_argument('-v','--verbose', + default=False, + required=False, + action='store_true', + help='Print more information to stdout.') + + out_opts = parser.add_argument_group(title='output options') + out_opts.add_argument('-f', '--output-format', + default='junit', + required=False, + type=str, + choices=['junit', 'csv', 'xlxs'], + help='Output format.') + out_opts.add_argument('-o', '--output-name', + required=False, + metavar='filename', + type=str, + help='Report filename.') + + return parser + +def main(): + + parser = Arguments.parser() + args = parser.parse_args() + + options = { + 'publisher': args.publisher, + 'subscriber': args.subscriber, + 'verbosity': args.verbose, + } + # The executables's names are supposed to follow the pattern: name_shape_main + # We will keep only the part of the name that is useful, deleting the path and + # the substring _shape_main. + # Example: if the shape_main application's name (including the path) is: + # ./srcCxx/objs/x64Linux4gcc7.3.0/rti_connext_dds-6.1.1_shape_main_linux + # we will take the substring rti_connext_dds-6.1.1. + # That will be the name that will appear in the report. + name_publisher = (options['publisher'].split('_shape')[0]).split('/')[-1] + name_subscriber = (options['subscriber'].split('_shape')[0]).split('/')[-1] + + if args.output_format is None: + options['output_format'] = 'junit' + else: + options['output_format'] = args.output_format + if args.output_name is None: + now = datetime.now() + date_time = now.strftime('%Y%m%d-%H_%M_%S') + options['filename_report'] = name_publisher+'-'+name_subscriber \ + +'-'+date_time+'.xml' + xml = JUnitXml() + + else: + options['filename_report'] = args.output_name + file_exists = exists(options['filename_report']) + if file_exists: + xml = JUnitXml.fromfile(options['filename_report']) + else: + xml = JUnitXml() + + # TestSuite is a class from junitparser that will contain the + # results of running different TestCases between two shape_main + # applications. A TestSuite contains a collection of TestCases. + suite = TestSuite(f"{name_publisher}---{name_subscriber}") + TestCase.param_pub = Attr('Parameters_Publisher') + TestCase.param_sub = Attr('Parameters_Subscriber') + TestCase.param_pub1 = Attr('Parameters_Publisher_1') + TestCase.param_pub2 = Attr('Parameters_Publisher_2') + + timeout = 10 + now = datetime.now() + for k, v in rtps_test_suite_1.items(): + # TestCase is an class from junitparser whose attributes + # are: name and result (OK, Failure, Error and Skipped), + # apart from other custom attributes (in this case param_pub, + # param_sub, param_pub1 and param_pub2). + case = TestCase(f'{k}') + now_test_case = datetime.now() + if k == 'Test_Ownership_3' or k == 'Test_Ownership_4': + run_test_pub_pub_sub(name_executable_pub=options['publisher'], + name_executable_sub=options['subscriber'], + test_case=case, + param_pub1=v[0], + param_pub2=v[1], + param_sub=v[2], + expected_code_pub1=v[3], + expected_code_pub2=v[4], + expected_code_sub=v[5], + verbosity=options['verbosity'], + timeout=timeout + ) + + else: + run_test(name_executable_pub=options['publisher'], + name_executable_sub=options['subscriber'], + test_case=case, + param_pub=v[0], + param_sub=v[1], + expected_code_pub=v[2], + expected_code_sub=v[3], + verbosity=options['verbosity'], + timeout=timeout + ) + case.time = (datetime.now() - now_test_case).total_seconds() + suite.add_testcase(case) + + suite.time = (datetime.now() - now).total_seconds() + xml.add_testsuite(suite) + + xml.write(options['filename_report']) + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..502d7d6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +argcomplete==2.0.0 +pexpect==4.8.0 +junitparser==2.8.0 diff --git a/rtps_test_utilities.py b/rtps_test_utilities.py new file mode 100644 index 0000000..68827bd --- /dev/null +++ b/rtps_test_utilities.py @@ -0,0 +1,37 @@ +from enum import Enum + +class ReturnCode(Enum): + """" + Codes to give information about Shape Applications' behavior. + + OK : Publisher/Subscriber sent/received data correctly + UNRECOGNIZED_VALUE : Parameters for the Publisher/Subscriber not supported + TOPIC_NOT_CREATED : Publisher/Subscriber does not create the topic + READER_NOT_CREATED : Subscriber does not create the Data Reader + WRITER_NOT_CREATED : Publisher does not create the Data Writer + FILTER_NOT_CREATED : Subscriber does not create the content filter + INCOMPATIBLE_QOS : Publisher/Subscriber with incompatible QoS. + READER_NOT_MATCHED : Publisher does not find any compatible Data Reader + WRITER_NOT_MATCHED : Subscriber does not find any compatible Data Writer + WRITER_NOT_ALIVE : Subscriber does not find any live Data Writer + DATA_NOT_RECEIVED : Subscriber does not receive the data + DATA_NOT_SENT : Publisher does not send the data + DATA_NOT_CORRECT : Subscriber does not find the data expected + RECEIVING_FROM_ONE : Subscriber receives from one Publisher + RECEIVING_FROM_BOTH : Subscriber receives from two Publishers + """ + OK = 0 + UNRECOGNIZED_VALUE = 1 + TOPIC_NOT_CREATED = 2 + READER_NOT_CREATED = 3 + WRITER_NOT_CREATED = 4 + FILTER_NOT_CREATED = 5 + INCOMPATIBLE_QOS = 6 + READER_NOT_MATCHED = 7 + WRITER_NOT_MATCHED = 8 + WRITER_NOT_ALIVE = 9 + DATA_NOT_RECEIVED = 10 + DATA_NOT_SENT = 11 + DATA_NOT_CORRECT = 12 + RECEIVING_FROM_ONE = 13 + RECEIVING_FROM_BOTH = 14 diff --git a/srcCxx/makefile_rti_connext_dds_linux_6.1.1 b/srcCxx/makefile_rti_connext_dds_linux_6.1.1 new file mode 100644 index 0000000..86a78f2 --- /dev/null +++ b/srcCxx/makefile_rti_connext_dds_linux_6.1.1 @@ -0,0 +1,83 @@ +###################################################################### +# To compile, type: +# make -f makefile_rti_connext_dds_linux_6.1.1 +# To compile with the Debug option, use: +# make -f makefile_rti_connext_dds_linux_6.1.1 DEBUG=1 +# +# This makefile assumes that your build environment is already correctly +# configured. (For example, the correct version of your compiler and +# linker should be on your PATH.) +# +# You should set the environemnt variable NDDSHOME to point to where +# RTI Connext DDS is installed. +# +###################################################################### + +# If undefined in the environment default NDDSHOME to install dir +ifndef NDDSHOME +$(error NDDSHOME not defined) +endif + +COMPILER_FLAGS = -m64 +LINKER_FLAGS = -m64 -static-libgcc + +split_path_name = $(subst /, , $(NDDSHOME)) + +version_name = $(lastword $(split_path_name)) +common_name = "_shape_main_linux" +executable_name = $(version_name)$(common_name) + +TARGET_ARCH = x64Linux4gcc7.3.0 + +ifndef COMPILER +COMPILER = g++ +endif + +ifndef LINKER +LINKER = g++ +endif + +SYSLIBS = -ldl -lnsl -lm -lpthread -lrt + +ifeq ($(DEBUG),1) +COMPILER_FLAGS += -g -O0 +LINKER_FLAGS += -g +LIBS = -L$(NDDSHOME)/lib/$(TARGET_ARCH) \ + -lnddscppzd -lnddsczd -lnddscorezd $(SYSLIBS) +else +# This option strips the executable symbols +LINKER_FLAGS += -s +LIBS = -L$(NDDSHOME)/lib/$(TARGET_ARCH) \ + -lnddscppz -lnddscz -lnddscorez $(SYSLIBS) +endif + + +DEFINES = -DRTI_UNIX -DRTI_LINUX -DRTI_CONNEXT_DDS + +INCLUDES = -I. -I$(NDDSHOME)/include -I$(NDDSHOME)/include/ndds + +OBJDIR := objs/$(TARGET_ARCH) + +CDRSOURCES := shape.idl +AUTOGENSOURCES := shapeSupport.cxx shapePlugin.cxx shape.cxx + +EXEC := $(executable_name) +AUTOGENOBJS := $(addprefix $(OBJDIR)/, $(AUTOGENSOURCES:%.cxx=%.o)) + +$(OBJDIR)/$(EXEC) : $(AUTOGENSOURCES) $(AUTOGENOBJS) $(OBJDIR)/shape_main.o + $(LINKER) $(LINKER_FLAGS) -o $@ $(OBJDIR)/shape_main.o $(AUTOGENOBJS) $(LIBS) + +$(OBJDIR)/%.o : %.cxx + $(COMPILER) $(COMPILER_FLAGS) -o $@ $(DEFINES) $(INCLUDES) -c $< + +shape_main.cxx : shape_configurator_rti_connext_dds.h + +# Generate type-specific sources +$(AUTOGENSOURCES) : $(CDRSOURCES) + $(NDDSHOME)/bin/rtiddsgen $(CDRSOURCES) -replace -language C++ + +$(AUTOGENOBJS): | objs/$(TARGET_ARCH) + +objs/$(TARGET_ARCH): + echo "Making directory objs/$(TARGET_ARCH)"; + mkdir -p objs/$(TARGET_ARCH) diff --git a/srcCxx/shape_main.cxx b/srcCxx/shape_main.cxx index 5b7154a..88ca041 100644 --- a/srcCxx/shape_main.cxx +++ b/srcCxx/shape_main.cxx @@ -15,6 +15,7 @@ #include #include #include +#include #if defined(RTI_CONNEXT_DDS) #include "shape_configurator_rti_connext_dds.h" @@ -62,6 +63,11 @@ install_sig_handlers() return 0; } +enum Verbosity +{ + ERROR=1, + DEBUG=2, +}; /*************************************************************/ class ShapeOptions { @@ -69,12 +75,13 @@ class ShapeOptions { DomainId_t domain_id; ReliabilityQosPolicyKind reliability_kind; DurabilityQosPolicyKind durability_kind; + DataRepresentationId_t data_representation; int history_depth; int ownership_strength; - char * topic_name; - char * color; - char * partition; + char *topic_name; + char *color; + char *partition; bool publish; bool subscribe; @@ -88,6 +95,9 @@ class ShapeOptions { int xvel; int yvel; + Verbosity verbosity; + bool print_writer_samples; + public: //------------------------------------------------------------- ShapeOptions() @@ -95,6 +105,7 @@ class ShapeOptions { domain_id = 0; reliability_kind = RELIABLE_RELIABILITY_QOS; durability_kind = VOLATILE_DURABILITY_QOS; + data_representation = XCDR_DATA_REPRESENTATION; history_depth = -1; /* means default */ ownership_strength = -1; /* means shared */ @@ -113,6 +124,9 @@ class ShapeOptions { xvel = 3; yvel = 3; + + verbosity = Verbosity::ERROR; + print_writer_samples = false; } //------------------------------------------------------------- @@ -124,7 +138,7 @@ class ShapeOptions { } //------------------------------------------------------------- - void print_usage( const char * prog ) + void print_usage( const char *prog ) { printf("%s: \n", prog); printf(" -d : domain id (default: 0)\n"); @@ -141,116 +155,211 @@ class ShapeOptions { printf(" t: TRANSIENT, p: PERSISTENT]\n"); printf(" -P : publish samples\n"); printf(" -S : subscribe samples\n"); + printf(" -x [1|2] : set data representation [1: XCDR, 2: XCDR2]\n"); + printf(" -w : print Publisher's samples\n"); + printf(" -v [e|d] : set log message verbosity [e: ERROR, d: DEBUG]\n"); } //------------------------------------------------------------- bool validate() { if (topic_name == NULL) { - printf("please specify topic name [-t]\n"); + log_message("please specify topic name [-t]", Verbosity::ERROR); return false; } if ( (!publish) && (!subscribe) ) { - printf("please specify publish [-P] or subscribe [-S]\n"); + log_message("please specify publish [-P] or subscribe [-S]", Verbosity::ERROR); return false; } if ( publish && subscribe ) { - printf("please specify only one of: publish [-P] or subscribe [-S]\n"); + log_message("please specify only one of: publish [-P] or subscribe [-S]", Verbosity::ERROR); return false; } if (publish && (color == NULL) ) { color = strdup("BLUE"); - printf("warning: color was not specified, defaulting to \"BLUE\"\n"); + log_message("warning: color was not specified, defaulting to \"BLUE\"", Verbosity::ERROR); } return true; } //------------------------------------------------------------- - bool parse(int argc, char * argv[]) + bool parse(int argc, char *argv[]) { int opt; bool parse_ok = true; - // double d; - while ((opt = getopt(argc, argv, "hbrc:d:D:f:i:k:p:s:t:PS")) != -1) + while ((opt = getopt(argc, argv, "hbrc:d:D:f:i:k:p:s:x:t:v:wPS")) != -1) { switch (opt) { + case 'v': + { + if (optarg[0] != '\0') + { + switch (optarg[0]) + { + case 'd': + { + verbosity = Verbosity::DEBUG; + break; + } + case 'e': + { + verbosity = Verbosity::ERROR; + break; + } + default: + { + log_message("unrecognized value for verbosity "+optarg[0], Verbosity::ERROR); + parse_ok = false; + } + } + } + break; + } + case 'w': + { + print_writer_samples = true; + break; + } case 'b': - reliability_kind = BEST_EFFORT_RELIABILITY_QOS; - break; + { + reliability_kind = BEST_EFFORT_RELIABILITY_QOS; + break; + } case 'c': - color = strdup(optarg); - break; + { + color = strdup(optarg); + break; + } case 'd': - domain_id = atoi(optarg); - break; + { + domain_id = atoi(optarg); + break; + } case 'D': + { if (optarg[0] != '\0') { switch (optarg[0]) { case 'v': - durability_kind = VOLATILE_DURABILITY_QOS; - break; + { + durability_kind = VOLATILE_DURABILITY_QOS; + break; + } case 'l': - durability_kind = TRANSIENT_LOCAL_DURABILITY_QOS; - break; + { + durability_kind = TRANSIENT_LOCAL_DURABILITY_QOS; + break; + } case 't': - durability_kind = TRANSIENT_DURABILITY_QOS; - break; + { + durability_kind = TRANSIENT_DURABILITY_QOS; + break; + } case 'p': - durability_kind = PERSISTENT_DURABILITY_QOS; - break; + { + durability_kind = PERSISTENT_DURABILITY_QOS; + break; + } default: - printf("unrecognized value for durability '%c'\n", optarg[0]); - parse_ok = false; + { + log_message("unrecognized value for durability "+optarg[0], Verbosity::ERROR); + parse_ok = false; + } } } break; + } case 'i': - timebasedfilter_interval = atoi(optarg); - break; + { + timebasedfilter_interval = atoi(optarg); + break; + } case 'f': - deadline_interval = atoi(optarg); - break; + { + deadline_interval = atoi(optarg); + break; + } case 'k': - history_depth = atoi(optarg); - if (history_depth <= 0) { - printf("unrecognized value for history_depth '%c'\n", optarg[0]); - parse_ok = false; + { + history_depth = atoi(optarg); + if (history_depth < 0) { + log_message("unrecognized value for history_depth "+optarg[0], Verbosity::ERROR); + parse_ok = false; + } + break; } - break; case 'p': - partition = strdup(optarg); - break; + { + partition = strdup(optarg); + break; + } case 'r': - reliability_kind = RELIABLE_RELIABILITY_QOS; - break; + { + reliability_kind = RELIABLE_RELIABILITY_QOS; + break; + } case 's': - ownership_strength = atoi(optarg); - if (ownership_strength <= 0) { - printf("unrecognized value for ownership_strength '%c'\n", optarg[0]); - parse_ok = false; + { + ownership_strength = atoi(optarg); + if (ownership_strength < -1) { + log_message("unrecognized value for ownership_strength "+optarg[0], Verbosity::ERROR); + parse_ok = false; + } + break; } - break; case 't': - topic_name = strdup(optarg); - break; + { + topic_name = strdup(optarg); + break; + } case 'P': - publish = true; - break; + { + publish = true; + break; + } case 'S': - subscribe = true; - break; - + { + subscribe = true; + break; + } case 'h': - print_usage(argv[0]); - exit(0); - break; - + { + print_usage(argv[0]); + exit(0); + break; + } + case 'x': + { + if (optarg[0] != '\0') + { + switch (optarg[0]) + { + case '1': + { + data_representation = XCDR_DATA_REPRESENTATION; + break; + } + case '2': + { + data_representation = XCDR2_DATA_REPRESENTATION; + break; + } + default: + { + log_message("unrecognized value for data representation "+optarg[0], Verbosity::ERROR); + parse_ok = false; + } + } + } + break; + } case '?': - parse_ok = false; - break; + { + parse_ok = false; + break; + } } } @@ -261,8 +370,42 @@ class ShapeOptions { if ( !parse_ok ) { print_usage(argv[0]); } + log_message("Shape Options: \ + \n DomainId = "+std::to_string(domain_id)+ + "\n ReliabilityKind = "+std::to_string(reliability_kind)+ + "\n DurabilityKind = "+std::to_string(durability_kind)+ + "\n DataRepresentation = "+std::to_string(data_representation)+ + "\n HistoryDepth = "+std::to_string(history_depth)+ + "\n OwnershipStrength = "+std::to_string(ownership_strength)+ + "\n Publish = "+std::to_string(publish)+ + "\n Subscribe = "+std::to_string(subscribe)+ + "\n TimeBasedFilterInterval = "+std::to_string(timebasedfilter_interval)+ + "\n DeadlineInterval = "+std::to_string(deadline_interval)+ + "\n Verbosity = "+std::to_string(verbosity), + Verbosity::DEBUG); + if (topic_name != NULL){ + log_message(" Topic = "+std::string(topic_name), + Verbosity::DEBUG); + } + if (color != NULL) { + log_message(" Color = "+std::string(color), + Verbosity::DEBUG); + } + if (partition != NULL) { + log_message(" Partition = "+std::string(partition), Verbosity::DEBUG); + } return parse_ok; } + + + + void log_message(std::string message, Verbosity level_verbosity) + { + if (level_verbosity <= verbosity) { + std::cout << message << std::endl; + } + } + }; @@ -271,17 +414,17 @@ class ShapeOptions { class DPListener : public DomainParticipantListener { public: - void on_inconsistent_topic ( Topic * topic, const InconsistentTopicStatus &) { - const char * topic_name = topic->get_name(); - const char * type_name = topic->get_type_name(); + void on_inconsistent_topic (Topic *topic, const InconsistentTopicStatus &) { + const char *topic_name = topic->get_name(); + const char *type_name = topic->get_type_name(); printf("%s() topic: '%s' type: '%s'\n", __FUNCTION__, topic_name, type_name); } - void on_offered_incompatible_qos( DataWriter *dw, const OfferedIncompatibleQosStatus & status ) { - Topic * topic = dw->get_topic( ); - const char * topic_name = topic->get_name( ); - const char * type_name = topic->get_type_name( ); - const char * policy_name = NULL; + void on_offered_incompatible_qos(DataWriter *dw, const OfferedIncompatibleQosStatus & status) { + Topic *topic = dw->get_topic( ); + const char *topic_name = topic->get_name( ); + const char *type_name = topic->get_type_name( ); + const char *policy_name = NULL; policy_name = get_qos_policy_name(status.last_policy_id); printf("%s() topic: '%s' type: '%s' : %d (%s)\n", __FUNCTION__, topic_name, type_name, @@ -289,61 +432,61 @@ class DPListener : public DomainParticipantListener policy_name ); } - void on_publication_matched (DataWriter * dw, const PublicationMatchedStatus & status) { - Topic * topic = dw->get_topic( ); - const char * topic_name = topic->get_name( ); - const char * type_name = topic->get_type_name( ); + void on_publication_matched (DataWriter *dw, const PublicationMatchedStatus & status) { + Topic *topic = dw->get_topic( ); + const char *topic_name = topic->get_name( ); + const char *type_name = topic->get_type_name( ); printf("%s() topic: '%s' type: '%s' : matched readers %d (change = %d)\n", __FUNCTION__, topic_name, type_name, status.current_count, status.current_count_change); } - void on_offered_deadline_missed (DataWriter * dw, const OfferedDeadlineMissedStatus & status) { - Topic * topic = dw->get_topic( ); - const char * topic_name = topic->get_name( ); - const char * type_name = topic->get_type_name( ); + void on_offered_deadline_missed (DataWriter *dw, const OfferedDeadlineMissedStatus & status) { + Topic *topic = dw->get_topic( ); + const char *topic_name = topic->get_name( ); + const char *type_name = topic->get_type_name( ); printf("%s() topic: '%s' type: '%s' : (total = %d, change = %d)\n", __FUNCTION__, topic_name, type_name, status.total_count, status.total_count_change); } - void on_liveliness_lost (DataWriter * dw, const LivelinessLostStatus & status) { - Topic * topic = dw->get_topic( ); - const char * topic_name = topic->get_name( ); - const char * type_name = topic->get_type_name( ); + void on_liveliness_lost (DataWriter *dw, const LivelinessLostStatus & status) { + Topic *topic = dw->get_topic( ); + const char *topic_name = topic->get_name( ); + const char *type_name = topic->get_type_name( ); printf("%s() topic: '%s' type: '%s' : (total = %d, change = %d)\n", __FUNCTION__, topic_name, type_name, status.total_count, status.total_count_change); } - void on_requested_incompatible_qos ( DataReader * dr, const RequestedIncompatibleQosStatus & status ) { - TopicDescription * td = dr->get_topicdescription( ); - const char * topic_name = td->get_name( ); - const char * type_name = td->get_type_name( ); - const char * policy_name = NULL; + void on_requested_incompatible_qos (DataReader *dr, const RequestedIncompatibleQosStatus & status) { + TopicDescription *td = dr->get_topicdescription( ); + const char *topic_name = td->get_name( ); + const char *type_name = td->get_type_name( ); + const char *policy_name = NULL; policy_name = get_qos_policy_name(status.last_policy_id); printf("%s() topic: '%s' type: '%s' : %d (%s)\n", __FUNCTION__, topic_name, type_name, status.last_policy_id, policy_name); } - void on_subscription_matched (DataReader * dr, const SubscriptionMatchedStatus & status) { - TopicDescription * td = dr->get_topicdescription( ); - const char * topic_name = td->get_name( ); - const char * type_name = td->get_type_name( ); + void on_subscription_matched (DataReader *dr, const SubscriptionMatchedStatus & status) { + TopicDescription *td = dr->get_topicdescription( ); + const char *topic_name = td->get_name( ); + const char *type_name = td->get_type_name( ); printf("%s() topic: '%s' type: '%s' : matched writers %d (change = %d)\n", __FUNCTION__, topic_name, type_name, status.current_count, status.current_count_change); } - void on_requested_deadline_missed (DataReader * dr, const RequestedDeadlineMissedStatus & status) { - TopicDescription * td = dr->get_topicdescription( ); - const char * topic_name = td->get_name( ); - const char * type_name = td->get_type_name( ); + void on_requested_deadline_missed (DataReader *dr, const RequestedDeadlineMissedStatus & status) { + TopicDescription *td = dr->get_topicdescription( ); + const char *topic_name = td->get_name( ); + const char *type_name = td->get_type_name( ); printf("%s() topic: '%s' type: '%s' : (total = %d, change = %d)\n", __FUNCTION__, topic_name, type_name, status.total_count, status.total_count_change); } - void on_liveliness_changed (DataReader * dr, const LivelinessChangedStatus & status) { - TopicDescription * td = dr->get_topicdescription( ); - const char * topic_name = td->get_name( ); - const char * type_name = td->get_type_name( ); + void on_liveliness_changed (DataReader *dr, const LivelinessChangedStatus & status) { + TopicDescription *td = dr->get_topicdescription( ); + const char *topic_name = td->get_name( ); + const char *type_name = td->get_type_name( ); printf("%s() topic: '%s' type: '%s' : (alive = %d, not_alive = %d)\n", __FUNCTION__, topic_name, type_name, status.alive_count, status.not_alive_count); } @@ -361,15 +504,15 @@ class ShapeApplication { private: DPListener dp_listener; - DomainParticipantFactory * dpf; - DomainParticipant * dp; - Publisher * pub; - Subscriber * sub; - Topic * topic; - ShapeTypeDataReader * dr; - ShapeTypeDataWriter * dw; + DomainParticipantFactory *dpf; + DomainParticipant *dp; + Publisher *pub; + Subscriber *sub; + Topic *topic; + ShapeTypeDataReader *dr; + ShapeTypeDataWriter *dw; - char * color; + char *color; int xvel; int yvel; @@ -405,20 +548,20 @@ class ShapeApplication { #endif DomainParticipantFactory *dpf = OBTAIN_DOMAIN_PARTICIPANT_FACTORY; if (dpf == NULL) { - printf("failed to create participant factory (missing license?).\n"); + options->log_message("failed to create participant factory (missing license?).", Verbosity::ERROR); return false; } - + options->log_message("Participant Factory created", Verbosity::DEBUG); #ifdef CONFIGURE_PARTICIPANT_FACTORY CONFIGURE_PARTICIPANT_FACTORY #endif dp = dpf->create_participant( options->domain_id, PARTICIPANT_QOS_DEFAULT, &dp_listener, LISTENER_STATUS_MASK_ALL ); if (dp == NULL) { - printf("failed to create participant (missing license?).\n"); + options->log_message("failed to create participant (missing license?).", Verbosity::ERROR); return false; } - + options->log_message("Participant created", Verbosity::DEBUG); #ifndef REGISTER_TYPE #define REGISTER_TYPE ShapeTypeTypeSupport::register_type #endif @@ -427,7 +570,7 @@ class ShapeApplication { printf("Create topic: %s\n", options->topic_name ); topic = dp->create_topic( options->topic_name, "ShapeType", TOPIC_QOS_DEFAULT, NULL, 0); if (topic == NULL) { - printf("failed to create topic\n"); + options->log_message("failed to create topic", Verbosity::ERROR); return false; } @@ -440,10 +583,10 @@ class ShapeApplication { } //------------------------------------------------------------- - bool run() + bool run(ShapeOptions *options) { if ( pub != NULL ) { - return run_publisher(); + return run_publisher(options); } else if ( sub != NULL ) { return run_subscriber(); @@ -455,7 +598,7 @@ class ShapeApplication { //------------------------------------------------------------- bool init_publisher(ShapeOptions *options) { - + options->log_message("Initializing Publisher", Verbosity::DEBUG); PublisherQos pub_qos; DataWriterQos dw_qos; ShapeType shape; @@ -467,23 +610,44 @@ class ShapeApplication { pub = dp->create_publisher(pub_qos, NULL, 0); if (pub == NULL) { - printf("failed to create publisher"); + options->log_message("failed to create publisher", Verbosity::ERROR); return false; } - + options->log_message("Publisher created", Verbosity::DEBUG); + options->log_message("Data Writer QoS:", Verbosity::DEBUG); pub->get_default_datawriter_qos( dw_qos ); dw_qos.reliability.kind = options->reliability_kind; + options->log_message(" Reliability = " + std::to_string(dw_qos.reliability.kind), Verbosity::DEBUG); dw_qos.durability.kind = options->durability_kind; + options->log_message(" Durability = "+std::to_string(dw_qos.durability.kind), Verbosity::DEBUG); +#if defined(RTI_CONNEXT_DDS) + DataRepresentationIdSeq data_representation_seq; + data_representation_seq.ensure_length(1,1); + data_representation_seq[0] = options->data_representation; + dw_qos.representation.value = data_representation_seq; + +#elif defined(OPENDDS) + dw_qos.representation.value.length(1); + dw_qos.representation.value[0] = options->data_representation; +#endif + options->log_message(" Data_Representation = "+std::to_string(dw_qos.representation.value[0]), Verbosity::DEBUG); if ( options->ownership_strength != -1 ) { dw_qos.ownership.kind = EXCLUSIVE_OWNERSHIP_QOS; dw_qos.ownership_strength.value = options->ownership_strength; } + if ( options->ownership_strength == -1 ) { + dw_qos.ownership.kind = SHARED_OWNERSHIP_QOS; + } + options->log_message(" Ownership = "+std::to_string(dw_qos.ownership.kind), Verbosity::DEBUG); + options->log_message(" OwnershipStrength = "+std::to_string(dw_qos.ownership_strength.value), Verbosity::DEBUG); + if ( options->deadline_interval > 0 ) { dw_qos.deadline.period.sec = options->deadline_interval; dw_qos.deadline.period.nanosec = 0; } + options->log_message(" DeadlinePeriod = "+std::to_string(dw_qos.deadline.period.sec), Verbosity::DEBUG); // options->history_depth < 0 means leave default value if ( options->history_depth > 0 ) { @@ -493,12 +657,14 @@ class ShapeApplication { else if ( options->history_depth == 0 ) { dw_qos.history.kind = KEEP_ALL_HISTORY_QOS; } + options->log_message(" HistoryKind = "+std::to_string(dw_qos.history.kind), Verbosity::DEBUG); + options->log_message(" HistoryDepth = "+std::to_string(dw_qos.history.depth), Verbosity::DEBUG); printf("Create writer for topic: %s color: %s\n", options->topic_name, options->color ); dw = dynamic_cast(pub->create_datawriter( topic, dw_qos, NULL, 0)); if (dw == NULL) { - printf("failed to create datawriter"); + options->log_message("failed to create datawriter", Verbosity::ERROR); return false; } @@ -507,6 +673,12 @@ class ShapeApplication { yvel = options->yvel; da_width = options->da_width; da_height = options->da_height; + options->log_message("Data Writer created", Verbosity::DEBUG); + options->log_message("Color "+std::string(color), Verbosity::DEBUG); + options->log_message("xvel "+std::to_string(xvel), Verbosity::DEBUG); + options->log_message("yvel "+std::to_string(yvel), Verbosity::DEBUG); + options->log_message("da_width "+std::to_string(da_width), Verbosity::DEBUG); + options->log_message("da_height "+std::to_string(da_height), Verbosity::DEBUG); return true; } @@ -524,14 +696,28 @@ class ShapeApplication { sub = dp->create_subscriber( sub_qos, NULL, 0 ); if (sub == NULL) { - printf("failed to create subscriber"); + options->log_message("failed to create subscriber", Verbosity::ERROR); return false; } - + options->log_message("Subscriber created", Verbosity::DEBUG); + options->log_message("Data Reader QoS:", Verbosity::DEBUG); sub->get_default_datareader_qos( dr_qos ); dr_qos.reliability.kind = options->reliability_kind; + options->log_message(" Reliability = "+std::to_string(dr_qos.reliability.kind), Verbosity::DEBUG); dr_qos.durability.kind = options->durability_kind; + options->log_message(" Durability = "+std::to_string(dr_qos.durability.kind), Verbosity::DEBUG); +#if defined(RTI_CONNEXT_DDS) + DataRepresentationIdSeq data_representation_seq; + data_representation_seq.ensure_length(1,1); + data_representation_seq[0] = options->data_representation; + dr_qos.representation.value = data_representation_seq; + +#elif defined(OPENDDS) + dr_qos.representation.value.length(1); + dr_qos.representation.value[0] = options->data_representation; +#endif + options->log_message(" DataRepresentation = "+std::to_string(dr_qos.representation.value[0]), Verbosity::DEBUG); if ( options->ownership_strength != -1 ) { dr_qos.ownership.kind = EXCLUSIVE_OWNERSHIP_QOS; } @@ -540,11 +726,13 @@ class ShapeApplication { dr_qos.time_based_filter.minimum_separation.sec = options->timebasedfilter_interval; dr_qos.time_based_filter.minimum_separation.nanosec = 0; } + options->log_message(" Ownership = "+ std::to_string(dr_qos.ownership.kind), Verbosity::DEBUG); if ( options->deadline_interval > 0 ) { dr_qos.deadline.period.sec = options->deadline_interval; dr_qos.deadline.period.nanosec = 0; } + options->log_message(" DeadlinePeriod = "+ std::to_string(dr_qos.deadline.period.sec), Verbosity::DEBUG); // options->history_depth < 0 means leave default value if ( options->history_depth > 0 ) { @@ -554,23 +742,27 @@ class ShapeApplication { else if ( options->history_depth == 0 ) { dr_qos.history.kind = KEEP_ALL_HISTORY_QOS; } + options->log_message(" HistoryKind = "+ std::to_string(dr_qos.history.kind), Verbosity::DEBUG); + options->log_message(" HistoryDepth = "+ std::to_string(dr_qos.history.depth), Verbosity::DEBUG); if ( options->color != NULL ) { /* filter on specified color */ - ContentFilteredTopic * cft; + ContentFilteredTopic *cft; StringSeq cf_params; #if defined(RTI_CONNEXT_DDS) - char paramater[64]; - sprintf(paramater, "'%s'", options->color); - StringSeq_push(cf_params, paramater); + char parameter[64]; + sprintf(parameter, "'%s'", options->color); + StringSeq_push(cf_params, parameter); cft = dp->create_contentfilteredtopic( options->topic_name, topic, "color MATCH %0", cf_params ); + options->log_message(" ContentFilterTopic = color MATCH " + std::string(parameter), Verbosity::DEBUG); #elif defined(TWINOAKS_COREDX) || defined(OPENDDS) StringSeq_push(cf_params, options->color); cft = dp->create_contentfilteredtopic( options->topic_name, topic, "color = %0", cf_params ); + options->log_message(" ContentFilterTopic = color = " + std::string(options->color), Verbosity::DEBUG); #endif if (cft == NULL) { - printf("failed to create content filtered topic"); + options->log_message("failed to create content filtered topic", Verbosity::ERROR); return false; } @@ -582,11 +774,12 @@ class ShapeApplication { dr = dynamic_cast(sub->create_datareader( topic, dr_qos, NULL, 0)); } + if (dr == NULL) { - printf("failed to create datareader"); + options->log_message("failed to create datareader", Verbosity::ERROR); return false; } - + options->log_message("Data Reader created", Verbosity::DEBUG); return true; } @@ -626,7 +819,7 @@ class ShapeApplication { if (retval == RETCODE_OK) { int i; - for (i = samples.length()-1; i>=0; i--) { + for (i = 0; i < samples.length(); i++) { #if defined(RTI_CONNEXT_DDS) || defined(OPENDDS) ShapeType *sample = &samples[i]; @@ -642,7 +835,6 @@ class ShapeApplication { sample->x, sample->y, sample->shapesize ); - break; } } @@ -664,7 +856,7 @@ class ShapeApplication { //------------------------------------------------------------- void - moveShape( ShapeType * shape) + moveShape( ShapeType *shape) { int w2; @@ -690,7 +882,7 @@ class ShapeApplication { } //------------------------------------------------------------- - bool run_publisher() + bool run_publisher(ShapeOptions *options) { ShapeType shape; #if defined(RTI_CONNEXT_DDS) @@ -718,7 +910,12 @@ class ShapeApplication { #elif defined(TWINOAKS_COREDX) dw->write( &shape, HANDLE_NIL ); #endif - + if (options->print_writer_samples) + printf("%-10s %-10s %03d %03d [%d]\n", dw->get_topic()->get_name(), + shape.color STRING_IN, + shape.x, + shape.y, + shape.shapesize ); usleep(33000); } @@ -736,13 +933,11 @@ int main( int argc, char * argv[] ) if ( !parseResult ) { exit(1); } - ShapeApplication shapeApp; if ( !shapeApp.initialize(&options) ) { exit(2); } - - if ( !shapeApp.run() ) { + if ( !shapeApp.run(&options) ) { exit(2); } diff --git a/test_suite.py b/test_suite.py new file mode 100644 index 0000000..274ceb9 --- /dev/null +++ b/test_suite.py @@ -0,0 +1,126 @@ +from rtps_test_utilities import ReturnCode + +# rtps_test_suite_1 is a dictionary where we define the TestSuite +# (with its TestCases that we will test in interoperability_report.py). +# The dictionary has the following structure: +# 'name' : [parameters_publisher, parameters_subscriber, +# expected_return_code_publisher, expected_return_code_subscriber] +# where: +# * name: TestCase's name (defined by us) +# * parameters_publisher: parameters the shape_main application +# uses for the publisher process +# * parameters_subscriber: parameters the shape_main application +# uses for the subscriber process +# * expected_return_code_publisher: expected publisher ReturnCode +# for a succeed test execution. +# * expected_return_code_subscriber: expected subscriber ReturnCode +# for a succeed test execution. +# +# There are also two testCases that contains more parameters: +# Test_Ownership_3 and Test_Ownership_4. +# That is because these two cases run two publishers and one subscriber. +# These two cases are handled manually in the interoperability_report.py +# script. +# The parameters in this case are: +# * name: TestCase's name (defined by us) +# * parameters_publisher1: parameters the shape_main application +# uses for the publisher1 process +# * parameters_publisher2: parameters the shape_main application +# uses for the publisher2 process +# * parameters_subscriber: parameters the shape_main application +# uses for the subscriber process +# * expected_return_code_publisher1: expected publisher1 ReturnCode +# for a succeed test execution. +# * expected_return_code_publisher2: expected publisher2 ReturnCode +# for a succeed test execution. +# * expected_return_code_subscriber: expected subscriber ReturnCode +# for a succeed test execution. + +rtps_test_suite_1 = { + # DATA REPRESENTATION + 'Test_DataRepresentation_0' : ['-P -t Square -x 1', '-S -t Square -x 1', ReturnCode.OK, ReturnCode.OK], + 'Test_DataRepresentation_1' : ['-P -t Square -x 1', '-S -t Square -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + 'Test_DataRepresentation_2' : ['-P -t Square -x 2', '-S -t Square -x 1', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + 'Test_DataRepresentation_3' : ['-P -t Square -x 2', '-S -t Square -x 2', ReturnCode.OK, ReturnCode.OK], + + # DOMAIN + 'Test_Domain_0' : ['-P -t Square -x 2', '-S -t Square -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Domain_1' : ['-P -t Square -x 2', '-S -t Square -d 1 -x 2', ReturnCode.READER_NOT_MATCHED, ReturnCode.WRITER_NOT_MATCHED], + 'Test_Domain_2' : ['-P -t Square -d 1 -x 2', '-S -t Square -x 2', ReturnCode.READER_NOT_MATCHED, ReturnCode.WRITER_NOT_MATCHED], + 'Test_Domain_3' : ['-P -t Square -d 1 -x 2', '-S -t Square -d 1 -x 2', ReturnCode.OK, ReturnCode.OK], + + # RELIABILITY + 'Test_Reliability_0' : ['-P -t Square -b -x 2', '-S -t Square -b -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Reliability_1' : ['-P -t Square -b -x 2', '-S -t Square -r -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + 'Test_Reliability_2' : ['-P -t Square -r -x 2', '-S -t Square -b -x 2', ReturnCode.OK, ReturnCode.OK], + # This test only checks that data is received correctly + 'Test_Reliability_3' : ['-P -t Square -r -k 3 -x 2', '-S -t Square -r -x 2', ReturnCode.OK, ReturnCode.OK], + # This test checks that data is received in the right order + 'Test_Reliability_4' : ['-P -t Square -r -k 0 -w -x 2', '-S -t Square -r -k 0 -x 2', ReturnCode.OK, ReturnCode.OK], + + # DEADLINE + 'Test_Deadline_0' : ['-P -t Square -f 3 -x 2', '-S -t Square -f 5 -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Deadline_1' : ['-P -t Square -f 5 -x 2', '-S -t Square -f 5 -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Deadline_2' : ['-P -t Square -f 7 -x 2', '-S -t Square -f 5 -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + + # OWNERSHIP + 'Test_Ownership_0': ['-P -t Square -s -1 -x 2', '-S -t Square -s -1 -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Ownership_1': ['-P -t Square -s -1 -x 2', '-S -t Square -s 3 -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + 'Test_Ownership_2': ['-P -t Square -s 3 -x 2', '-S -t Square -s -1 -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + # Two Publishers and One Subscriber to test that if each one has a different color, the ownership strength does not matter + 'Test_Ownership_3': ['-P -t Square -s 3 -c BLUE -w -x 2', '-P -t Square -s 4 -c RED -w -x 2', '-S -t Square -s 2 -r -k 0 -x 2', + ReturnCode.OK, ReturnCode.OK, ReturnCode.RECEIVING_FROM_BOTH], + # Two Publishers and One Subscriber to test that the Subscriber only receives samples from the Publisher with the greatest ownership + 'Test_Ownership_4': ['-P -t Square -s 5 -r -k 0 -w -x 2', '-P -t Square -s 4 -r -k 0 -w -x 2', '-S -t Square -s 2 -r -k 0 -x 2', + ReturnCode.OK, ReturnCode.OK, ReturnCode.RECEIVING_FROM_ONE], + + # TOPIC + 'Test_Topic_0' : ['-P -t Square -x 2', '-S -t Square -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Topic_1' : ['-P -t Square -x 2', '-S -t Circle -x 2', ReturnCode.READER_NOT_MATCHED, ReturnCode.WRITER_NOT_MATCHED], + 'Test_Topic_2' : ['-P -t Circle -x 2', '-S -t Square -x 2', ReturnCode.READER_NOT_MATCHED, ReturnCode.WRITER_NOT_MATCHED], + 'Test_Topic_3' : ['-P -t Circle -x 2', '-S -t Circle -x 2', ReturnCode.OK, ReturnCode.OK], + + # COLOR + 'Test_Color_0' : ['-P -t Square -c BLUE -x 2', '-S -t Square -c BLUE -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Color_1' : ['-P -t Square -c BLUE -x 2', '-S -t Square -c RED -x 2', ReturnCode.OK, ReturnCode.DATA_NOT_RECEIVED], + 'Test_Color_2' : ['-P -t Square -c BLUE -x 2', '-S -t Square -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Color_3' : ['-P -t Square -c RED -x 2', '-S -t Square -c BLUE -x 2', ReturnCode.OK, ReturnCode.DATA_NOT_RECEIVED], + 'Test_Color_4' : ['-P -t Square -c RED -x 2', '-S -t Square -c RED -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Color_5' : ['-P -t Square -c RED -x 2', '-S -t Square -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Color_6' : ['-P -t Square -x 2', '-S -t Square -c BLUE -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Color_7' : ['-P -t Square -x 2', '-S -t Square -c RED -x 2', ReturnCode.OK, ReturnCode.DATA_NOT_RECEIVED], + 'Test_Color_8' : ['-P -t Square -x 2', '-S -t Square -x 2', ReturnCode.OK, ReturnCode.OK], + + # PARTITION + 'Test_Partition_0' : ['-P -t Square -p "p1" -x 2', '-S -t Square -p "p1" -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Partition_1' : ['-P -t Square -p "p1" -x 2', '-S -t Square -p "p2" -x 2', ReturnCode.READER_NOT_MATCHED, ReturnCode.WRITER_NOT_MATCHED], + 'Test_Partition_2' : ['-P -t Square -p "p2" -x 2', '-S -t Square -p "p1" -x 2', ReturnCode.READER_NOT_MATCHED, ReturnCode.WRITER_NOT_MATCHED], + 'Test_Partition_3' : ['-P -t Square -p "p2" -x 2', '-S -t Square -p "p2" -x 2', ReturnCode.OK, ReturnCode.OK], + + # DURABILITY + 'Test_Durability_0' : ['-P -t Square -D v -x 2', '-S -t Square -D v -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_1' : ['-P -t Square -D v -x 2', '-S -t Square -D l -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + 'Test_Durability_2' : ['-P -t Square -D v -x 2', '-S -t Square -D t -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + 'Test_Durability_3' : ['-P -t Square -D v -x 2', '-S -t Square -D p -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + + 'Test_Durability_4' : ['-P -t Square -D l -x 2', '-S -t Square -D v -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_5' : ['-P -t Square -D l -x 2', '-S -t Square -D l -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_6' : ['-P -t Square -D l -x 2', '-S -t Square -D t -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + 'Test_Durability_7' : ['-P -t Square -D l -x 2', '-S -t Square -D p -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + + 'Test_Durability_8' : ['-P -t Square -D t -x 2', '-S -t Square -D v -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_9' : ['-P -t Square -D t -x 2', '-S -t Square -D l -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_10': ['-P -t Square -D t -x 2', '-S -t Square -D t -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_11': ['-P -t Square -D t -x 2', '-S -t Square -D p -x 2', ReturnCode.INCOMPATIBLE_QOS, ReturnCode.INCOMPATIBLE_QOS], + + 'Test_Durability_12' : ['-P -t Square -D p -x 2', '-S -t Square -D v -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_13' : ['-P -t Square -D p -x 2', '-S -t Square -D l -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_14' : ['-P -t Square -D p -x 2', '-S -t Square -D t -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_Durability_15' : ['-P -t Square -D p -x 2', '-S -t Square -D p -x 2', ReturnCode.OK, ReturnCode.OK], + + # HISTORY + 'Test_History_0' : ['-P -t Square -k 3 -x 2', '-S -t Square -k 3 -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_History_1' : ['-P -t Square -k 3 -x 2', '-S -t Square -k 0 -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_History_2' : ['-P -t Square -k 0 -x 2', '-S -t Square -k 3 -x 2', ReturnCode.OK, ReturnCode.OK], + 'Test_History_3' : ['-P -t Square -k 0 -x 2', '-S -t Square -k 0 -x 2', ReturnCode.OK, ReturnCode.OK] +}