Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix block persisting retry #22

Merged
merged 5 commits into from
Apr 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@
/xmodem.egg-info
/.coverage
/.tox
/.cache
/.idea
*.py.swp
12 changes: 11 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ For more information, take a look at the documentation_.
Changes
=======

0.4.4:
* bugfix: Large file transfers in ``send()`` were more likely to fail for
small values of ``retry``: This value should be the maximum failures per
block transfer as documented, but was improperly implemented as the number
of failures allowed for the total duration of the transfer, `PR #21
<https://github.com/tehmaze/xmodem/pull/21>`_.
* bugfix: ``send(retry=n)`` and ``recv(retry=n)`` would not retry ``n-1``
times, rather than ``n`` times as documented, causing 'retry=1' to never
retry, for example.

0.4.3:
* bugfix: ``putc()`` callback was called in series, 3 times for each part of
xmodem block header, data, and checksum during block transfer. Now all
Expand All @@ -57,7 +67,7 @@ Changes
`Issue #16 <https://github.com/tehmaze/xmodem/issues/16>`_.

0.4.1
* bugfix: re-transmit in send() on NAK or timeout, previously
* bugfix: re-transmit in ``send()`` on ``NAK`` or timeout, previously
re-transmissions (wrongly) occurred only on garbage bytes.
`PR #12 <https://github.com/tehmaze/xmodem/pull/12>`_.

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@

setup(
name='xmodem',
version='0.4.3',
author='Wijnand Modderman, Jeff Quast',
version='0.4.4',
author='Wijnand Modderman, Jeff Quast, Kris Hardy',
author_email='maze@pyth0n.org',
description=('XMODEM protocol implementation.'),
url='https://github.com/tehmaze/xmodem',
Expand Down
95 changes: 83 additions & 12 deletions test/unit/test_xmodem.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
from io import BytesIO
except ImportError:
# python 2
import StringIO.StringIO as BytesIO
from StringIO import StringIO as BytesIO

# local
import xmodem
from xmodem import NAK, CRC, ACK, XMODEM

# 3rd-party
import pytest
Expand All @@ -24,24 +24,95 @@ def dummy_putc(data, timeout=1):
return 0


def test_xmodem_bad_mode():
# given,
mode = 'XXX'
modem = XMODEM(getc=dummy_getc, putc=dummy_putc, mode=mode)
# exercise
with pytest.raises(ValueError):
status = modem.send(BytesIO(b'dummy-stream'))


@pytest.mark.parametrize('mode', ['xmodem', 'xmodem1k'])
def test_xmodem_dummy_fails_send(mode):
# given,
modem = xmodem.XMODEM(getc=dummy_getc,
putc=dummy_putc,
mode=mode)
modem = XMODEM(getc=dummy_getc, putc=dummy_putc, mode=mode)
# exercise
status = modem.send(BytesIO(b'dummy-stream'))
# verify
assert not status, ("Expected value of status `False'")


def test_xmodem_bad_mode():
@pytest.mark.parametrize('mode', ['xmodem', 'xmodem1k'])
@pytest.mark.parametrize('stream_data', [BytesIO(b'dummy-stream ' * 17),
BytesIO(b'dummy-stream ' * 1000)])
def test_xmodem_send_exceed_maximum_number_of_resend(mode, stream_data):
""" Verify send(retry=n) after 'n' transfer failures of single block. """

# given,
mode = 'XXX'
modem = xmodem.XMODEM(getc=dummy_getc,
putc=dummy_putc,
mode=mode)
max_resend = 3

def getc_generator():
if mode == 'xmodem':
yield NAK
else:
# xmodem1k
yield CRC

if mode == 'xmodem':
yield ACK

for i in range(max_resend + 1):
yield None

while True:
yield ACK

mock = getc_generator()

def mock_getc(size, timeout=1):
return next(mock)

xmodem = XMODEM(getc=mock_getc, putc=dummy_putc, mode=mode)

# exercise
with pytest.raises(ValueError):
status = modem.send(BytesIO(b'dummy-stream'))
result = xmodem.send(stream=stream_data, retry=max_resend)

# verify
assert not result


@pytest.mark.parametrize('mode', ['xmodem', 'xmodem1k'])
@pytest.mark.parametrize('stream_data', [BytesIO(b'dummy-stream ' * 17),
BytesIO(b'dummy-stream ' * 1000)])
def test_xmodem_send_fails_once_each_packet(mode, stream_data):
""" Verify send(retry=n) under 'n' transfer failures of single block. """
# given,
max_resend = 1

def getc_generator():
if mode == 'xmodem':
yield NAK
else:
# xmodem1k
yield CRC

while True:
# fail
yield None

# succeed
yield ACK

mock = getc_generator()

def mock_getc(size, timeout=1):
return next(mock)

xmodem = XMODEM(getc=mock_getc, putc=dummy_putc, mode=mode)

# exercise
result = xmodem.send(stream=stream_data, retry=max_resend)

# verify
assert result
15 changes: 8 additions & 7 deletions xmodem/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
__copyright__ = ['Copyright (c) 2010 Wijnand Modderman',
'Copyright (c) 1981 Chuck Forsberg']
__license__ = 'MIT'
__version__ = '0.4.3'
__version__ = '0.4.4'

import platform
import logging
Expand Down Expand Up @@ -210,7 +210,7 @@ def send(self, stream, retry=16, timeout=60, quiet=False, callback=None):
'''
Send a stream via the XMODEM protocol.

>>> stream = file('/etc/issue', 'rb')
>>> stream = open('/etc/issue', 'rb')
>>> print(modem.send(stream))
True

Expand Down Expand Up @@ -276,7 +276,7 @@ def callback(total_packets, success_count, error_count)
'got %r', char)

error_count += 1
if error_count >= retry:
if error_count > retry:
self.log.info('send error: error_count reached %d, '
'aborting.', retry)
self.abort(timeout=timeout)
Expand Down Expand Up @@ -308,14 +308,15 @@ def callback(total_packets, success_count, error_count)
success_count += 1
if callable(callback):
callback(total_packets, success_count, error_count)
error_count = 0
break

self.log.error('send error: expected ACK; got %r for block %d',
char, sequence)
error_count += 1
if callable(callback):
callback(total_packets, success_count, error_count)
if error_count >= retry:
if error_count > retry:
# excessive amounts of retransmissions requested,
# abort transfer
self.log.error('send error: NAK received %d times, '
Expand All @@ -338,7 +339,7 @@ def callback(total_packets, success_count, error_count)
else:
self.log.error('send error: expected ACK; got %r', char)
error_count += 1
if error_count >= retry:
if error_count > retry:
self.log.warn('EOT was not ACKd, aborting transfer')
self.abort(timeout=timeout)
return False
Expand Down Expand Up @@ -370,7 +371,7 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0):
'''
Receive a stream via the XMODEM protocol.

>>> stream = file('/etc/issue', 'wb')
>>> stream = open('/etc/issue', 'wb')
>>> print(modem.recv(stream))
2342

Expand Down Expand Up @@ -466,7 +467,7 @@ def recv(self, stream, crc_mode=1, retry=16, timeout=60, delay=1, quiet=0):
print(err_msg, file=sys.stderr)
self.log.warn(err_msg)
error_count += 1
if error_count >= retry:
if error_count > retry:
self.log.info('error_count reached %d, aborting.',
retry)
self.abort()
Expand Down