{ "info": { "author": "Kenn Takara", "author_email": "kenn.takara@outlook.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Intended Audience :: Financial and Insurance Industry", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Topic :: Software Development :: Testing", "Topic :: System :: Networking" ], "description": "==================================\nFIXTest - FIX Protocol Test tool\n==================================\n\nThe purpose of this tool is to provide a way to test networking components\nusing the FIX level at the system level, not unit test. Initially, I\nwanted a way to reproduce and document specific test cases so that I\ncould perform regression tests at a later date.\n\nThis tool provides a way of creating test cases that can act as FIX clients\nor as FIX servers. But this is not a simulator, the test case author is\nresponsible for generating the actual messages and checking their correctness.\n\nWhat this is not\n------------------\n\n* This is not a simulator. This tool was made to help document specific test cases (thus ensuring that I could repro and verify a test case).\n* This is not meant for unit testing, but component level testing.\n* This currently only supports FIX-based protocols. In theory this could support other protocols, but I haven't tried to make it more protocol agnostic.\n\n\nWhat is supported\n------------------\n\n* Groups\n* Binary fields\n* TestRequest/Heartbeat processing\n\n\nHow to use\n-----------\n\n1. Write a configuration file\n2. Write a testcase\n3. Run the testcase\n\n\nConfiguration\n---------------\nThe configuration is gathered from a config file. The default name for the\nfile is my_config.py. A full description of the contents of the file may be\nfound in sample_config.py\n\nHere is a sample configuration file.\n\n.. code:: python\n \n ROLES = {\n 'client': {},\n 'test-server': {},\n }\n \n FIX_4_2 = {\n # The values here are examples only. They should be customized\n # for your particular needs/implementation.\n 'protocol_version': 'FIX.4.2',\n 'header_fields': [8, 9],\n 'binary_fields': [],\n 'required_fields': [8, 9, 10, 35],\n 'group_fields': {},\n 'max_length': 2048,\n }\n \n CONNECTIONS = [\n {\n # connection information\n 'name': 'client-FIX-test-server',\n 'protocol': 'FIX',\n 'host': 'localhost',\n 'port': 9000,\n \n 'client': 'FixClient',\n 'test-server': 'FixServer',\n 'acts-as-server': 'test-server',\n \n # protocol information\n 'protocol_version': FIX_4_2['protocol_version'],\n 'binary_fields': FIX_4_2['binary_fields'],\n 'header_fields': FIX_4_2['header_fields'],\n 'required_fields': FIX_4_2['required_fields'],\n 'group_fields': FIX_4_2['group_fields'],\n },\n ]\n\n\nSample test code\n------------------\n\nHere is an example of a test. This test sends a logon message and\nthen a logout message. In this case, the test tool is running as a\nserver and a client (thus all messages are logged twice, once on the\nsending side and once on the receiving side).\n\nIn a more typical test case, this code would be hidden inside of a base\nclass. Logon/logout are usually performed within the setup/teardown rather\nthan part of the test proper.\n\n.. code:: python\n \n import logging\n import time\n \n from fixtest.base.asserts import *\n from fixtest.base.controller import TestCaseController\n from fixtest.base.queue import TestInterruptedError\n from fixtest.base.utils import log_text\n from fixtest.fix.constants import FIX\n from fixtest.fix.messages import logon_message, logout_message\n from fixtest.fix.transport import FIXTransportFactory\n \n \n class LogonController(TestCaseController):\n \"\"\" The base class for FIX-based TestCaseControllers.\n \n This creates a client and a server that will \n communicate with each other. So they will use\n the same link config.\n \"\"\"\n def __init__(self, **kwargs):\n super(LogonController, self).__init__(**kwargs)\n \n self.testcase_id = 'Simple-1'\n self.description = 'Test of the command-line tool'\n \n config = kwargs['config']\n \n self.server_config = config.get_role('test-server')\n self.server_config.update({'name': 'server-9000'})\n \n self.server_link_config = config.get_link('client', 'test-server')\n self.server_link_config.update({\n 'sender_compid': self.server_link_config['test-server'],\n 'target_compid': self.server_link_config['client'],\n })\n \n self.client_config = config.get_role('client')\n self.client_config.update({'name': 'client-9000'})\n \n self.client_link_config = config.get_link('client', 'test-server')\n self.client_link_config.update({\n 'sender_compid': self.client_link_config['client'],\n 'target_compid': self.client_link_config['test-server'],\n })\n \n self._servers = dict()\n self._clients = dict()\n \n factory = FIXTransportFactory('server-9000',\n self.server_config,\n self.server_link_config)\n factory.filter_heartbeat = False\n \n server = {\n 'name': 'server-9000',\n 'port': self.server_link_config['port'],\n 'factory': factory,\n }\n self._servers[server['name']] = server\n \n # In the client case we do not need to provide a\n # factory, Just need a transport.\n client = {\n 'name': 'client-9000',\n 'host': self.client_link_config['host'],\n 'port': self.client_link_config['port'],\n 'node': factory.create_transport('client-9000',\n self.client_config,\n self.client_link_config),\n }\n self._clients[client['name']] = client\n \n self._logger = logging.getLogger(__name__)\n \n \n def clients(self):\n \"\"\" The clients that need to be started \"\"\"\n return self._clients\n \n def servers(self):\n \"\"\" The servers that need to be started \"\"\"\n return self._servers\n \n def setup(self):\n \"\"\" For this case, wait until our servers are all\n connected before continuing with the test.\n \"\"\"\n # at this point the servers should be waiting\n # so startup the clients\n self.wait_for_client_connections(10)\n self.wait_for_server_connections(10)\n \n def teardown(self):\n pass\n \n def run(self):\n \"\"\" This test is a demonstration of logon and\n heartbeat/TestRequest processing. Usually\n the logon process should be done from setup().\n \"\"\"\n client = self._clients['client-9000']['node']\n client.protocol.heartbeat = 5\n # We only have a single server connection\n server = self._servers['server-9000']['factory'].servers[0]\n server.protocol.heartbeat = 5\n \n # client -> server\n client.send_message(logon_message(client))\n \n # server <- client\n message = server.wait_for_message(title='waiting for logon')\n assert_is_not_none(message)\n assert_tag(message, [(35, FIX.LOGON)])\n \n # server -> client\n server.send_message(logon_message(server))\n server.start_heartbeat(True)\n \n # client <- server\n message = client.wait_for_message(title='waiting for logon ack')\n client.start_heartbeat(True)\n assert_is_not_none(message)\n assert_tag(message, [(35, FIX.LOGON)])\n \n # Logout\n client.send_message(logout_message(client))\n message = server.wait_for_message(title='waiting for logout')\n assert_is_not_none(message)\n assert_tag(message, [(35, FIX.LOGOUT)])\n \n server.send_message(logout_message(server))\n server.start_heartbeat(False)\n \n message = client.wait_for_message('waiting for logout ack')\n client.start_heartbeat(False)\n assert_is_not_none(message)\n assert_tag(message, [(35, FIX.LOGOUT)])\n\n\nRunning the test\n-----------------\nTo run this, use the command line\n\n\tfixtest -c simple_config.py testcases/logon_test.py\n\n\nSample output\n--------------\n\n.. code:: python\n\n (fixtest)~/dev/src/fixtest > fixtest -c simple/simple_config.py simple/logon_controller.py \n 12:52:10.468172: ================\n 12:52:10.468496: Starting test: 2014-08-06\n 12:52:10.468643: Module: simple/logon_controller.py\n 12:52:10.468778: Controller: LogonController\n 12:52:10.468908: Config: simple/simple_config.py\n 12:52:10.470420: \n 12:52:10.470547: Test case: Simple-1\n 12:52:10.470657: Description: Test of the command-line tool\n 12:52:10.470761: ================\n 12:52:10.470868: server:server-9000 starting on port 9000\n 12:52:10.472101: fixtest.fix.transport: server:server-9000 listening on port 9000\n 12:52:10.472997: client:client-9000 attempting localhost:9000\n 12:52:12.810111: client-9000: Connection made\n 12:52:12.810329: fixtest.fix.transport: client:client-9000 connected to localhost:9000\n 12:52:12.810626: Connected: fixtest.fix.transport.FIXTransportFactory : server-9000\n 12:52:12.811074: server-9000: Connection made\n 12:52:13.010270: client-9000: message sent\n Logon : 8=FIX.4.2, 9=68, 35=A, 49=FixClient, 56=FixServer, 98=0, 108=5, 34=1, 52=20140806-12:52:13, 10=045\n \n 12:52:13.012275: server-9000: message received\n Logon : 8=FIX.4.2, 9=68, 35=A, 49=FixClient, 56=FixServer, 98=0, 108=5, 34=1, 52=20140806-12:52:13, 10=045\n \n 12:52:13.015563: server-9000: message sent\n Logon : 8=FIX.4.2, 9=68, 35=A, 49=FixServer, 56=FixClient, 98=0, 108=5, 34=1, 52=20140806-12:52:13, 10=045\n \n 12:52:13.016854: client-9000: message received\n Logon : 8=FIX.4.2, 9=68, 35=A, 49=FixServer, 56=FixClient, 98=0, 108=5, 34=1, 52=20140806-12:52:13, 10=045\n \n 12:52:13.017925: client-9000: message sent\n Logout : 8=FIX.4.2, 9=57, 35=5, 49=FixClient, 56=FixServer, 34=2, 52=20140806-12:52:13, 10=053\n \n 12:52:13.019156: server-9000: message received\n Logout : 8=FIX.4.2, 9=57, 35=5, 49=FixClient, 56=FixServer, 34=2, 52=20140806-12:52:13, 10=053\n \n 12:52:13.020144: server-9000: message sent\n Logout : 8=FIX.4.2, 9=57, 35=5, 49=FixServer, 56=FixClient, 34=2, 52=20140806-12:52:13, 10=053\n \n 12:52:13.021321: client-9000: message received\n Logout : 8=FIX.4.2, 9=57, 35=5, 49=FixServer, 56=FixClient, 34=2, 52=20140806-12:52:13, 10=053\n \n 12:52:13.022400: server-9000: Connection lost\n 12:52:13.022687: client-9000: Connection lost\n 12:52:13.023373: ================\n 12:52:13.023508: Test status: ok\n\n\nMore sample code\n------------------\n\nThis is a sample of what the code would like if the logon/logout\ncode were removed and placed in the base class setup/teardown functions.\n\nThus leaving run() to perform the real test work.\n\n.. code:: python\n \n import logging\n \n from fixtest.base.asserts import *\n from fixtest.fix.constants import FIX\n from fixtest.fix.messages import new_order_message, execution_report\n \n from simple_base import BaseClientServerController\n \n \n class SimpleClientServerController(BaseClientServerController):\n \"\"\" The base class for FIX-based TestCaseControllers.\n \"\"\"\n def __init__(self, **kwargs):\n super(SimpleClientServerController, self).__init__(**kwargs)\n \n self.testcase_id = 'Simple NewOrder test'\n self.description = 'Test of the command-line tool'\n \n self._logger = logging.getLogger(__name__)\n \n def run(self):\n \"\"\" Run the test. Here we send a new_order and\n then a modify.\n \"\"\"\n # client -> server\n self.client.send_message(new_order_message(self.client,\n symbol='abc',\n side='0',\n order_type='1',\n extra_tags=[(38, 100), # orderQty\n (44, 10), # price\n ]))\n \n # server <- client\n message = self.server.wait_for_message('waiting for new order')\n assert_is_not_none(message)\n \n # server -> client\n self.server.send_message(execution_report(self.server,\n message,\n exec_trans_type='0',\n exec_type='0',\n ord_status='0',\n symbol='abc',\n side='0',\n leaves_qty='100',\n cum_qty='0',\n avg_px='0'))\n \n # client <- server\n message = self.client.wait_for_message('waiting for new order ack')\n assert_is_not_none(message)```\n\n\nHere is the resulting output:\n\n.. code:: python\n\n 17:48:15.066436: ================\n 17:48:15.066607: Starting test: 2014-08-07\n 17:48:15.066684: Module: simple/simple_test.py\n 17:48:15.066757: Controller: SimpleClientServerController\n 17:48:15.066827: Config: simple/simple_config.py\n 17:48:15.067619: \n 17:48:15.067700: Test case: Simple NewOrder test\n 17:48:15.067772: Description: Test of the command-line tool\n 17:48:15.067841: ================\n 17:48:15.067912: server:server-9940 starting on port 9940\n 17:48:15.068883: fixtest.fix.transport: server:server-9940 listening on port 9940\n 17:48:15.069471: client:client-9940 attempting localhost:9940\n 17:48:15.112361: Connected: fixtest.fix.transport.FIXTransportFactory : server-9940\n 17:48:15.112912: server-9940: Connection made\n 17:48:15.113281: client-9940: Connection made\n 17:48:15.113377: fixtest.fix.transport: client:client-9940 connected to localhost:9940\n 17:48:15.270715: client-9940: message sent\n Logon : 8=FIX.4.2, 9=68, 35=A, 49=FixClient, 56=FixServer, 98=0, 108=5, 34=1, 52=20140807-17:48:15, 10=058\n \n 17:48:15.271588: server-9940: message received\n Logon : 8=FIX.4.2, 9=68, 35=A, 49=FixClient, 56=FixServer, 98=0, 108=5, 34=1, 52=20140807-17:48:15, 10=058\n \n 17:48:15.272481: server-9940: message sent\n Logon : 8=FIX.4.2, 9=68, 35=A, 49=FixServer, 56=FixClient, 98=0, 108=5, 34=1, 52=20140807-17:48:15, 10=058\n \n 17:48:15.273204: client-9940: message received\n Logon : 8=FIX.4.2, 9=68, 35=A, 49=FixServer, 56=FixClient, 98=0, 108=5, 34=1, 52=20140807-17:48:15, 10=058\n \n 17:48:15.274292: client-9940: message sent\n NewOrderSingle : 8=FIX.4.2, 9=139, 35=D, 49=FixClient, 56=FixServer, 11=client-9940/20140807/1, 21=1, 55=abc, 54=0, 60=20140807-17:48:15, 40=1, 38=100, 44=10, 34=2, 52=20140807-17:48:15, 10=105\n \n 17:48:15.275188: server-9940: message received\n NewOrderSingle : 8=FIX.4.2, 9=139, 35=D, 49=FixClient, 56=FixServer, 11=client-9940/20140807/1, 21=1, 55=abc, 54=0, 60=20140807-17:48:15, 40=1, 38=100, 44=10, 34=2, 52=20140807-17:48:15, 10=105\n \n 17:48:15.276382: server-9940: message sent\n ExecutionReport : (New) : 8=FIX.4.2, 9=224, 35=8, 49=FixServer, 56=FixClient, 11=client-9940/20140807/1, 21=1, 55=abc, 54=0, 60=20140807-17:48:15, 40=1, 38=100, 44=10, 34=2, 52=20140807-17:48:15, 37=server-9940/20140807/2, 17=server- 9940/20140807/1, 20=0, 150=0, 39=0, 151=100, 14=0, 6=0, 10=176\n \n 17:48:15.277720: client-9940: message received\n ExecutionReport : (New) : 8=FIX.4.2, 9=224, 35=8, 49=FixServer, 56=FixClient, 11=client-9940/20140807/1, 21=1, 55=abc, 54=0, 60=20140807-17:48:15, 40=1, 38=100, 44=10, 34=2, 52=20140807-17:48:15, 37=server-9940/20140807/2, 17=server- 9940/20140807/1, 20=0, 150=0, 39=0, 151=100, 14=0, 6=0, 10=176\n \n 17:48:15.280502: client-9940: message sent\n Logout : 8=FIX.4.2, 9=57, 35=5, 49=FixClient, 56=FixServer, 34=3, 52=20140807-17:48:15, 10=067\n \n 17:48:15.281089: server-9940: message received\n Logout : 8=FIX.4.2, 9=57, 35=5, 49=FixClient, 56=FixServer, 34=3, 52=20140807-17:48:15, 10=067\n \n 17:48:15.282183: server-9940: message sent\n Logout : 8=FIX.4.2, 9=57, 35=5, 49=FixServer, 56=FixClient, 34=3, 52=20140807-17:48:15, 10=067\n \n 17:48:15.282773: client-9940: message received\n Logout : 8=FIX.4.2, 9=57, 35=5, 49=FixServer, 56=FixClient, 34=3, 52=20140807-17:48:15, 10=067\n \n 17:48:15.284066: server-9940: Connection lost\n 17:48:15.284246: client-9940: Connection lost\n 17:48:15.284561: ================\n 17:48:15.284648: Test status: ok\n\n\nChangelog\n---------\n\n0.1.1\n=====\nFixed Issue #1, tests only run out of the simple directory. They can\nnow be run from any directory.\n\n0.1.3\n=====\nSome additional features by Jon Evans.\nAdded a list of tag/values that are added to all messages.\nHeartbeats are now sent on at heartbeat/2", "description_content_type": null, "docs_url": null, "download_url": null, "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/kennt/fixtest", "keywords": null, "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "fixtest", "package_url": "https://pypi.org/project/fixtest/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/fixtest/", "project_urls": { "Homepage": "https://github.com/kennt/fixtest" }, "release_url": "https://pypi.org/project/fixtest/0.1.3/", "requires_dist": null, "requires_python": null, "summary": "Tool for testing FIX (Financial Information eXchange protocol) services", "version": "0.1.3" }, "last_serial": 1973462, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "04cae30e4eea5defb869dd6a2cd8fce8", "sha256": "64055109442fc99b665c65e648c09e615473d0bb22e46303d9dcaae163838bb4" }, "downloads": -1, "filename": "fixtest-0.1.0.tar.gz", "has_sig": false, "md5_digest": "04cae30e4eea5defb869dd6a2cd8fce8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 33639, "upload_time": "2014-09-27T09:15:56", "url": "https://files.pythonhosted.org/packages/c5/5a/d1294c3952a3b776278b8bc93f94fb441efee5a91b97d911e442aa4d6e8a/fixtest-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "e5cc40fc4b3c8a2bfc015104448a0e67", "sha256": "4ecee45b364185b89f61877b1ad95f5128f55fe071cd1d3e3391736dc9ccbb39" }, "downloads": -1, "filename": "fixtest-0.1.1.tar.gz", "has_sig": false, "md5_digest": "e5cc40fc4b3c8a2bfc015104448a0e67", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 37465, "upload_time": "2015-05-12T12:33:28", "url": "https://files.pythonhosted.org/packages/7e/e6/63056e623f774faab04b7764c5f94e81b3c3b8695cc8f1d89d7843be1a5a/fixtest-0.1.1.tar.gz" } ], "0.1.2": [], "0.1.3": [ { "comment_text": "", "digests": { "md5": "cbaecfb4f50489e491b825ae2ee10396", "sha256": "9072fc777e0e3e5e902ca86bc731ba759f1198f559906700dc30dc7478e99503" }, "downloads": -1, "filename": "fixtest-0.1.3.tar.gz", "has_sig": false, "md5_digest": "cbaecfb4f50489e491b825ae2ee10396", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 37750, "upload_time": "2016-02-24T07:32:11", "url": "https://files.pythonhosted.org/packages/67/a0/10015beb08f473e0086bec280e9c02d4e6edfa83b2de781dca074f2f8ff9/fixtest-0.1.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "cbaecfb4f50489e491b825ae2ee10396", "sha256": "9072fc777e0e3e5e902ca86bc731ba759f1198f559906700dc30dc7478e99503" }, "downloads": -1, "filename": "fixtest-0.1.3.tar.gz", "has_sig": false, "md5_digest": "cbaecfb4f50489e491b825ae2ee10396", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 37750, "upload_time": "2016-02-24T07:32:11", "url": "https://files.pythonhosted.org/packages/67/a0/10015beb08f473e0086bec280e9c02d4e6edfa83b2de781dca074f2f8ff9/fixtest-0.1.3.tar.gz" } ] }