From af32c11add9c24bb04f040b7c138e580fc7cbfbb Mon Sep 17 00:00:00 2001 From: Wilfred Tyler Gee Date: Tue, 2 Jan 2018 11:19:01 +1100 Subject: [PATCH 1/3] Mock TheSkyX * Use [Mocket](https://github.com/mindflayer/python-mocket) to create a fake socket connection. * A real connection is used once with `--with-hardware theskyx` and the responses are recorded into a json file. Subsequent tests then use the json file to mock the responses * Improve errors for TheSkyX --- pocs/hardware.py | 2 +- pocs/tests/data/theskyx.json | 18 ++++++++ pocs/tests/test_theskyx_utils.py | 72 ++++++++++++++++++++++++++++++++ pocs/utils/error.py | 16 +++++++ pocs/utils/theskyx.py | 42 ++++++++++++------- requirements.txt | 3 +- 6 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 pocs/tests/data/theskyx.json create mode 100644 pocs/tests/test_theskyx_utils.py diff --git a/pocs/hardware.py b/pocs/hardware.py index 940aea78e..60737f636 100644 --- a/pocs/hardware.py +++ b/pocs/hardware.py @@ -1,6 +1,6 @@ """Information about hardware supported by Panoptes.""" -ALL_NAMES = sorted(['camera', 'dome', 'mount', 'night', 'weather']) +ALL_NAMES = sorted(['camera', 'dome', 'mount', 'night', 'weather', 'theskyx']) def get_all_names(all_names=ALL_NAMES, without=list()): diff --git a/pocs/tests/data/theskyx.json b/pocs/tests/data/theskyx.json new file mode 100644 index 000000000..91b861d58 --- /dev/null +++ b/pocs/tests/data/theskyx.json @@ -0,0 +1,18 @@ +{ + "localhost": { + "3040": { + "5dd3bdd5e788cb8b3880563a7633fc96": { + "request": "/* Java Script */", + "response": "75 6E 64 65 66 69 6E 65 64 7C 4E 6F 20 65 72 72 6F 72 2E 20 45 72 72 6F 72 20 3D 20 30 2E" + }, + "95c72a49c488d59f60c022fcfecf4382": { + "request": "FOOBAR", + "response": "7C 55 6E 6B 6E 6F 77 6E 20 63 6F 6D 6D 61 6E 64 2E 20 45 72 72 6F 72 20 3D 20 33 30 33 2E" + }, + "a6ceb538a4029b586782d371aac2b947": { + "request": "\n/* Java Script */\nvar Out;\nOut=Application.version\n", + "response": "31 30 2E 35 2E 30 7C 4E 6F 20 65 72 72 6F 72 2E 20 45 72 72 6F 72 20 3D 20 30 2E" + } + } + } +} \ No newline at end of file diff --git a/pocs/tests/test_theskyx_utils.py b/pocs/tests/test_theskyx_utils.py new file mode 100644 index 000000000..b54dba961 --- /dev/null +++ b/pocs/tests/test_theskyx_utils.py @@ -0,0 +1,72 @@ +import os +import pytest + +from mocket import Mocket + +from pocs.utils import error +from pocs.utils.theskyx import TheSkyX + + +@pytest.fixture(scope="function") +def skyx(request): + """Create TheSkyX class but don't connect.t + + If running with a real connection TheSkyX then the Mokcet will + be disabled here. + """ + + # Use `--with-hardware thesky` on cli to run without mock + Mocket.enable('theskyx', '{}/pocs/tests/data'.format(os.getenv('POCS'))) + if 'theskyx' in pytest.config.getoption('--with-hardware'): + Mocket.disable() + + theskyx = TheSkyX(connect=False) + + yield theskyx + + +def test_default_connect(): + """Test connection to TheSkyX + + If not running with a real connection then use Mocket + """ + # Use `--with-hardware thesky` on cli to run without mock + if 'theskyx' not in pytest.config.getoption('--with-hardware'): + Mocket.enable('theskyx', '{}/pocs/tests/data'.format(os.getenv('POCS'))) + + skyx = TheSkyX() + assert skyx.is_connected is True + + +def test_no_connect_write(skyx): + with pytest.raises(error.BadConnection): + skyx.write('/* Java Script */') + + +def test_no_connect_read(skyx): + with pytest.raises(error.BadConnection): + skyx.read() + + +def test_write_bad_key(skyx): + skyx.connect() + skyx.write('FOOBAR') + with pytest.raises(error.TheSkyXKeyError): + skyx.read() + + +def test_write_no_command(skyx): + skyx.connect() + skyx.write('/* Java Script */') + assert skyx.read() == 'undefined' + + +def test_get_build(skyx): + js = ''' +/* Java Script */ +var Out; +Out=Application.version +''' + skyx.connect() + skyx.write(js) + assert skyx.read().startswith('10.5') diff --git a/pocs/utils/error.py b/pocs/utils/error.py index 7ba5f293c..6bafb74f2 100644 --- a/pocs/utils/error.py +++ b/pocs/utils/error.py @@ -72,6 +72,12 @@ class InvalidMountCommand(PanError): pass +class BadConnection(PanError): + + """ PanError raised when a connection is bad """ + pass + + class BadSerialConnection(PanError): """ PanError raised when serial command is bad """ @@ -101,3 +107,13 @@ class SolveError(NotFound): """ Camera cannot be imported """ pass + + +class TheSkyXError(PanError): + """ Errors from TheSkyX """ + pass + + +class TheSkyXKeyError(TheSkyXError): + """ Errors from TheSkyX because bad key passed """ + pass diff --git a/pocs/utils/theskyx.py b/pocs/utils/theskyx.py index aab027a8c..bdb03c3d6 100644 --- a/pocs/utils/theskyx.py +++ b/pocs/utils/theskyx.py @@ -1,6 +1,7 @@ import socket from pocs import PanBase +from pocs.utils import error class TheSkyX(PanBase): @@ -41,22 +42,31 @@ def connect(self): self.logger.info('Connected to TheSkyX via {}:{}'.format(self._host, self._port)) def write(self, value): - assert isinstance(value, str) - self.socket.sendall(value.encode()) + try: + assert isinstance(value, str) + self.socket.sendall(value.encode()) + except AttributeError: + raise error.BadConnection("Not connected to TheSkyX") def read(self, timeout=5): - self.socket.settimeout(timeout) - response = None - try: - response = self.socket.recv(4096).decode() - if '|' in response: - response, err = response.split('|') - if err is not None and 'No error' not in err: - self.logger.warning("Mount error: {}".format(err)) - elif err is None: - self.logger.warning("Error status not returned") - except socket.timeout: - pass - - return response + self.socket.settimeout(timeout) + response = None + + try: + response = self.socket.recv(4096).decode() + if '|' in response: + response, err = response.split('|') + if err is not None and 'No error' not in err: + if 'Error = 303' in err: + raise error.TheSkyXKeyError("Invalid TheSkyX key") + + raise error.TheSkyXError(err) + elif err is None: + raise error.TheSkyXError("Error status not returned") + except socket.timeout: + pass + + return response + except AttributeError: + raise error.BadConnection("Not connected to TheSkyX") diff --git a/requirements.txt b/requirements.txt index 45f918ed1..d2f473551 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,4 +19,5 @@ codecov ffmpy google-cloud-storage dateparser -coveralls \ No newline at end of file +coveralls +mocket \ No newline at end of file From 398db51c7c32569d1b9c092f005be3491a399d2f Mon Sep 17 00:00:00 2001 From: Wilfred Tyler Gee Date: Tue, 2 Jan 2018 13:37:20 +1100 Subject: [PATCH 2/3] More testing Couldn't get timeout to work with Mocket although does work with actual copy of TheSkyX. Removed timeout test but left exception class --- pocs/tests/data/theskyx.json | 4 ++++ pocs/tests/test_theskyx_utils.py | 10 ++++++++++ pocs/utils/error.py | 5 +++++ pocs/utils/theskyx.py | 15 ++++++++++++--- 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/pocs/tests/data/theskyx.json b/pocs/tests/data/theskyx.json index 91b861d58..6bbd1b38c 100644 --- a/pocs/tests/data/theskyx.json +++ b/pocs/tests/data/theskyx.json @@ -1,6 +1,10 @@ { "localhost": { "3040": { + "1912c6e0354588e8b2a221638f490c5f": { + "request": "\n/* Java Script */\nsky6RASCOMTele.FindHome()\n", + "response": "54 79 70 65 45 72 72 6F 72 3A 20 54 68 65 20 6F 70 65 72 61 74 69 6F 6E 20 66 61 69 6C 65 64 20 62 65 63 61 75 73 65 20 74 68 65 72 65 20 69 73 20 6E 6F 20 63 6F 6E 6E 65 63 74 69 6F 6E 20 74 6F 20 74 68 65 20 64 65 76 69 63 65 2E 20 45 72 72 6F 72 20 3D 20 32 30 30 2E 7C 4E 6F 20 65 72 72 6F 72 2E 20 45 72 72 6F 72 20 3D 20 30 2E" + }, "5dd3bdd5e788cb8b3880563a7633fc96": { "request": "/* Java Script */", "response": "75 6E 64 65 66 69 6E 65 64 7C 4E 6F 20 65 72 72 6F 72 2E 20 45 72 72 6F 72 20 3D 20 30 2E" diff --git a/pocs/tests/test_theskyx_utils.py b/pocs/tests/test_theskyx_utils.py index b54dba961..677d0b0e2 100644 --- a/pocs/tests/test_theskyx_utils.py +++ b/pocs/tests/test_theskyx_utils.py @@ -70,3 +70,13 @@ def test_get_build(skyx): skyx.connect() skyx.write(js) assert skyx.read().startswith('10.5') + + +def test_error(skyx): + skyx.connect() + skyx.write(''' +/* Java Script */ +sky6RASCOMTele.FindHome() +''') + with pytest.raises(error.TheSkyXError): + skyx.read() diff --git a/pocs/utils/error.py b/pocs/utils/error.py index 6bafb74f2..f729d9f33 100644 --- a/pocs/utils/error.py +++ b/pocs/utils/error.py @@ -117,3 +117,8 @@ class TheSkyXError(PanError): class TheSkyXKeyError(TheSkyXError): """ Errors from TheSkyX because bad key passed """ pass + + +class TheSkyXTimeout(TheSkyXError): + """ Errors from TheSkyX because bad key passed """ + pass diff --git a/pocs/utils/theskyx.py b/pocs/utils/theskyx.py index bdb03c3d6..b798ecc56 100644 --- a/pocs/utils/theskyx.py +++ b/pocs/utils/theskyx.py @@ -52,20 +52,29 @@ def read(self, timeout=5): try: self.socket.settimeout(timeout) response = None + err = None try: response = self.socket.recv(4096).decode() + self.logger.debug("Raw response: {}", response) if '|' in response: response, err = response.split('|') + + if 'Error:' in response: + response, err = response.split(':') + + self.logger.debug("Raw response 2: {}", response) + self.logger.debug("Raw error: {}", err) + if err is not None and 'No error' not in err: if 'Error = 303' in err: raise error.TheSkyXKeyError("Invalid TheSkyX key") raise error.TheSkyXError(err) - elif err is None: - raise error.TheSkyXError("Error status not returned") + + self.logger.debug("{} - {}", response, err) except socket.timeout: - pass + raise error.TheSkyXTimeout() return response except AttributeError: From 3f4aa1a89a58efbfcb9771d868ae83164d3ca07e Mon Sep 17 00:00:00 2001 From: Wilfred Tyler Gee Date: Tue, 2 Jan 2018 13:57:17 +1100 Subject: [PATCH 3/3] Add cheating pragma --- pocs/utils/theskyx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pocs/utils/theskyx.py b/pocs/utils/theskyx.py index b798ecc56..d1753cf61 100644 --- a/pocs/utils/theskyx.py +++ b/pocs/utils/theskyx.py @@ -73,7 +73,7 @@ def read(self, timeout=5): raise error.TheSkyXError(err) self.logger.debug("{} - {}", response, err) - except socket.timeout: + except socket.timeout: # pragma: no cover raise error.TheSkyXTimeout() return response