From 7a168b2163aa74a471e89334d0b8b4a16472ab65 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 21 Mar 2015 05:11:14 +0000 Subject: [PATCH 01/17] updated shields in README --- README.rst | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 6a1428c..3294579 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ APRS library for Python ~~~~~~~~~~~~~~~~~~~~~~~ -|Build Status| |Coverage Status| +|pypi| |coverage| |master_build| |dev_build| A tiny library for dealing with APRS. It can be used to connect and listen to the APRS-IS feed as well as upload. Parsing of packets is also possible, but the entire spec is not fully implemented yet. @@ -150,9 +150,19 @@ Docs $ python -m pydoc aprslib -.. |Build Status| image:: https://travis-ci.org/rossengeorgiev/aprs-python.svg?branch=master - :target: https://travis-ci.org/rossengeorgiev/aprs-python -.. |Coverage Status| image:: https://coveralls.io/repos/rossengeorgiev/aprs-python/badge.png?branch=master - :target: https://coveralls.io/r/rossengeorgiev/aprs-python?branch=master +.. |pypi| image:: https://img.shields.io/pypi/v/aprslib.svg?style=flat&label=latest%20version + :target: https://pypi.python.org/pypi/aprslib + :alt: Latest version released on PyPi +.. |coverage| image:: https://img.shields.io/coveralls/rossengeorgiev/aprs-python/master.svg?style=flat + :target: https://coveralls.io/r/rossengeorgiev/aprs-python?branch=master + :alt: Test coverage + +.. |master_build| image:: https://img.shields.io/travis/rossengeorgiev/aprs-python/master.svg?style=flat&label=master%20build + :target: http://travis-ci.org/rossengeorgiev/aprs-python + :alt: Build status of master branch + +.. |dev_build| image:: https://img.shields.io/travis/rossengeorgiev/aprs-python/dev.svg?style=flat&label=dev%20build + :target: http://travis-ci.org/rossengeorgiev/aprs-python + :alt: Build status of dev branch From 9fa62c433af11e9071e82c6dac731ea8b019d797 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 21 Mar 2015 23:08:29 +0000 Subject: [PATCH 02/17] don't try to decode already unicode * includes tests for decoding charset --- aprslib/parse.py | 17 +++++++++-------- req.txt | 1 + tests/test_parse.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 tests/test_parse.py diff --git a/aprslib/parse.py b/aprslib/parse.py index 8ee2f1e..887aa82 100644 --- a/aprslib/parse.py +++ b/aprslib/parse.py @@ -79,15 +79,16 @@ def parse(packet): """ # attempt to detect encoding - try: - packet = packet.decode('utf-8') - except UnicodeDecodeError: - res = chardet.detect(packet) + if isinstance(packet, str): + try: + packet = packet.decode('utf-8') + except UnicodeDecodeError: + res = chardet.detect(packet) - if res['confidence'] > 0.7: - packet = packet.decode(res['encoding']) - else: - packet = packet.decode('latin-1') + if res['confidence'] > 0.7: + packet = packet.decode(res['encoding']) + else: + packet = packet.decode('latin-1') packet = packet.rstrip("\r\n") logger.debug("Parsing: %s", packet) diff --git a/req.txt b/req.txt index a8c4082..324ce1a 100644 --- a/req.txt +++ b/req.txt @@ -2,3 +2,4 @@ pylint nose coverage mox +chardet diff --git a/tests/test_parse.py b/tests/test_parse.py new file mode 100644 index 0000000..4987e28 --- /dev/null +++ b/tests/test_parse.py @@ -0,0 +1,36 @@ +# encoding: utf-8 + +import unittest + +from aprslib.parse import parse + + +class ParseTestCase(unittest.TestCase): + def test_unicode(self): + # 7bit ascii + result = parse("A>B:>status") + + self.assertIsInstance(result['status'], unicode) + self.assertEqual(result['status'], u"status") + + # string with degree sign + result = parse("A>B:>status\xb0") + + self.assertIsInstance(result['status'], unicode) + self.assertEqual(result['status'], u"status\xb0") + + # str with unicode + result = parse("A>B:>статус") + + self.assertIsInstance(result['status'], unicode) + self.assertEqual(result['status'], u"статус") + + # uncide input + result = parse(u"A>B:>статус") + + self.assertIsInstance(result['status'], unicode) + self.assertEqual(result['status'], u"статус") + + +if __name__ == '__main__': + unittest.main() From a0b59eeaf7a117c86a69735efac0f8d9ea8bb450 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sun, 22 Mar 2015 03:04:06 +0000 Subject: [PATCH 03/17] first part of tests for parsing submodule --- aprslib/IS.py | 2 +- aprslib/__init__.py | 2 +- aprslib/{parse.py => parsing.py} | 6 +-- tests/test_parse.py | 77 ++++++++++++++++++++++++++- tests/test_parse_comment_telemetry.py | 2 +- tests/test_parse_header.py | 4 +- 6 files changed, 84 insertions(+), 9 deletions(-) rename aprslib/{parse.py => parsing.py} (99%) diff --git a/aprslib/IS.py b/aprslib/IS.py index 8f99d62..63790e4 100644 --- a/aprslib/IS.py +++ b/aprslib/IS.py @@ -25,7 +25,7 @@ import logging import sys from . import __version__ -from .parse import parse +from .parsing import parse from .exceptions import ( GenericError, ConnectionDrop, diff --git a/aprslib/__init__.py b/aprslib/__init__.py index a181640..2b1f361 100644 --- a/aprslib/__init__.py +++ b/aprslib/__init__.py @@ -42,7 +42,7 @@ __author__ = "Rossen Georgiev" __all__ = ['IS', 'parse', 'passcode'] from .exceptions import * -from .parse import parse +from .parsing import parse from .passcode import passcode from .IS import IS diff --git a/aprslib/parse.py b/aprslib/parsing.py similarity index 99% rename from aprslib/parse.py rename to aprslib/parsing.py index 887aa82..c6d9669 100644 --- a/aprslib/parse.py +++ b/aprslib/parsing.py @@ -34,8 +34,8 @@ except ImportError: def detect(x): return {'confidence': 0.0, 'encoding': 'windows-1252'} -from .exceptions import (UnknownFormat, ParseError) -from . import base91 +from exceptions import (UnknownFormat, ParseError) +import base91 __all__ = ['parse'] @@ -99,7 +99,7 @@ def parse(packet): # typical packet format # # CALL1>CALL2,CALL3,CALL4:>longtext...... - # |--------header--------|-----body-------| + # |--------header---------|-----body------| # try: (head, body) = packet.split(':', 1) diff --git a/tests/test_parse.py b/tests/test_parse.py index 4987e28..d9fb871 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -1,8 +1,11 @@ # encoding: utf-8 import unittest +import mox -from aprslib.parse import parse +from aprslib import parse +from aprslib import parsing +from aprslib.exceptions import ParseError, UnknownFormat class ParseTestCase(unittest.TestCase): @@ -31,6 +34,78 @@ class ParseTestCase(unittest.TestCase): self.assertIsInstance(result['status'], unicode) self.assertEqual(result['status'], u"статус") + def test_empty_packet(self): + self.assertRaises(ParseError, parse, "") + + def test_no_body(self): + self.assertRaises(ParseError, parse, "A>B") + + def test_empty_body(self): + self.assertRaises(ParseError, parse, "A>B:") + + def test_parse_header_exception(self): + self.assertRaises(ParseError, parse, "A:asd") + + def test_empty_body_of_format_that_is_not_status(self): + self.assertRaises(ParseError, parse, "A>B:!") + + try: + parse("A>B:>") + except: + self.fail("empty status packed shouldn't raise exception") + + def test_unsupported_formats_raising(self): + with self.assertRaises(UnknownFormat): + for packet_type in '#$%)*,B:%saaa" % packet_type) + + +class ParseBranchesTestCase(unittest.TestCase): + def setUp(self): + self.m = mox.Mox() + + def tearDown(self): + self.m.UnsetStubs() + + def test_status_format_branch(self): + self.m.StubOutWithMock(parsing, "_parse_timestamp") + parsing._parse_timestamp(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(("test", {})) + self.m.ReplayAll() + + expected = { + 'status': 'test', + 'raw': u'A>B:>test', + 'via': '', + 'from': u'A', + 'to': u'B', + 'path': [], + 'format': 'status' + } + result = parse("A>B:>test") + + self.assertEqual(result, expected) + self.m.VerifyAll() + + def test_mice_format_branch(self): + self.m.StubOutWithMock(parsing, "_parse_mice") + parsing._parse_mice("B","test").AndReturn(('', {'format':''})) + parsing._parse_mice("D","test").AndReturn(('', {'format':''})) + self.m.ReplayAll() + + parse("A>B:`test") + parse("C>D:'test") + + self.m.VerifyAll() + + def test_message_format_branch(self): + self.m.StubOutWithMock(parsing, "_parse_message") + parsing._parse_message("test").AndReturn(('', {'format':''})) + self.m.ReplayAll() + + parse("A>B::test") + + self.m.VerifyAll() + if __name__ == '__main__': unittest.main() diff --git a/tests/test_parse_comment_telemetry.py b/tests/test_parse_comment_telemetry.py index d0c8f4b..1d40f23 100644 --- a/tests/test_parse_comment_telemetry.py +++ b/tests/test_parse_comment_telemetry.py @@ -1,6 +1,6 @@ import unittest -from aprslib.parse import _parse_comment_telemetry +from aprslib.parsing import _parse_comment_telemetry from aprslib import base91 from random import randint diff --git a/tests/test_parse_header.py b/tests/test_parse_header.py index ab61212..92b8126 100644 --- a/tests/test_parse_header.py +++ b/tests/test_parse_header.py @@ -2,8 +2,8 @@ import unittest import string from random import randint, randrange, sample -from aprslib.parse import _parse_header -from aprslib.parse import _validate_callsign +from aprslib.parsing import _parse_header +from aprslib.parsing import _validate_callsign from aprslib.exceptions import ParseError From 6798a424276ab22d31e08d38f49ff00b435d0cad Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Tue, 10 Mar 2015 20:17:59 +0000 Subject: [PATCH 04/17] include latest changes in CHANGES file --- CHANGES | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGES b/CHANGES index 2f7d9b7..fcc36c6 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,23 @@ CHANGES ------- +# v0.6.37 + +- charset detection, unicode for all +- improved _parse_timestamp +- fix mic-e dstcall decoding bug +- tiny syntax fix in parse.py + +# v0.6.36 + +- fix typo in timestamp format +- tweak debug messages for position reports +- now able to parse object report format + +# v0.6.35 + +- rotated base91 telemetry bits in output + # v0.6.34 - fixed timestamps being parsed incorrectly From 92b22bd5bc15d57914036fe6bac059b91b427bc5 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sun, 22 Mar 2015 18:38:21 +0000 Subject: [PATCH 05/17] removed chardet from req.txt for test suite --- req.txt | 1 - tests/test_parse.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/req.txt b/req.txt index 324ce1a..a8c4082 100644 --- a/req.txt +++ b/req.txt @@ -2,4 +2,3 @@ pylint nose coverage mox -chardet diff --git a/tests/test_parse.py b/tests/test_parse.py index d9fb871..fb108bd 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -22,13 +22,13 @@ class ParseTestCase(unittest.TestCase): self.assertIsInstance(result['status'], unicode) self.assertEqual(result['status'], u"status\xb0") - # str with unicode + # str with utf8 result = parse("A>B:>статус") self.assertIsInstance(result['status'], unicode) self.assertEqual(result['status'], u"статус") - # uncide input + # unicode input result = parse(u"A>B:>статус") self.assertIsInstance(result['status'], unicode) From 3caa412696375ead081497bceaa4158453403c4c Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sun, 22 Mar 2015 20:57:11 +0000 Subject: [PATCH 06/17] test_parse code style tweaks --- tests/test_parse.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/test_parse.py b/tests/test_parse.py index fb108bd..e0e67cf 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -52,12 +52,18 @@ class ParseTestCase(unittest.TestCase): try: parse("A>B:>") except: - self.fail("empty status packed shouldn't raise exception") + self.fail("empty status packet shouldn't raise exception") def test_unsupported_formats_raising(self): with self.assertRaises(UnknownFormat): for packet_type in '#$%)*,B:%saaa" % packet_type) + packet = "A>B:%saaa" % packet_type + + try: + parse(packet) + except UnknownFormat as exp: + self.assertEqual(exp.packet, packet) + raise class ParseBranchesTestCase(unittest.TestCase): @@ -81,15 +87,15 @@ class ParseBranchesTestCase(unittest.TestCase): 'path': [], 'format': 'status' } - result = parse("A>B:>test") + result = parse("A>B:>test") self.assertEqual(result, expected) self.m.VerifyAll() def test_mice_format_branch(self): self.m.StubOutWithMock(parsing, "_parse_mice") - parsing._parse_mice("B","test").AndReturn(('', {'format':''})) - parsing._parse_mice("D","test").AndReturn(('', {'format':''})) + parsing._parse_mice("B", "test").AndReturn(('', {'format': ''})) + parsing._parse_mice("D", "test").AndReturn(('', {'format': ''})) self.m.ReplayAll() parse("A>B:`test") @@ -99,7 +105,7 @@ class ParseBranchesTestCase(unittest.TestCase): def test_message_format_branch(self): self.m.StubOutWithMock(parsing, "_parse_message") - parsing._parse_message("test").AndReturn(('', {'format':''})) + parsing._parse_message("test").AndReturn(('', {'format': ''})) self.m.ReplayAll() parse("A>B::test") From 6ac5220fe057f62263d30a67274febf23167580b Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sun, 19 Apr 2015 09:02:40 +0100 Subject: [PATCH 07/17] updated email in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4aad8e9..d2fe011 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ setup( long_description=long_description, url='https://github.com/rossengeorgiev/aprs-python', author='Rossen Georgiev', - author_email='zx.devel@gmail.com', + author_email='hello@rgp.io', license='GPLv2', classifiers=[ 'Development Status :: 4 - Beta', From 5f487bbaeee9819e31c20f7a06f4f47c0bb747eb Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Tue, 12 May 2015 15:54:20 +0100 Subject: [PATCH 08/17] fix relative import bug introduced in a0b59ee --- aprslib/parsing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aprslib/parsing.py b/aprslib/parsing.py index c6d9669..5db7c4e 100644 --- a/aprslib/parsing.py +++ b/aprslib/parsing.py @@ -34,8 +34,8 @@ except ImportError: def detect(x): return {'confidence': 0.0, 'encoding': 'windows-1252'} -from exceptions import (UnknownFormat, ParseError) -import base91 +from .exceptions import (UnknownFormat, ParseError) +from . import base91 __all__ = ['parse'] From b3cffc5fcaf02ab20727f17236d2efa7c7d3cbfe Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 16 May 2015 18:33:58 +0100 Subject: [PATCH 09/17] set verbosity=1 for make test --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 89e68be..8c0b825 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ Available commands: endef +verbosity=1 + export HELPBODY help: @echo "$$HELPBODY" @@ -20,7 +22,7 @@ init: test: rm -f aprslib/*.pyc - nosetests --verbosity 2 --with-coverage --cover-package=aprslib + nosetests --verbosity $(verbosity) --with-coverage --cover-package=aprslib pylint: pylint -r n -f colorized aprslib || true From d10860db5732a8c66274febf3d2e28a4d8827e58 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 16 May 2015 21:50:11 +0100 Subject: [PATCH 10/17] code and test changes for py3 compability --- .travis.yml | 6 ++- Makefile | 2 +- aprslib/IS.py | 11 ++-- aprslib/__init__.py | 13 +++++ aprslib/base91.py | 17 +++--- aprslib/exceptions.py | 1 + aprslib/parsing.py | 12 +++-- req.txt | 3 +- tests/test_IS.py | 74 +++++++++++++++------------ tests/test_base91.py | 29 +++++++---- tests/test_parse.py | 42 ++++++++++----- tests/test_parse_comment_telemetry.py | 4 +- tests/test_parse_header.py | 6 +-- 13 files changed, 140 insertions(+), 80 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5ef0739..ae60fc5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,15 @@ language: python python: + - "2.6" - "2.7" + - "3.2" + - "3.3" + - "3.4" install: - make init - pip install coveralls script: - make build + make test after_success: coveralls diff --git a/Makefile b/Makefile index 8c0b825..a3bad69 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ init: pip install -r req.txt test: - rm -f aprslib/*.pyc + rm -f .coverage aprslib/*.pyc nosetests --verbosity $(verbosity) --with-coverage --cover-package=aprslib pylint: diff --git a/aprslib/IS.py b/aprslib/IS.py index 63790e4..354f1cf 100644 --- a/aprslib/IS.py +++ b/aprslib/IS.py @@ -24,7 +24,7 @@ import time import logging import sys -from . import __version__ +from . import __version__, string_type from .parsing import parse from .exceptions import ( GenericError, @@ -131,14 +131,11 @@ class IS(object): """ Send a line, or multiple lines sperapted by '\\r\\n' """ + if not isinstance(line, string_type): + raise TypeError("Expected line to be str, got %s", type(line)) if not self._connected: raise ConnectionError("not connected") - if isinstance(line, unicode): - line = line.encode('utf8') - elif not isinstance(line, str): - line = str(line) - if line == "": return @@ -324,7 +321,7 @@ class IS(object): if not short_buf: raise ConnectionDrop("connection dropped") except socket.error as e: - if "Resource temporarily unavailable" in e: + if "Resource temporarily unavailable" in str(e): if not blocking: if len(self.buf) == 0: break diff --git a/aprslib/__init__.py b/aprslib/__init__.py index 2b1f361..4497069 100644 --- a/aprslib/__init__.py +++ b/aprslib/__init__.py @@ -23,6 +23,19 @@ Currently the library provides facilities to: - Connect and listen to an aprs-is packet feed """ +# Py2 & Py3 compability +import sys +if sys.version_info[0] >= 3: + is_py3 = True + string_type = (str, ) + string_type_parse = string_type + (bytes, ) + int_type = int +else: + is_py3 = False + string_type = (str, unicode) + string_type_parse = string_type + int_type = (int, long) + # handles reloading if 'IS' in globals(): MODULES = __import__('sys').modules diff --git a/aprslib/base91.py b/aprslib/base91.py index 15392c0..466c35a 100644 --- a/aprslib/base91.py +++ b/aprslib/base91.py @@ -22,6 +22,7 @@ Provides facilities for covertion from/to base91 __all__ = ['to_decimal', 'from_decimal'] from math import log from re import findall +from . import string_type, int_type def to_decimal(text): @@ -29,7 +30,7 @@ def to_decimal(text): Takes a base91 char string and returns decimal """ - if not isinstance(text, basestring): + if not isinstance(text, string_type): raise TypeError("expected str or unicode, %s given" % type(text)) if findall(r"[\x00-\x20\x7c-\xff]", text): @@ -50,13 +51,17 @@ def from_decimal(number, padding=1): """ text = [] - if not isinstance(number, (int, long)) is not int or number < 0: - raise ValueError("non-positive integer error") - elif not isinstance(number, (int, long)) or padding < 1: - raise ValueError("padding must be integer and >0") + if not isinstance(number, int_type): + raise TypeError("Expected number to be int, got %s", type(number)) + elif not isinstance(padding, int_type): + raise TypeError("Expected padding to be int, got %s", type(number)) + elif number < 0: + raise ValueError("Expected number to be positive integer") + elif padding < 1: + raise ValueError("Expected padding to be >0") elif number > 0: for divisor in [91**e for e in reversed(range(int(log(number) / log(91)) + 1))]: - quotient = number / divisor + quotient = number // divisor number = number % divisor text.append(chr(33 + quotient)) diff --git a/aprslib/exceptions.py b/aprslib/exceptions.py index 536a9c1..e21a632 100644 --- a/aprslib/exceptions.py +++ b/aprslib/exceptions.py @@ -35,6 +35,7 @@ class GenericError(Exception): """ def __init__(self, message): super(GenericError, self).__init__(message) + self.message = message class UnknownFormat(GenericError): diff --git a/aprslib/parsing.py b/aprslib/parsing.py index 5db7c4e..f4380dd 100644 --- a/aprslib/parsing.py +++ b/aprslib/parsing.py @@ -35,7 +35,7 @@ except ImportError: return {'confidence': 0.0, 'encoding': 'windows-1252'} from .exceptions import (UnknownFormat, ParseError) -from . import base91 +from . import base91, string_type_parse, is_py3 __all__ = ['parse'] @@ -78,12 +78,18 @@ def parse(packet): * status message """ + if not isinstance(packet, string_type_parse): + raise TypeError("Epected packet to be str/unicode/bytes, got %s", type(packet)) + # attempt to detect encoding - if isinstance(packet, str): + if isinstance(packet, bytes if is_py3 else str): try: packet = packet.decode('utf-8') except UnicodeDecodeError: - res = chardet.detect(packet) + if is_py3: + res = chardet.detect(packet.split(b':', 1)[-1]) + else: + res = chardet.detect(packet.split(':', 1)[-1]) if res['confidence'] > 0.7: packet = packet.decode(res['encoding']) diff --git a/req.txt b/req.txt index a8c4082..1a2edd9 100644 --- a/req.txt +++ b/req.txt @@ -1,4 +1,3 @@ -pylint nose coverage -mox +mox3 diff --git a/tests/test_IS.py b/tests/test_IS.py index b7aa382..43cf3df 100644 --- a/tests/test_IS.py +++ b/tests/test_IS.py @@ -1,11 +1,11 @@ import unittest -import mox -import aprslib -import logging import socket import sys import os +import aprslib +from mox3 import mox + class TC_IS(unittest.TestCase): def setUp(self): @@ -42,7 +42,7 @@ class TC_IS(unittest.TestCase): def test_socket_readlines(self): fdr, fdw = os.pipe() - f = os.fdopen(fdw,'w') + f = os.fdopen(fdw, 'w') f.write("something") f.close() @@ -57,15 +57,15 @@ class TC_IS(unittest.TestCase): # part 3 - nothing to read self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndRaise(socket.error("" - "Resource temporarily unavailable")) + self.ais.sock.recv(mox.IgnoreArg()).AndRaise( + socket.error("Resource temporarily unavailable")) # part 4 - yield 3 lines (blocking False) self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) self.ais.sock.recv(mox.IgnoreArg()).AndReturn("a\r\n"*3) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndRaise(socket.error("" - "Resource temporarily unavailable")) + self.ais.sock.recv(mox.IgnoreArg()).AndRaise( + socket.error("Resource temporarily unavailable")) # part 5 - yield 3 lines 2 times (blocking True) self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) @@ -76,15 +76,17 @@ class TC_IS(unittest.TestCase): self.ais.sock.recv(mox.IgnoreArg()).AndRaise(StopIteration) mox.Replay(self.ais.sock) + next_method = '__next__' if sys.version_info[0] >= 3 else 'next' + # part 1 with self.assertRaises(aprslib.exceptions.ConnectionDrop): - self.ais._socket_readlines().next() + getattr(self.ais._socket_readlines(), next_method)() # part 2 with self.assertRaises(aprslib.exceptions.ConnectionDrop): - self.ais._socket_readlines().next() + getattr(self.ais._socket_readlines(), next_method)() # part 3 with self.assertRaises(StopIteration): - self.ais._socket_readlines().next() + getattr(self.ais._socket_readlines(), next_method)() # part 4 for line in self.ais._socket_readlines(): self.assertEqual(line, 'a') @@ -260,47 +262,55 @@ class TC_IS(unittest.TestCase): self.m.VerifyAll() - def test_sendall(self): + def test_sendall_type_exception(self): + for testType in [5, 0.5, dict, list]: + with self.assertRaises(TypeError): + self.ais.sendall(testType) + + def test_sendall_not_connected(self): + self.ais._connected = False + with self.assertRaises(aprslib.ConnectionError): + self.ais.sendall("test") + + def test_sendall_socketerror(self): self.ais.sock = mox.MockAnything() self.m.StubOutWithMock(self.ais, "close") - # part 1 not connected - # - # part 2 socket.error + # setup self.ais.sock.setblocking(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) self.ais.sock.sendall(mox.IgnoreArg()).AndRaise(socket.error) self.ais.close() - # part 3 empty input - # mox.Replay(self.ais.sock) self.m.ReplayAll() - # part 1 - self.ais._connected = False - with self.assertRaises(aprslib.ConnectionError): - self.ais.sendall("test") - # part 2 + # test self.ais._connected = True with self.assertRaises(aprslib.ConnectionError): self.ais.sendall("test") - # part 3 - self.ais._connected = True - self.ais.sendall("") - - # verify so far + # verify mox.Verify(self.ais.sock) self.m.VerifyAll() + def test_sendall_empty_input(self): + self.ais._connected = True + self.ais.sendall("") + + def test_sendall_passing_to_socket(self): + self.ais.sock = mox.MockAnything() + self.m.StubOutWithMock(self.ais, "close") + # rest + _unicode = str if sys.version_info[0] >= 3 else unicode + self.ais._connected = True for line in [ - "test", # no \r\n - "test\r\n", # with \r\n - u"test", # unicode - 5, # number or anything with __str__ + "test", + "test\r\n", + _unicode("test"), + _unicode("test\r\n"), ]: # setup self.ais.sock = mox.MockAnything() @@ -380,7 +390,7 @@ class TC_IS_consumer(unittest.TestCase): self.m.VerifyAll() - def test_consumer_exptions(self): + def test_consumer_exceptions(self): self.ais._socket_readlines(False).AndRaise(SystemExit) self.ais._socket_readlines(False).AndRaise(KeyboardInterrupt) self.ais._socket_readlines(False).AndRaise(Exception("random")) diff --git a/tests/test_base91.py b/tests/test_base91.py index ba3cb6c..0789a92 100644 --- a/tests/test_base91.py +++ b/tests/test_base91.py @@ -1,4 +1,5 @@ import unittest +import sys from aprslib import base91 @@ -16,23 +17,29 @@ class a_FromDecimal(unittest.TestCase): # 91**2 = "!! # 91**3 = "!!! # etc - testData += [[91**i, '"' + '!'*i] for i in xrange(20)] + testData += [[91**i, '"' + '!'*i] for i in range(20)] for n, expected in testData: self.assertEqual(expected, base91.from_decimal(n)) - def test_invalid_input_type(self): - testData = ['0', '1', 1.0, None, [0]] + def test_invalid_number_type(self): + testData = ['0', '1', 1.0, None, [0], dict] for n in testData: - self.assertRaises(ValueError, base91.from_decimal, n) + self.assertRaises(TypeError, base91.from_decimal, n) - def test_invalid_input_range(self): + def test_invalid_number_range(self): testData = [-10000, -5, -1] for n in testData: self.assertRaises(ValueError, base91.from_decimal, n) + def test_invalid_padding_type(self): + testData = ['0', '1', 1.0, None, [0], dict] + + for n in testData: + self.assertRaises(TypeError, base91.from_decimal, 0, padding=n) + def test_valid_padding(self): testData = [1, 2, 5, 10, 100] @@ -63,8 +70,10 @@ class b_ToDecimal(unittest.TestCase): # 91**2 = "!! # 91**3 = "!!! # etc - testData += [[91**i, '"' + '!'*i] for i in xrange(20)] - testData += [[91**i, u'"' + u'!'*i] for i in xrange(20)] + testData += [[91**i, '"' + '!'*i] for i in range(20)] + + if sys.version_info[0] < 3: + testData += [[91**i, unicode('"') + unicode('!')*i] for i in range(20)] for expected, n in testData: self.assertEqual(expected, base91.to_decimal(n)) @@ -77,7 +86,7 @@ class b_ToDecimal(unittest.TestCase): def test_invalid_input(self): # test for every value outside of the accepted range - testData = [chr(i) for i in range(ord('!'))+range(ord('{')+1, 256)] + testData = [chr(i) for i in list(range(ord('!')))+list(range(ord('{')+1, 256))] # same as above, except each value is prefix with a valid char testData += ['!'+c for c in testData] @@ -88,14 +97,14 @@ class b_ToDecimal(unittest.TestCase): class c_Both(unittest.TestCase): def test_from_decimal_to_decimal(self): - for number in xrange(91**2 + 5): + for number in range(91**2 + 5): text = base91.from_decimal(number) result = base91.to_decimal(text) self.assertEqual(result, number) def test_stability(self): - for number in xrange(50): + for number in range(50): largeN = 91 ** number text = base91.from_decimal(largeN) result = base91.to_decimal(text) diff --git a/tests/test_parse.py b/tests/test_parse.py index e0e67cf..44522c1 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -1,7 +1,8 @@ # encoding: utf-8 +import sys import unittest -import mox +from mox3 import mox from aprslib import parse from aprslib import parsing @@ -10,29 +11,38 @@ from aprslib.exceptions import ParseError, UnknownFormat class ParseTestCase(unittest.TestCase): def test_unicode(self): + def _u(text, c='utf8'): + if sys.version_info[0] >= 3: + return text + else: + return text.decode(c) + + + _unicode = str if sys.version_info[0] >= 3 else unicode + # 7bit ascii result = parse("A>B:>status") - self.assertIsInstance(result['status'], unicode) - self.assertEqual(result['status'], u"status") + self.assertIsInstance(result['status'],_unicode) + self.assertEqual(result['status'], _u("status")) # string with degree sign result = parse("A>B:>status\xb0") - self.assertIsInstance(result['status'], unicode) - self.assertEqual(result['status'], u"status\xb0") + self.assertIsInstance(result['status'],_unicode) + self.assertEqual(result['status'], _u("status\xb0",'latin-1')) # str with utf8 result = parse("A>B:>статус") - self.assertIsInstance(result['status'], unicode) - self.assertEqual(result['status'], u"статус") + self.assertIsInstance(result['status'],_unicode) + self.assertEqual(result['status'], _u("статус")) # unicode input - result = parse(u"A>B:>статус") + result = parse(_u("A>B:>статус")) - self.assertIsInstance(result['status'], unicode) - self.assertEqual(result['status'], u"статус") + self.assertIsInstance(result['status'],_unicode) + self.assertEqual(result['status'], _u("статус")) def test_empty_packet(self): self.assertRaises(ParseError, parse, "") @@ -78,12 +88,18 @@ class ParseBranchesTestCase(unittest.TestCase): parsing._parse_timestamp(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(("test", {})) self.m.ReplayAll() + def _u(text, c='utf8'): + if sys.version_info[0] >= 3: + return text + else: + return text.decode(c) + expected = { 'status': 'test', - 'raw': u'A>B:>test', + 'raw': _u('A>B:>test'), 'via': '', - 'from': u'A', - 'to': u'B', + 'from': _u('A'), + 'to': _u('B'), 'path': [], 'format': 'status' } diff --git a/tests/test_parse_comment_telemetry.py b/tests/test_parse_comment_telemetry.py index 1d40f23..320effa 100644 --- a/tests/test_parse_comment_telemetry.py +++ b/tests/test_parse_comment_telemetry.py @@ -42,8 +42,8 @@ class ParseCommentTelemetry(unittest.TestCase): return "".join(text) def test_random_valid_telemetry(self): - for i in xrange(100): - vals = [randint(0, self.b91max) for x in xrange(randint(1, 5))] + for i in range(100): + vals = [randint(0, self.b91max) for x in range(randint(1, 5))] bits = None diff --git a/tests/test_parse_header.py b/tests/test_parse_header.py index 92b8126..653f1a5 100644 --- a/tests/test_parse_header.py +++ b/tests/test_parse_header.py @@ -10,10 +10,10 @@ from aprslib.exceptions import ParseError class ValidateCallsign(unittest.TestCase): def test_valid_input(self): - chars = string.letters.upper() + string.digits + chars = string.ascii_letters.upper() + string.digits def random_valid_callsigns(): - for x in xrange(0, 500): + for x in range(0, 500): call = "".join(sample(chars, randrange(1, 6))) if bool(randint(0, 1)): @@ -118,7 +118,7 @@ class ParseHeader(unittest.TestCase): for head in testData: try: _parse_header(head) - except ParseError, msg: + except ParseError as msg: self.fail("{0}('{1}') PraseError, {2}" .format(_parse_header.__name__, head, msg)) From b5c0ce07ac6d516e020f00ff7a579da2fe34e398 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 16 May 2015 22:04:22 +0100 Subject: [PATCH 11/17] use unittest2, so tests run under py2.6 --- req.txt | 1 + tests/test_IS.py | 2 +- tests/test_base91.py | 2 +- tests/test_exceptions.py | 2 +- tests/test_parse.py | 2 +- tests/test_parse_comment_telemetry.py | 2 +- tests/test_parse_header.py | 2 +- tests/test_passcode.py | 2 +- 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/req.txt b/req.txt index 1a2edd9..257fde8 100644 --- a/req.txt +++ b/req.txt @@ -1,3 +1,4 @@ nose coverage +unittest2 mox3 diff --git a/tests/test_IS.py b/tests/test_IS.py index 43cf3df..5309d12 100644 --- a/tests/test_IS.py +++ b/tests/test_IS.py @@ -1,4 +1,4 @@ -import unittest +import unittest2 as unittest import socket import sys import os diff --git a/tests/test_base91.py b/tests/test_base91.py index 0789a92..958f4c7 100644 --- a/tests/test_base91.py +++ b/tests/test_base91.py @@ -1,4 +1,4 @@ -import unittest +import unittest2 as unittest import sys from aprslib import base91 diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 68595e4..f6f06df 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,4 +1,4 @@ -import unittest +import unittest2 as unittest from aprslib.exceptions import * diff --git a/tests/test_parse.py b/tests/test_parse.py index 44522c1..da2a5f5 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -1,7 +1,7 @@ # encoding: utf-8 import sys -import unittest +import unittest2 as unittest from mox3 import mox from aprslib import parse diff --git a/tests/test_parse_comment_telemetry.py b/tests/test_parse_comment_telemetry.py index 320effa..068d50f 100644 --- a/tests/test_parse_comment_telemetry.py +++ b/tests/test_parse_comment_telemetry.py @@ -1,4 +1,4 @@ -import unittest +import unittest2 as unittest from aprslib.parsing import _parse_comment_telemetry from aprslib import base91 diff --git a/tests/test_parse_header.py b/tests/test_parse_header.py index 653f1a5..15f991f 100644 --- a/tests/test_parse_header.py +++ b/tests/test_parse_header.py @@ -1,4 +1,4 @@ -import unittest +import unittest2 as unittest import string from random import randint, randrange, sample diff --git a/tests/test_passcode.py b/tests/test_passcode.py index af51566..03181b0 100644 --- a/tests/test_passcode.py +++ b/tests/test_passcode.py @@ -1,4 +1,4 @@ -import unittest +import unittest2 as unittest from aprslib import passcode From d55cc91176cf3777f9da196785eaf56fd3f5a273 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 16 May 2015 22:08:52 +0100 Subject: [PATCH 12/17] fix format field index (py2.6 things) --- tests/test_parse_comment_telemetry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_parse_comment_telemetry.py b/tests/test_parse_comment_telemetry.py index 068d50f..5c9058d 100644 --- a/tests/test_parse_comment_telemetry.py +++ b/tests/test_parse_comment_telemetry.py @@ -48,7 +48,7 @@ class ParseCommentTelemetry(unittest.TestCase): bits = None if len(vals) is 5 and randint(1, 10) > 5: - bits = "{:08b}".format(randint(0, 255))[::-1] + bits = "{0:08b}".format(randint(0, 255))[::-1] testData = self.genTelem(i, vals, bits) From 5329bf782dfb338e3664d948d56568c40a142414 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 16 May 2015 22:58:15 +0100 Subject: [PATCH 13/17] fix code+tests to make them work under py3 --- aprslib/IS.py | 41 ++++++++++++++++++++------- tests/test_IS.py | 74 +++++++++++++++++++++++++++--------------------- 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/aprslib/IS.py b/aprslib/IS.py index 354f1cf..b2f016b 100644 --- a/aprslib/IS.py +++ b/aprslib/IS.py @@ -24,7 +24,7 @@ import time import logging import sys -from . import __version__, string_type +from . import __version__, string_type, is_py3 from .parsing import parse from .exceptions import ( GenericError, @@ -67,7 +67,15 @@ class IS(object): self.filter = "" # default filter, everything self._connected = False - self.buf = '' + if is_py3: + self.buf = b'' + else: + self.buf = '' + + def _sendall(self, text): + if is_py3: + text = text.encode('utf-8') + self.sock.sendall(text) def set_filter(self, filter_text): """ @@ -78,7 +86,7 @@ class IS(object): self.logger.info("Setting filter to: %s", self.filter) if self._connected: - self.sock.sendall("#filter %s\r\n" % self.filter) + self._sendall("#filter %s\r\n" % self.filter) def set_login(self, callsign, passwd): """ @@ -122,7 +130,10 @@ class IS(object): """ self._connected = False - self.buf = '' + if is_py3: + self.buf = b'' + else: + self.buf = '' if self.sock is not None: self.sock.close() @@ -144,7 +155,7 @@ class IS(object): try: self.sock.setblocking(1) self.sock.settimeout(5) - self.sock.sendall(line) + self._sendall(line) except socket.error as exp: self.close() raise ConnectionError(str(exp)) @@ -239,6 +250,8 @@ class IS(object): # pylint: enable=E1103 banner = self.sock.recv(512) + if is_py3: + banner = banner.decode('latin-1') if banner[0] == "#": self.logger.debug("Banner: %s", banner.rstrip()) @@ -273,9 +286,12 @@ class IS(object): self.logger.info("Sending login information") try: - self.sock.sendall(login_str) + self._sendall(login_str) self.sock.settimeout(5) - test = self.sock.recv(len(login_str) + 100).rstrip() + test = self.sock.recv(len(login_str) + 100) + if is_py3: + test = test.decode('latin-1') + test = test.rstrip() self.logger.debug("Server: %s", test) @@ -310,7 +326,12 @@ class IS(object): raise ConnectionDrop("connection dropped") while True: - short_buf = '' + if is_py3: + short_buf = b'' + newline = b'\r\n' + else: + short_buf = '' + newline = '\r\n' select.select([self.sock], [], [], None if blocking else 0) @@ -328,7 +349,7 @@ class IS(object): self.buf += short_buf - while "\r\n" in self.buf: - line, self.buf = self.buf.split("\r\n", 1) + while newline in self.buf: + line, self.buf = self.buf.split(newline, 1) yield line diff --git a/tests/test_IS.py b/tests/test_IS.py index 5309d12..ee258ce 100644 --- a/tests/test_IS.py +++ b/tests/test_IS.py @@ -7,6 +7,14 @@ import aprslib from mox3 import mox +# byte shim for testing in both py2 and py3 +def _b(text): + if sys.version_info[0] >= 3: + return text.encode('latin-1') + else: + return text + + class TC_IS(unittest.TestCase): def setUp(self): self.ais = aprslib.IS("LZ1DEV-99", "testpwd", "127.0.0.1", "11111") @@ -17,7 +25,7 @@ class TC_IS(unittest.TestCase): def test_initilization(self): self.assertFalse(self.ais._connected) - self.assertEqual(self.ais.buf, '') + self.assertEqual(self.ais.buf, _b('')) self.assertIsNone(self.ais.sock) self.assertEqual(self.ais.callsign, "LZ1DEV-99") self.assertEqual(self.ais.passwd, "testpwd") @@ -25,16 +33,15 @@ class TC_IS(unittest.TestCase): def test_close(self): self.ais._connected = True - s = mox.MockAnything() - s.close() - self.ais.sock = s - mox.Replay(s) + self.ais.sock = mox.MockAnything() + self.ais.sock.close() + mox.Replay(self.ais.sock) self.ais.close() - mox.Verify(s) + mox.Verify(self.ais.sock) self.assertFalse(self.ais._connected) - self.assertEqual(self.ais.buf, '') + self.assertEqual(self.ais.buf, _b('')) def test_open_socket(self): with self.assertRaises(socket.error): @@ -53,7 +60,7 @@ class TC_IS(unittest.TestCase): # part 2 - conn drop trying to recv self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn('') + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b('')) # part 3 - nothing to read self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) @@ -62,16 +69,16 @@ class TC_IS(unittest.TestCase): # part 4 - yield 3 lines (blocking False) self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("a\r\n"*3) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("a\r\n"*3)) self.ais.sock.fileno().AndReturn(fdr) self.ais.sock.recv(mox.IgnoreArg()).AndRaise( socket.error("Resource temporarily unavailable")) # part 5 - yield 3 lines 2 times (blocking True) self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("b\r\n"*3) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("b\r\n"*3)) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("b\r\n"*3) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("b\r\n"*3)) self.ais.sock.fileno().AndReturn(fdr) self.ais.sock.recv(mox.IgnoreArg()).AndRaise(StopIteration) mox.Replay(self.ais.sock) @@ -89,44 +96,45 @@ class TC_IS(unittest.TestCase): getattr(self.ais._socket_readlines(), next_method)() # part 4 for line in self.ais._socket_readlines(): - self.assertEqual(line, 'a') + self.assertEqual(line, _b('a')) # part 5 for line in self.ais._socket_readlines(blocking=True): - self.assertEqual(line, 'b') + self.assertEqual(line, _b('b')) mox.Verify(self.ais.sock) def test_send_login(self): self.ais.sock = mox.MockAnything() self.m.StubOutWithMock(self.ais, "close") + self.m.StubOutWithMock(self.ais, "_sendall") # part 1 - raises - self.ais.sock.sendall(mox.IgnoreArg()) + self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("invalidreply") + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("invalidreply")) self.ais.close() # part 2 - raises (empty callsign) - self.ais.sock.sendall(mox.IgnoreArg()) + self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("# logresp verified, xx") + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp verified, xx")) self.ais.close() # part 3 - raises (callsign doesn't match - self.ais.sock.sendall(mox.IgnoreArg()) + self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("# logresp NOMATCH verified, xx") + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp NOMATCH verified, xx")) self.ais.close() # part 4 - raises (unverified, but pass is not -1) - self.ais.sock.sendall(mox.IgnoreArg()) + self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("# logresp CALL unverified, xx") + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp CALL unverified, xx")) self.ais.close() # part 5 - normal, receive only - self.ais.sock.sendall(mox.IgnoreArg()) + self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("# logresp CALL unverified, xx") + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp CALL unverified, xx")) # part 6 - normal, correct pass - self.ais.sock.sendall(mox.IgnoreArg()) + self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("# logresp CALL verified, xx") + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp CALL verified, xx")) mox.Replay(self.ais.sock) self.m.ReplayAll() @@ -171,7 +179,7 @@ class TC_IS(unittest.TestCase): self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("junk") + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("junk")) self.ais.close() # part 3 - everything going well self.ais._open_socket() @@ -183,7 +191,7 @@ class TC_IS(unittest.TestCase): self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn("# server banner") + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# server banner")) mox.Replay(self.ais.sock) self.m.ReplayAll() @@ -206,15 +214,14 @@ class TC_IS(unittest.TestCase): testFilter = "x/CALLSIGN" self.ais._connected = True - s = mox.MockAnything() - s.sendall("#filter %s\r\n" % testFilter) - self.ais.sock = s - mox.Replay(s) + self.ais.sock = mox.MockAnything() + self.ais.sock.sendall(_b("#filter %s\r\n" % testFilter)) + mox.Replay(self.ais.sock) self.ais.set_filter(testFilter) self.assertEqual(self.ais.filter, testFilter) - mox.Verify(s) + mox.Verify(self.ais.sock) def test_connect_from_notconnected(self): self.m.StubOutWithMock(self.ais, "_connect") @@ -301,6 +308,7 @@ class TC_IS(unittest.TestCase): def test_sendall_passing_to_socket(self): self.ais.sock = mox.MockAnything() self.m.StubOutWithMock(self.ais, "close") + self.m.StubOutWithMock(self.ais, "_sendall") # rest _unicode = str if sys.version_info[0] >= 3 else unicode @@ -316,7 +324,7 @@ class TC_IS(unittest.TestCase): self.ais.sock = mox.MockAnything() self.ais.sock.setblocking(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.sendall("%s\r\n" % str(line).rstrip('\r\n')).AndReturn(None) + self.ais._sendall(_b("%s\r\n" % str(line).rstrip('\r\n'))).AndReturn(None) mox.Replay(self.ais.sock) self.ais.sendall(line) From a3a72cb4af1c25a40656788b212d66d40d9025e9 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Wed, 27 May 2015 23:22:23 +0100 Subject: [PATCH 14/17] refined code using bytes type --- aprslib/IS.py | 18 ++++-------------- aprslib/parsing.py | 7 ++----- tests/test_IS.py | 44 +++++++++++++++++++------------------------- tests/test_parse.py | 24 ++++++++++++------------ 4 files changed, 37 insertions(+), 56 deletions(-) diff --git a/aprslib/IS.py b/aprslib/IS.py index b2f016b..0716f12 100644 --- a/aprslib/IS.py +++ b/aprslib/IS.py @@ -67,10 +67,7 @@ class IS(object): self.filter = "" # default filter, everything self._connected = False - if is_py3: - self.buf = b'' - else: - self.buf = '' + self.buf = b'' def _sendall(self, text): if is_py3: @@ -130,10 +127,7 @@ class IS(object): """ self._connected = False - if is_py3: - self.buf = b'' - else: - self.buf = '' + self.buf = b'' if self.sock is not None: self.sock.close() @@ -326,12 +320,8 @@ class IS(object): raise ConnectionDrop("connection dropped") while True: - if is_py3: - short_buf = b'' - newline = b'\r\n' - else: - short_buf = '' - newline = '\r\n' + short_buf = b'' + newline = b'\r\n' select.select([self.sock], [], [], None if blocking else 0) diff --git a/aprslib/parsing.py b/aprslib/parsing.py index f4380dd..83d8407 100644 --- a/aprslib/parsing.py +++ b/aprslib/parsing.py @@ -82,14 +82,11 @@ def parse(packet): raise TypeError("Epected packet to be str/unicode/bytes, got %s", type(packet)) # attempt to detect encoding - if isinstance(packet, bytes if is_py3 else str): + if isinstance(packet, bytes): try: packet = packet.decode('utf-8') except UnicodeDecodeError: - if is_py3: - res = chardet.detect(packet.split(b':', 1)[-1]) - else: - res = chardet.detect(packet.split(':', 1)[-1]) + res = chardet.detect(packet.split(b':', 1)[-1]) if res['confidence'] > 0.7: packet = packet.decode(res['encoding']) diff --git a/tests/test_IS.py b/tests/test_IS.py index ee258ce..6bfa4b4 100644 --- a/tests/test_IS.py +++ b/tests/test_IS.py @@ -8,12 +8,6 @@ from mox3 import mox # byte shim for testing in both py2 and py3 -def _b(text): - if sys.version_info[0] >= 3: - return text.encode('latin-1') - else: - return text - class TC_IS(unittest.TestCase): def setUp(self): @@ -25,7 +19,7 @@ class TC_IS(unittest.TestCase): def test_initilization(self): self.assertFalse(self.ais._connected) - self.assertEqual(self.ais.buf, _b('')) + self.assertEqual(self.ais.buf, b'') self.assertIsNone(self.ais.sock) self.assertEqual(self.ais.callsign, "LZ1DEV-99") self.assertEqual(self.ais.passwd, "testpwd") @@ -41,7 +35,7 @@ class TC_IS(unittest.TestCase): mox.Verify(self.ais.sock) self.assertFalse(self.ais._connected) - self.assertEqual(self.ais.buf, _b('')) + self.assertEqual(self.ais.buf, b'') def test_open_socket(self): with self.assertRaises(socket.error): @@ -60,7 +54,7 @@ class TC_IS(unittest.TestCase): # part 2 - conn drop trying to recv self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b('')) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b'') # part 3 - nothing to read self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) @@ -69,16 +63,16 @@ class TC_IS(unittest.TestCase): # part 4 - yield 3 lines (blocking False) self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("a\r\n"*3)) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"a\r\n"*3) self.ais.sock.fileno().AndReturn(fdr) self.ais.sock.recv(mox.IgnoreArg()).AndRaise( socket.error("Resource temporarily unavailable")) # part 5 - yield 3 lines 2 times (blocking True) self.ais.sock.setblocking(0) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("b\r\n"*3)) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"b\r\n"*3) self.ais.sock.fileno().AndReturn(fdr) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("b\r\n"*3)) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"b\r\n"*3) self.ais.sock.fileno().AndReturn(fdr) self.ais.sock.recv(mox.IgnoreArg()).AndRaise(StopIteration) mox.Replay(self.ais.sock) @@ -96,10 +90,10 @@ class TC_IS(unittest.TestCase): getattr(self.ais._socket_readlines(), next_method)() # part 4 for line in self.ais._socket_readlines(): - self.assertEqual(line, _b('a')) + self.assertEqual(line, b'a') # part 5 for line in self.ais._socket_readlines(blocking=True): - self.assertEqual(line, _b('b')) + self.assertEqual(line, b'b') mox.Verify(self.ais.sock) @@ -110,31 +104,31 @@ class TC_IS(unittest.TestCase): # part 1 - raises self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("invalidreply")) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"invalidreply") self.ais.close() # part 2 - raises (empty callsign) self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp verified, xx")) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"# logresp verified, xx") self.ais.close() # part 3 - raises (callsign doesn't match self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp NOMATCH verified, xx")) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"# logresp NOMATCH verified, xx") self.ais.close() # part 4 - raises (unverified, but pass is not -1) self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp CALL unverified, xx")) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"# logresp CALL unverified, xx") self.ais.close() # part 5 - normal, receive only self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp CALL unverified, xx")) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"# logresp CALL unverified, xx") # part 6 - normal, correct pass self.ais._sendall(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# logresp CALL verified, xx")) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"# logresp CALL verified, xx") mox.Replay(self.ais.sock) self.m.ReplayAll() @@ -179,7 +173,7 @@ class TC_IS(unittest.TestCase): self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("junk")) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"junk") self.ais.close() # part 3 - everything going well self.ais._open_socket() @@ -191,7 +185,7 @@ class TC_IS(unittest.TestCase): self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) self.ais.sock.setsockopt(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) - self.ais.sock.recv(mox.IgnoreArg()).AndReturn(_b("# server banner")) + self.ais.sock.recv(mox.IgnoreArg()).AndReturn(b"# server banner") mox.Replay(self.ais.sock) self.m.ReplayAll() @@ -211,11 +205,11 @@ class TC_IS(unittest.TestCase): self.m.VerifyAll() def test_filter(self): - testFilter = "x/CALLSIGN" + testFilter = 'x/CALLSIGN' self.ais._connected = True self.ais.sock = mox.MockAnything() - self.ais.sock.sendall(_b("#filter %s\r\n" % testFilter)) + self.ais.sock.sendall(b'#filter ' + testFilter.encode('ascii') + b'\r\n') mox.Replay(self.ais.sock) self.ais.set_filter(testFilter) @@ -324,7 +318,7 @@ class TC_IS(unittest.TestCase): self.ais.sock = mox.MockAnything() self.ais.sock.setblocking(mox.IgnoreArg()) self.ais.sock.settimeout(mox.IgnoreArg()) - self.ais._sendall(_b("%s\r\n" % str(line).rstrip('\r\n'))).AndReturn(None) + self.ais._sendall(b"%c" + line.rstrip('\r\n').encode('ascii') + b'\r\n').AndReturn(None) mox.Replay(self.ais.sock) self.ais.sendall(line) diff --git a/tests/test_parse.py b/tests/test_parse.py index da2a5f5..c0b7a4c 100644 --- a/tests/test_parse.py +++ b/tests/test_parse.py @@ -9,39 +9,39 @@ from aprslib import parsing from aprslib.exceptions import ParseError, UnknownFormat +def _u(text, c='utf8'): + if sys.version_info[0] >= 3: + return text + else: + return text.decode(c) + + class ParseTestCase(unittest.TestCase): def test_unicode(self): - def _u(text, c='utf8'): - if sys.version_info[0] >= 3: - return text - else: - return text.decode(c) - - _unicode = str if sys.version_info[0] >= 3 else unicode # 7bit ascii result = parse("A>B:>status") - self.assertIsInstance(result['status'],_unicode) + self.assertIsInstance(result['status'], _unicode) self.assertEqual(result['status'], _u("status")) # string with degree sign result = parse("A>B:>status\xb0") - self.assertIsInstance(result['status'],_unicode) - self.assertEqual(result['status'], _u("status\xb0",'latin-1')) + self.assertIsInstance(result['status'], _unicode) + self.assertEqual(result['status'], _u("status\xb0", 'latin-1')) # str with utf8 result = parse("A>B:>статус") - self.assertIsInstance(result['status'],_unicode) + self.assertIsInstance(result['status'], _unicode) self.assertEqual(result['status'], _u("статус")) # unicode input result = parse(_u("A>B:>статус")) - self.assertIsInstance(result['status'],_unicode) + self.assertIsInstance(result['status'], _unicode) self.assertEqual(result['status'], _u("статус")) def test_empty_packet(self): From 8ee87bd85bd739fd429b5409599a3fe9f141c9c0 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 30 May 2015 04:16:52 +0100 Subject: [PATCH 15/17] raise exception on long/lat ambiguity mismatch --- aprslib/parsing.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aprslib/parsing.py b/aprslib/parsing.py index 83d8407..37223b4 100644 --- a/aprslib/parsing.py +++ b/aprslib/parsing.py @@ -868,6 +868,10 @@ def _parse_normal(body): # position ambiguity posambiguity = lat_min.count(' ') + + if posambiguity != lon_min.count(' '): + raise ParseError("latitude and longitude ambiguity mismatch") + parsed.update({'posambiguity': posambiguity}) # we center the position inside the ambiguity box From 8ea78c76efbbc40e3e31f6f506ec7442fa382018 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sun, 31 May 2015 21:44:09 +0100 Subject: [PATCH 16/17] mark package as zip_safe --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d2fe011..286136e 100644 --- a/setup.py +++ b/setup.py @@ -31,5 +31,5 @@ setup( keywords='aprs aprslib parse parsing aprs-is library base91', packages=['aprslib'], install_requires=[], - zip_safe=False, + zip_safe=True, ) From dc6356a33da122cab1dfc38356040d01b44fd979 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Fri, 17 Jul 2015 20:31:07 +0100 Subject: [PATCH 17/17] bump to v0.6.38 --- aprslib/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aprslib/__init__.py b/aprslib/__init__.py index 4497069..477a038 100644 --- a/aprslib/__init__.py +++ b/aprslib/__init__.py @@ -50,7 +50,7 @@ from datetime import date as _date __date__ = str(_date.today()) del _date -__version__ = "0.6.37" +__version__ = "0.6.38" __author__ = "Rossen Georgiev" __all__ = ['IS', 'parse', 'passcode']