summaryrefslogtreecommitdiff
path: root/tests/integration
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration')
-rw-r--r--tests/integration/harness.py78
1 files changed, 78 insertions, 0 deletions
diff --git a/tests/integration/harness.py b/tests/integration/harness.py
index 7e75574..03a0561 100644
--- a/tests/integration/harness.py
+++ b/tests/integration/harness.py
@@ -197,6 +197,10 @@ class Tracker:
197 assert action == 2, "udp scrape answered action %d" % action 197 assert action == 2, "udp scrape answered action %d" % action
198 return [struct.unpack(">III", answer[8 + 12 * i : 20 + 12 * i]) for i in range(len(hashes))] 198 return [struct.unpack(">III", answer[8 + 12 * i : 20 + 12 * i]) for i in range(len(hashes))]
199 199
200 def connect(self):
201 """A connection the caller drives and closes."""
202 return Connection(self.port)
203
200 def udp_raw(self, payload, timeout=1.0): 204 def udp_raw(self, payload, timeout=1.0):
201 """Sends a packet exactly as given. Returns the answer, or None if the 205 """Sends a packet exactly as given. Returns the answer, or None if the
202 tracker chose to say nothing, which is how it treats a broken one.""" 206 tracker chose to say nothing, which is how it treats a broken one."""
@@ -212,6 +216,73 @@ class Tracker:
212 sock.close() 216 sock.close()
213 217
214 218
219class Connection:
220 """One HTTP connection the caller keeps open, which urllib will not do.
221
222 Reads exactly one reply at a time, so a test can tell "the tracker
223 answered and kept the socket" from "the tracker answered and hung up"."""
224
225 def __init__(self, port, timeout=5):
226 self.sock = socket.create_connection(("127.0.0.1", port), timeout=timeout)
227 self.sock.settimeout(timeout)
228 self.buffer = b""
229
230 def send(self, path, connection="Keep-Alive", version="1.1"):
231 request = "GET %s HTTP/%s\r\nHost: tracker\r\n" % (path, version)
232 if connection is not None:
233 request += "Connection: %s\r\n" % connection
234 self.sock.sendall((request + "\r\n").encode())
235
236 def _fill(self):
237 chunk = self.sock.recv(65536)
238 if not chunk:
239 raise EOFError("the tracker closed the connection")
240 self.buffer += chunk
241
242 def reply(self):
243 """The one complete reply at the front of the stream, headers and body."""
244 while b"\r\n\r\n" not in self.buffer:
245 self._fill()
246 head, rest = self.buffer.split(b"\r\n\r\n", 1)
247 headers = {}
248 for line in head.split(b"\r\n")[1:]:
249 name, _, value = line.partition(b":")
250 headers[name.strip().lower()] = value.strip()
251
252 if headers.get(b"transfer-encoding") == b"chunked":
253 while not rest.endswith(b"0\r\n\r\n"):
254 self.buffer = head + b"\r\n\r\n" + rest
255 self._fill()
256 rest = self.buffer.split(b"\r\n\r\n", 1)[1]
257 body, self.buffer = rest, b""
258 else:
259 length = int(headers.get(b"content-length", b"0"))
260 while len(rest) < length:
261 self.buffer = head + b"\r\n\r\n" + rest
262 self._fill()
263 rest = self.buffer.split(b"\r\n\r\n", 1)[1]
264 body, self.buffer = rest[:length], rest[length:]
265 return head.split(b"\r\n")[0], headers, body
266
267 def is_open(self):
268 """True if the tracker is still holding the connection."""
269 try:
270 self.sock.settimeout(0.4)
271 return self.sock.recv(1) != b""
272 except socket.timeout:
273 return True
274 except OSError:
275 return False
276 finally:
277 try:
278 self.sock.settimeout(5)
279 except OSError:
280 pass
281
282 def close(self):
283 self.sock.close()
284
285
215class TrackerTestCase(unittest.TestCase): 286class TrackerTestCase(unittest.TestCase):
216 """Gives every test its own tracker and fails it on any sanitizer output.""" 287 """Gives every test its own tracker and fails it on any sanitizer output."""
217 288
@@ -222,6 +293,13 @@ class TrackerTestCase(unittest.TestCase):
222 self.tracker = Tracker(self.config, self.files) 293 self.tracker = Tracker(self.config, self.files)
223 294
224 def tearDown(self): 295 def tearDown(self):
296 # A connection the test closed is only reaped when the event loop gets
297 # round to it, and a cookie still held at exit reads as a leak. One
298 # more request puts us behind that work in the loop's queue.
299 try:
300 self.tracker.torrent_count()
301 except Exception:
302 pass
225 complaints = self.tracker.stop() 303 complaints = self.tracker.stop()
226 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints)) 304 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints))
227 305