Skip to content

Commit 6540900

Browse files
committed
add tests for _read_from_socket
Check that the return value against the various combination of parameters this function can take (tty, stream, and demux). This commit also fixes a bug that the tests uncovered a bug in consume_socket_output. Signed-off-by: Corentin Henry <[email protected]>
1 parent 5f157bb commit 6540900

File tree

2 files changed

+91
-23
lines changed

2 files changed

+91
-23
lines changed

docker/utils/socket.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,17 @@ def consume_socket_output(frames, demux=False):
136136
# we just need to concatenate.
137137
return six.binary_type().join(frames)
138138

139-
# If the streams are demultiplexed, the generator returns tuples
140-
# (stdin, stdout, stderr)
139+
# If the streams are demultiplexed, the generator yields tuples
140+
# (stdout, stderr)
141141
out = [six.binary_type(), six.binary_type()]
142142
for frame in frames:
143-
for stream_id in [STDOUT, STDERR]:
144-
# It is guaranteed that for each frame, one and only one stream
145-
# is not None.
146-
if frame[stream_id] is not None:
147-
out[stream_id] += frame[stream_id]
143+
# It is guaranteed that for each frame, one and only one stream
144+
# is not None.
145+
assert frame != (None, None)
146+
if frame[0] is not None:
147+
out[0] += frame[0]
148+
else:
149+
out[1] += frame[1]
148150
return tuple(out)
149151

150152

tests/unit/api_test.py

Lines changed: 82 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import requests
1616
from requests.packages import urllib3
1717
import six
18+
import struct
1819

1920
from . import fake_api
2021

@@ -467,56 +468,121 @@ def test_early_stream_response(self):
467468

468469

469470
class TCPSocketStreamTest(unittest.TestCase):
470-
text_data = b'''
471+
stdout_data = b'''
471472
Now, those children out there, they're jumping through the
472473
flames in the hope that the god of the fire will make them fruitful.
473474
Really, you can't blame them. After all, what girl would not prefer the
474475
child of a god to that of some acne-scarred artisan?
475476
'''
477+
stderr_data = b'''
478+
And what of the true God? To whose glory churches and monasteries have been
479+
built on these islands for generations past? Now shall what of Him?
480+
'''
476481

477482
def setUp(self):
478-
479483
self.server = six.moves.socketserver.ThreadingTCPServer(
480-
('', 0), self.get_handler_class()
481-
)
484+
('', 0), self.get_handler_class())
482485
self.thread = threading.Thread(target=self.server.serve_forever)
483486
self.thread.setDaemon(True)
484487
self.thread.start()
485488
self.address = 'http://{}:{}'.format(
486-
socket.gethostname(), self.server.server_address[1]
487-
)
489+
socket.gethostname(), self.server.server_address[1])
488490

489491
def tearDown(self):
490492
self.server.shutdown()
491493
self.server.server_close()
492494
self.thread.join()
493495

494496
def get_handler_class(self):
495-
text_data = self.text_data
497+
stdout_data = self.stdout_data
498+
stderr_data = self.stderr_data
496499

497500
class Handler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler, object):
498501
def do_POST(self):
502+
resp_data = self.get_resp_data()
499503
self.send_response(101)
500504
self.send_header(
501-
'Content-Type', 'application/vnd.docker.raw-stream'
502-
)
505+
'Content-Type', 'application/vnd.docker.raw-stream')
503506
self.send_header('Connection', 'Upgrade')
504507
self.send_header('Upgrade', 'tcp')
505508
self.end_headers()
506509
self.wfile.flush()
507510
time.sleep(0.2)
508-
self.wfile.write(text_data)
511+
self.wfile.write(resp_data)
509512
self.wfile.flush()
510513

514+
def get_resp_data(self):
515+
path = self.path.split('/')[-1]
516+
if path == 'tty':
517+
return stdout_data + stderr_data
518+
elif path == 'no-tty':
519+
data = b''
520+
data += self.frame_header(1, stdout_data)
521+
data += stdout_data
522+
data += self.frame_header(2, stderr_data)
523+
data += stderr_data
524+
return data
525+
else:
526+
raise Exception('Unknown path {0}'.format(path))
527+
528+
@staticmethod
529+
def frame_header(stream, data):
530+
return struct.pack('>BxxxL', stream, len(data))
531+
511532
return Handler
512533

513-
def test_read_from_socket(self):
534+
def request(self, stream=None, tty=None, demux=None):
535+
assert stream is not None and tty is not None and demux is not None
514536
with APIClient(base_url=self.address) as client:
515-
resp = client._post(client._url('/dummy'), stream=True)
516-
data = client._read_from_socket(resp, stream=True, tty=True)
517-
results = b''.join(data)
518-
519-
assert results == self.text_data
537+
if tty:
538+
url = client._url('/tty')
539+
else:
540+
url = client._url('/no-tty')
541+
resp = client._post(url, stream=True)
542+
return client._read_from_socket(
543+
resp, stream=stream, tty=tty, demux=demux)
544+
545+
def test_read_from_socket_1(self):
546+
res = self.request(stream=True, tty=True, demux=False)
547+
assert next(res) == self.stdout_data + self.stderr_data
548+
with self.assertRaises(StopIteration):
549+
next(res)
550+
551+
def test_read_from_socket_2(self):
552+
res = self.request(stream=True, tty=True, demux=True)
553+
assert next(res) == (self.stdout_data + self.stderr_data, None)
554+
with self.assertRaises(StopIteration):
555+
next(res)
556+
557+
def test_read_from_socket_3(self):
558+
res = self.request(stream=True, tty=False, demux=False)
559+
assert next(res) == self.stdout_data
560+
assert next(res) == self.stderr_data
561+
with self.assertRaises(StopIteration):
562+
next(res)
563+
564+
def test_read_from_socket_4(self):
565+
res = self.request(stream=True, tty=False, demux=True)
566+
assert (self.stdout_data, None) == next(res)
567+
assert (None, self.stderr_data) == next(res)
568+
with self.assertRaises(StopIteration):
569+
next(res)
570+
571+
def test_read_from_socket_5(self):
572+
res = self.request(stream=False, tty=True, demux=False)
573+
assert res == self.stdout_data + self.stderr_data
574+
575+
def test_read_from_socket_6(self):
576+
res = self.request(stream=False, tty=True, demux=True)
577+
assert res == (self.stdout_data + self.stderr_data, b'')
578+
579+
def test_read_from_socket_7(self):
580+
res = self.request(stream=False, tty=False, demux=False)
581+
res == self.stdout_data + self.stderr_data
582+
583+
def test_read_from_socket_8(self):
584+
res = self.request(stream=False, tty=False, demux=True)
585+
assert res == (self.stdout_data, self.stderr_data)
520586

521587

522588
class UserAgentTest(unittest.TestCase):

0 commit comments

Comments
 (0)