Merge ~pappacena/turnip:hookrpc-networking-fixes into turnip:master

Proposed by Thiago F. Pappacena
Status: Merged
Approved by: Thiago F. Pappacena
Approved revision: 848df674ac7bc86451dd719923487495fbfba6a9
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~pappacena/turnip:hookrpc-networking-fixes
Merge into: turnip:master
Diff against target: 120 lines (+46/-6)
3 files modified
turnip/pack/hookrpc.py (+2/-0)
turnip/pack/hooks/hook.py (+9/-3)
turnip/pack/tests/test_hooks.py (+35/-3)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+384848@code.launchpad.net

Commit message

Fixing bugs on hookrpc communication protocol causing large pushes to stale, and small refactoring to add more verbose exceptions.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/turnip/pack/hookrpc.py b/turnip/pack/hookrpc.py
index 8c99e76..ec62672 100644
--- a/turnip/pack/hookrpc.py
+++ b/turnip/pack/hookrpc.py
@@ -41,6 +41,8 @@ from turnip.pack.helpers import (
4141
42class JSONNetstringProtocol(basic.NetstringReceiver):42class JSONNetstringProtocol(basic.NetstringReceiver):
43 """A protocol that sends and receives JSON as netstrings."""43 """A protocol that sends and receives JSON as netstrings."""
44 # 200MB should be enough.
45 MAX_LENGTH = 200 * 1024 * 1024
4446
45 def stringReceived(self, string):47 def stringReceived(self, string):
46 try:48 try:
diff --git a/turnip/pack/hooks/hook.py b/turnip/pack/hooks/hook.py
index e135395..0dba4dd 100755
--- a/turnip/pack/hooks/hook.py
+++ b/turnip/pack/hooks/hook.py
@@ -106,21 +106,27 @@ def match_update_rules(rule_lines, ref_line):
106106
107107
108def netstring_send(sock, s):108def netstring_send(sock, s):
109 sock.send(b'%d:%s,' % (len(s), s))109 sock.sendall(b'%d:%s,' % (len(s), s))
110110
111111
112def netstring_recv(sock):112def netstring_recv(sock):
113 c = sock.recv(1)113 c = sock.recv(1)
114 lengthstr = b''114 lengthstr = b''
115 while c != b':':115 while c != b':':
116 assert c.isdigit()116 if not c.isdigit():
117 raise ValueError(
118 "Invalid response: %s" % (six.ensure_text(c + sock.recv(256))))
117 lengthstr += c119 lengthstr += c
118 c = sock.recv(1)120 c = sock.recv(1)
119 length = int(lengthstr)121 length = int(lengthstr)
120 s = bytearray()122 s = bytearray()
121 while len(s) < length:123 while len(s) < length:
122 s += sock.recv(length - len(s))124 s += sock.recv(length - len(s))
123 assert sock.recv(1) == b','125 ending = sock.recv(1)
126 if ending != b',':
127 raise ValueError(
128 "Length error for message '%s': ending='%s'" %
129 (six.ensure_text(bytes(s)), six.ensure_text(ending)))
124 return bytes(s)130 return bytes(s)
125131
126132
diff --git a/turnip/pack/tests/test_hooks.py b/turnip/pack/tests/test_hooks.py
index 449b9f9..a98f16b 100644
--- a/turnip/pack/tests/test_hooks.py
+++ b/turnip/pack/tests/test_hooks.py
@@ -88,9 +88,24 @@ class MockRepo(object):
8888
8989
90class MockSocket(object):90class MockSocket(object):
91 # "sends" up to this amount of bytes on "send()"
92 MAX_SEND_DATA = 5
9193
92 def __init__(self, blocks=None):94 def __init__(self, blocks=None):
93 self.blocks = blocks or []95 self.blocks = blocks or []
96 self._sent_data = b""
97
98 def send(self, data, flags=None):
99 to_send = data[:self.MAX_SEND_DATA]
100 self._sent_data += to_send
101 return min(self.MAX_SEND_DATA, len(data))
102
103 def sendall(self, data, flags=None):
104 ret = self.send(data, flags)
105 if ret > 0:
106 return self.sendall(data[ret:], flags)
107 else:
108 return None
94109
95 def recv(self, bufsize, flags=None):110 def recv(self, bufsize, flags=None):
96 if not self.blocks:111 if not self.blocks:
@@ -108,15 +123,21 @@ class TestNetstringRecv(TestCase):
108123
109 def test_nondigit(self):124 def test_nondigit(self):
110 sock = MockSocket([b'zzz:abc,'])125 sock = MockSocket([b'zzz:abc,'])
111 self.assertRaises(AssertionError, hook.netstring_recv, sock)126 self.assertRaisesRegex(
127 ValueError, "Invalid response: zzz:abc,",
128 hook.netstring_recv, sock)
112129
113 def test_short(self):130 def test_short(self):
114 sock = MockSocket([b'4:abc,'])131 sock = MockSocket([b'4:abc,'])
115 self.assertRaises(AssertionError, hook.netstring_recv, sock)132 self.assertRaisesRegex(
133 ValueError, "Length error for message 'abc,': ending=''",
134 hook.netstring_recv, sock)
116135
117 def test_unterminated(self):136 def test_unterminated(self):
118 sock = MockSocket([b'4:abcd'])137 sock = MockSocket([b'4:abcd'])
119 self.assertRaises(AssertionError, hook.netstring_recv, sock)138 self.assertRaisesRegex(
139 ValueError, "Length error for message 'abcd': ending=''",
140 hook.netstring_recv, sock)
120141
121 def test_split_data(self):142 def test_split_data(self):
122 sock = MockSocket([b'12:abcd', b'efgh', b'ijkl,'])143 sock = MockSocket([b'12:abcd', b'efgh', b'ijkl,'])
@@ -131,6 +152,17 @@ class TestNetstringRecv(TestCase):
131 hook.netstring_recv(sock))152 hook.netstring_recv(sock))
132 self.assertEqual([b'remaining'], sock.blocks)153 self.assertEqual([b'remaining'], sock.blocks)
133154
155 def test_send_uses_sendall(self):
156 sock = MockSocket([])
157
158 # Send up to 5 bytes on each "send()" call.
159 # This is important to make sure we are using python's higher level
160 # socket.sendall() rather than socket.send().
161 sock.MAX_SEND_DATA = 5
162
163 hook.netstring_send(sock, b"some-fake-data")
164 self.assertEqual(b"14:some-fake-data,", sock._sent_data)
165
134166
135class HookTestMixin(object):167class HookTestMixin(object):
136 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)168 run_tests_with = AsynchronousDeferredRunTest.make_factory(timeout=5)

Subscribers

People subscribed via source and target branches