diff options
Diffstat (limited to 'tests/unit')
26 files changed, 841 insertions, 431 deletions
diff --git a/tests/unit/api_container_test.py b/tests/unit/api_container_test.py index a7e183c..3a2fbde 100644 --- a/tests/unit/api_container_test.py +++ b/tests/unit/api_container_test.py @@ -1,25 +1,19 @@ -# -*- coding: utf-8 -*- - import datetime import json import signal import docker +from docker.api import APIClient +from unittest import mock import pytest -import six from . import fake_api from ..helpers import requires_api_version from .api_test import ( BaseAPIClientTest, url_prefix, fake_request, DEFAULT_TIMEOUT_SECONDS, - fake_inspect_container + fake_inspect_container, url_base ) -try: - from unittest import mock -except ImportError: - import mock - def fake_inspect_container_tty(self, container): return fake_inspect_container(self, container, tty=True) @@ -30,7 +24,8 @@ class StartContainerTest(BaseAPIClientTest): self.client.start(fake_api.FAKE_CONTAINER_ID) args = fake_request.call_args - assert args[0][1] == url_prefix + 'containers/3cc2351ab11b/start' + assert args[0][1] == (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/start') assert 'data' not in args[1] assert args[1]['timeout'] == DEFAULT_TIMEOUT_SECONDS @@ -123,7 +118,8 @@ class StartContainerTest(BaseAPIClientTest): self.client.start({'Id': fake_api.FAKE_CONTAINER_ID}) args = fake_request.call_args - assert args[0][1] == url_prefix + 'containers/3cc2351ab11b/start' + assert args[0][1] == (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/start') assert 'data' not in args[1] assert args[1]['timeout'] == DEFAULT_TIMEOUT_SECONDS @@ -352,6 +348,22 @@ class CreateContainerTest(BaseAPIClientTest): assert args[1]['headers'] == {'Content-Type': 'application/json'} assert args[1]['params'] == {'name': 'marisa-kirisame'} + def test_create_container_with_platform(self): + self.client.create_container('busybox', 'true', + platform='linux') + + args = fake_request.call_args + assert args[0][1] == url_prefix + 'containers/create' + assert json.loads(args[1]['data']) == json.loads(''' + {"Tty": false, "Image": "busybox", "Cmd": ["true"], + "AttachStdin": false, + "AttachStderr": true, "AttachStdout": true, + "StdinOnce": false, + "OpenStdin": false, "NetworkDisabled": false} + ''') + assert args[1]['headers'] == {'Content-Type': 'application/json'} + assert args[1]['params'] == {'name': None, 'platform': 'linux'} + def test_create_container_with_mem_limit_as_int(self): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( @@ -767,10 +779,71 @@ class CreateContainerTest(BaseAPIClientTest): assert args[1]['headers'] == {'Content-Type': 'application/json'} assert args[1]['timeout'] == DEFAULT_TIMEOUT_SECONDS + def test_create_container_with_device_requests(self): + client = APIClient(version='1.40') + fake_api.fake_responses.setdefault( + f'{fake_api.prefix}/v1.40/containers/create', + fake_api.post_fake_create_container, + ) + client.create_container( + 'busybox', 'true', host_config=client.create_host_config( + device_requests=[ + { + 'device_ids': [ + '0', + 'GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a' + ] + }, + { + 'driver': 'nvidia', + 'Count': -1, + 'capabilities': [ + ['gpu', 'utility'] + ], + 'options': { + 'key': 'value' + } + } + ] + ) + ) + + args = fake_request.call_args + assert args[0][1] == url_base + 'v1.40/' + 'containers/create' + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = client.create_host_config() + expected_payload['HostConfig']['DeviceRequests'] = [ + { + 'Driver': '', + 'Count': 0, + 'DeviceIDs': [ + '0', + 'GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a' + ], + 'Capabilities': [], + 'Options': {} + }, + { + 'Driver': 'nvidia', + 'Count': -1, + 'DeviceIDs': [], + 'Capabilities': [ + ['gpu', 'utility'] + ], + 'Options': { + 'key': 'value' + } + } + ] + assert json.loads(args[1]['data']) == expected_payload + assert args[1]['headers']['Content-Type'] == 'application/json' + assert set(args[1]['headers']) <= {'Content-Type', 'User-Agent'} + assert args[1]['timeout'] == DEFAULT_TIMEOUT_SECONDS + def test_create_container_with_labels_dict(self): labels_dict = { - six.text_type('foo'): six.text_type('1'), - six.text_type('bar'): six.text_type('2'), + 'foo': '1', + 'bar': '2', } self.client.create_container( @@ -786,12 +859,12 @@ class CreateContainerTest(BaseAPIClientTest): def test_create_container_with_labels_list(self): labels_list = [ - six.text_type('foo'), - six.text_type('bar'), + 'foo', + 'bar', ] labels_dict = { - six.text_type('foo'): six.text_type(), - six.text_type('bar'): six.text_type(), + 'foo': '', + 'bar': '', } self.client.create_container( @@ -951,11 +1024,11 @@ class CreateContainerTest(BaseAPIClientTest): def test_create_container_with_unicode_envvars(self): envvars_dict = { - 'foo': u'☃', + 'foo': '☃', } expected = [ - u'foo=☃' + 'foo=☃' ] self.client.create_container( @@ -1024,7 +1097,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/resize', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/resize'), params={'h': 15, 'w': 120}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1037,7 +1111,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/rename', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/rename'), params={'name': 'foobar'}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1047,7 +1122,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/wait', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/wait', timeout=None, params={} ) @@ -1057,7 +1132,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/wait', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/wait', timeout=None, params={} ) @@ -1069,14 +1144,14 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=False ) - assert logs == 'Flowering Nights\n(Sakuya Iyazoi)\n'.encode('ascii') + assert logs == b'Flowering Nights\n(Sakuya Iyazoi)\n' def test_logs_with_dict_instead_of_id(self): with mock.patch('docker.api.client.APIClient.inspect_container', @@ -1085,14 +1160,14 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, stream=False ) - assert logs == 'Flowering Nights\n(Sakuya Iyazoi)\n'.encode('ascii') + assert logs == b'Flowering Nights\n(Sakuya Iyazoi)\n' def test_log_streaming(self): with mock.patch('docker.api.client.APIClient.inspect_container', @@ -1102,7 +1177,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, @@ -1117,7 +1192,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, @@ -1131,7 +1206,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, @@ -1146,7 +1221,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, @@ -1162,7 +1237,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 10}, timeout=DEFAULT_TIMEOUT_SECONDS, @@ -1178,7 +1253,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all', 'since': ts}, timeout=DEFAULT_TIMEOUT_SECONDS, @@ -1195,7 +1270,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, 'tail': 'all', 'since': ts}, timeout=DEFAULT_TIMEOUT_SECONDS, @@ -1221,7 +1296,7 @@ class ContainerTest(BaseAPIClientTest): assert m.called fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/logs', params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, 'tail': 'all'}, timeout=DEFAULT_TIMEOUT_SECONDS, @@ -1233,7 +1308,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/changes', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/changes'), timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1242,7 +1318,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/changes', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/changes'), timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1251,7 +1328,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/json', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/json', timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1262,7 +1339,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/stop', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/stop', params={'t': timeout}, timeout=(DEFAULT_TIMEOUT_SECONDS + timeout) ) @@ -1275,7 +1352,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/stop', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/stop', params={'t': timeout}, timeout=(DEFAULT_TIMEOUT_SECONDS + timeout) ) @@ -1285,7 +1362,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/pause', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/pause'), timeout=(DEFAULT_TIMEOUT_SECONDS) ) @@ -1294,7 +1372,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/unpause', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/unpause'), timeout=(DEFAULT_TIMEOUT_SECONDS) ) @@ -1303,7 +1382,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/kill', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/kill', params={}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1313,7 +1392,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/kill', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/kill', params={}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1323,7 +1402,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/kill', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/kill', params={'signal': signal.SIGTERM}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1333,7 +1412,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/restart', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/restart'), params={'t': 2}, timeout=(DEFAULT_TIMEOUT_SECONDS + 2) ) @@ -1343,7 +1423,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'containers/3cc2351ab11b/restart', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/restart'), params={'t': 2}, timeout=(DEFAULT_TIMEOUT_SECONDS + 2) ) @@ -1353,7 +1434,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'DELETE', - url_prefix + 'containers/3cc2351ab11b', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID, params={'v': False, 'link': False, 'force': False}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1363,7 +1444,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'DELETE', - url_prefix + 'containers/3cc2351ab11b', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID, params={'v': False, 'link': False, 'force': False}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1373,7 +1454,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/export', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/export'), stream=True, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1383,7 +1465,8 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/export', + (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/export'), stream=True, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1393,7 +1476,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/json', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/json', timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1409,7 +1492,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/stats', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/stats', timeout=60, stream=True ) @@ -1419,7 +1502,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/top', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/top', params={}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1429,7 +1512,7 @@ class ContainerTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'containers/3cc2351ab11b/top', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID + '/top', params={'ps_args': 'waux'}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -1441,7 +1524,8 @@ class ContainerTest(BaseAPIClientTest): blkio_weight=345 ) args = fake_request.call_args - assert args[0][1] == url_prefix + 'containers/3cc2351ab11b/update' + assert args[0][1] == (url_prefix + 'containers/' + + fake_api.FAKE_CONTAINER_ID + '/update') assert json.loads(args[1]['data']) == { 'Memory': 2 * 1024, 'CpuShares': 124, 'BlkioWeight': 345 } diff --git a/tests/unit/api_exec_test.py b/tests/unit/api_exec_test.py index a9d2dd5..4504250 100644 --- a/tests/unit/api_exec_test.py +++ b/tests/unit/api_exec_test.py @@ -11,7 +11,7 @@ class ExecTest(BaseAPIClientTest): self.client.exec_create(fake_api.FAKE_CONTAINER_ID, ['ls', '-1']) args = fake_request.call_args - assert 'POST' == args[0][0], url_prefix + 'containers/{0}/exec'.format( + assert 'POST' == args[0][0], url_prefix + 'containers/{}/exec'.format( fake_api.FAKE_CONTAINER_ID ) @@ -32,7 +32,7 @@ class ExecTest(BaseAPIClientTest): self.client.exec_start(fake_api.FAKE_EXEC_ID) args = fake_request.call_args - assert args[0][1] == url_prefix + 'exec/{0}/start'.format( + assert args[0][1] == url_prefix + 'exec/{}/start'.format( fake_api.FAKE_EXEC_ID ) @@ -51,7 +51,7 @@ class ExecTest(BaseAPIClientTest): self.client.exec_start(fake_api.FAKE_EXEC_ID, detach=True) args = fake_request.call_args - assert args[0][1] == url_prefix + 'exec/{0}/start'.format( + assert args[0][1] == url_prefix + 'exec/{}/start'.format( fake_api.FAKE_EXEC_ID ) @@ -68,7 +68,7 @@ class ExecTest(BaseAPIClientTest): self.client.exec_inspect(fake_api.FAKE_EXEC_ID) args = fake_request.call_args - assert args[0][1] == url_prefix + 'exec/{0}/json'.format( + assert args[0][1] == url_prefix + 'exec/{}/json'.format( fake_api.FAKE_EXEC_ID ) @@ -77,7 +77,7 @@ class ExecTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'exec/{0}/resize'.format(fake_api.FAKE_EXEC_ID), + url_prefix + f'exec/{fake_api.FAKE_EXEC_ID}/resize', params={'h': 20, 'w': 60}, timeout=DEFAULT_TIMEOUT_SECONDS ) diff --git a/tests/unit/api_image_test.py b/tests/unit/api_image_test.py index 1e2315d..e285932 100644 --- a/tests/unit/api_image_test.py +++ b/tests/unit/api_image_test.py @@ -3,16 +3,12 @@ import pytest from . import fake_api from docker import auth +from unittest import mock from .api_test import ( BaseAPIClientTest, fake_request, DEFAULT_TIMEOUT_SECONDS, url_prefix, fake_resolve_authconfig ) -try: - from unittest import mock -except ImportError: - import mock - class ImageTest(BaseAPIClientTest): def test_image_viz(self): @@ -26,7 +22,18 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', url_prefix + 'images/json', - params={'filter': None, 'only_ids': 0, 'all': 1}, + params={'only_ids': 0, 'all': 1}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_images_name(self): + self.client.images('foo:bar') + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/json', + params={'only_ids': 0, 'all': 0, + 'filters': '{"reference": ["foo:bar"]}'}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -36,7 +43,7 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', url_prefix + 'images/json', - params={'filter': None, 'only_ids': 1, 'all': 1}, + params={'only_ids': 1, 'all': 1}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -46,7 +53,7 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', url_prefix + 'images/json', - params={'filter': None, 'only_ids': 1, 'all': 0}, + params={'only_ids': 1, 'all': 0}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -56,7 +63,7 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', url_prefix + 'images/json', - params={'filter': None, 'only_ids': 0, 'all': 0, + params={'only_ids': 0, 'all': 0, 'filters': '{"dangling": ["true"]}'}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -67,7 +74,7 @@ class ImageTest(BaseAPIClientTest): args = fake_request.call_args assert args[0][1] == url_prefix + 'images/create' assert args[1]['params'] == { - 'tag': None, 'fromImage': 'joffrey/test001' + 'tag': 'latest', 'fromImage': 'joffrey/test001' } assert not args[1]['stream'] @@ -77,7 +84,7 @@ class ImageTest(BaseAPIClientTest): args = fake_request.call_args assert args[0][1] == url_prefix + 'images/create' assert args[1]['params'] == { - 'tag': None, 'fromImage': 'joffrey/test001' + 'tag': 'latest', 'fromImage': 'joffrey/test001' } assert args[1]['stream'] @@ -93,7 +100,7 @@ class ImageTest(BaseAPIClientTest): 'repo': None, 'comment': None, 'tag': None, - 'container': '3cc2351ab11b', + 'container': fake_api.FAKE_CONTAINER_ID, 'author': None, 'changes': None }, @@ -105,7 +112,7 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'DELETE', - url_prefix + 'images/e9aa60c60128', + url_prefix + 'images/' + fake_api.FAKE_IMAGE_ID, params={'force': False, 'noprune': False}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -280,7 +287,7 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'images/e9aa60c60128/tag', + url_prefix + 'images/' + fake_api.FAKE_IMAGE_ID + '/tag', params={ 'tag': None, 'repo': 'repo', @@ -298,7 +305,7 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'images/e9aa60c60128/tag', + url_prefix + 'images/' + fake_api.FAKE_IMAGE_ID + '/tag', params={ 'tag': 'tag', 'repo': 'repo', @@ -313,7 +320,7 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'POST', - url_prefix + 'images/e9aa60c60128/tag', + url_prefix + 'images/' + fake_api.FAKE_IMAGE_ID + '/tag', params={ 'tag': None, 'repo': 'repo', @@ -327,7 +334,7 @@ class ImageTest(BaseAPIClientTest): fake_request.assert_called_with( 'GET', - url_prefix + 'images/e9aa60c60128/get', + url_prefix + 'images/' + fake_api.FAKE_IMAGE_ID + '/get', stream=True, timeout=DEFAULT_TIMEOUT_SECONDS ) diff --git a/tests/unit/api_network_test.py b/tests/unit/api_network_test.py index c78554d..8afab73 100644 --- a/tests/unit/api_network_test.py +++ b/tests/unit/api_network_test.py @@ -1,14 +1,8 @@ import json -import six - from .api_test import BaseAPIClientTest, url_prefix, response from docker.types import IPAMConfig, IPAMPool - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock class NetworkTest(BaseAPIClientTest): @@ -103,16 +97,16 @@ class NetworkTest(BaseAPIClientTest): self.client.remove_network(network_id) args = delete.call_args - assert args[0][0] == url_prefix + 'networks/{0}'.format(network_id) + assert args[0][0] == url_prefix + f'networks/{network_id}' def test_inspect_network(self): network_id = 'abc12345' network_name = 'foo' network_data = { - six.u('name'): network_name, - six.u('id'): network_id, - six.u('driver'): 'bridge', - six.u('containers'): {}, + 'name': network_name, + 'id': network_id, + 'driver': 'bridge', + 'containers': {}, } network_response = response(status_code=200, content=network_data) @@ -123,7 +117,7 @@ class NetworkTest(BaseAPIClientTest): assert result == network_data args = get.call_args - assert args[0][0] == url_prefix + 'networks/{0}'.format(network_id) + assert args[0][0] == url_prefix + f'networks/{network_id}' def test_connect_container_to_network(self): network_id = 'abc12345' @@ -136,11 +130,12 @@ class NetworkTest(BaseAPIClientTest): container={'Id': container_id}, net_id=network_id, aliases=['foo', 'bar'], - links=[('baz', 'quux')] + links=[('baz', 'quux')], + driver_opt={'com.docker-py.setting': 'yes'}, ) assert post.call_args[0][0] == ( - url_prefix + 'networks/{0}/connect'.format(network_id) + url_prefix + f'networks/{network_id}/connect' ) assert json.loads(post.call_args[1]['data']) == { @@ -148,6 +143,7 @@ class NetworkTest(BaseAPIClientTest): 'EndpointConfig': { 'Aliases': ['foo', 'bar'], 'Links': ['baz:quux'], + 'DriverOpts': {'com.docker-py.setting': 'yes'}, }, } @@ -162,7 +158,7 @@ class NetworkTest(BaseAPIClientTest): container={'Id': container_id}, net_id=network_id) assert post.call_args[0][0] == ( - url_prefix + 'networks/{0}/disconnect'.format(network_id) + url_prefix + f'networks/{network_id}/disconnect' ) assert json.loads(post.call_args[1]['data']) == { 'Container': container_id diff --git a/tests/unit/api_test.py b/tests/unit/api_test.py index f4d220a..a2348f0 100644 --- a/tests/unit/api_test.py +++ b/tests/unit/api_test.py @@ -1,31 +1,28 @@ import datetime -import json import io +import json import os import re import shutil import socket +import struct import tempfile import threading import time import unittest +import socketserver +import http.server import docker -from docker.api import APIClient +import pytest import requests +from docker.api import APIClient +from docker.constants import DEFAULT_DOCKER_API_VERSION from requests.packages import urllib3 -import six -import struct +from unittest import mock from . import fake_api -import pytest - -try: - from unittest import mock -except ImportError: - import mock - DEFAULT_TIMEOUT_SECONDS = docker.constants.DEFAULT_TIMEOUT_SECONDS @@ -34,7 +31,7 @@ def response(status_code=200, content='', headers=None, reason=None, elapsed=0, request=None, raw=None): res = requests.Response() res.status_code = status_code - if not isinstance(content, six.binary_type): + if not isinstance(content, bytes): content = json.dumps(content).encode('ascii') res._content = content res.headers = requests.structures.CaseInsensitiveDict(headers or {}) @@ -60,7 +57,7 @@ def fake_resp(method, url, *args, **kwargs): elif (url, method) in fake_api.fake_responses: key = (url, method) if not key: - raise Exception('{0} {1}'.format(method, url)) + raise Exception(f'{method} {url}') status_code, content = fake_api.fake_responses[key]() return response(status_code=status_code, content=content) @@ -85,11 +82,11 @@ def fake_delete(self, url, *args, **kwargs): def fake_read_from_socket(self, response, stream, tty=False, demux=False): - return six.binary_type() + return bytes() -url_base = '{0}/'.format(fake_api.prefix) -url_prefix = '{0}v{1}/'.format( +url_base = f'{fake_api.prefix}/' +url_prefix = '{}v{}/'.format( url_base, docker.constants.DEFAULT_DOCKER_API_VERSION) @@ -105,7 +102,7 @@ class BaseAPIClientTest(unittest.TestCase): _read_from_socket=fake_read_from_socket ) self.patcher.start() - self.client = APIClient() + self.client = APIClient(version=DEFAULT_DOCKER_API_VERSION) def tearDown(self): self.client.close() @@ -133,20 +130,20 @@ class DockerApiTest(BaseAPIClientTest): def test_url_valid_resource(self): url = self.client._url('/hello/{0}/world', 'somename') - assert url == '{0}{1}'.format(url_prefix, 'hello/somename/world') + assert url == '{}{}'.format(url_prefix, 'hello/somename/world') url = self.client._url( '/hello/{0}/world/{1}', 'somename', 'someothername' ) - assert url == '{0}{1}'.format( + assert url == '{}{}'.format( url_prefix, 'hello/somename/world/someothername' ) url = self.client._url('/hello/{0}/world', 'some?name') - assert url == '{0}{1}'.format(url_prefix, 'hello/some%3Fname/world') + assert url == '{}{}'.format(url_prefix, 'hello/some%3Fname/world') url = self.client._url("/images/{0}/push", "localhost:5000/image") - assert url == '{0}{1}'.format( + assert url == '{}{}'.format( url_prefix, 'images/localhost:5000/image/push' ) @@ -156,13 +153,13 @@ class DockerApiTest(BaseAPIClientTest): def test_url_no_resource(self): url = self.client._url('/simple') - assert url == '{0}{1}'.format(url_prefix, 'simple') + assert url == '{}{}'.format(url_prefix, 'simple') def test_url_unversioned_api(self): url = self.client._url( '/hello/{0}/world', 'somename', versioned_api=False ) - assert url == '{0}{1}'.format(url_base, 'hello/somename/world') + assert url == '{}{}'.format(url_base, 'hello/somename/world') def test_version(self): self.client.version() @@ -184,13 +181,13 @@ class DockerApiTest(BaseAPIClientTest): def test_retrieve_server_version(self): client = APIClient(version="auto") - assert isinstance(client._version, six.string_types) + assert isinstance(client._version, str) assert not (client._version == "auto") client.close() def test_auto_retrieve_server_version(self): version = self.client._retrieve_server_version() - assert isinstance(version, six.string_types) + assert isinstance(version, str) def test_info(self): self.client.info() @@ -282,27 +279,37 @@ class DockerApiTest(BaseAPIClientTest): return socket_adapter.socket_path def test_url_compatibility_unix(self): - c = APIClient(base_url="unix://socket") + c = APIClient( + base_url="unix://socket", + version=DEFAULT_DOCKER_API_VERSION) assert self._socket_path_for_client_session(c) == '/socket' def test_url_compatibility_unix_triple_slash(self): - c = APIClient(base_url="unix:///socket") + c = APIClient( + base_url="unix:///socket", + version=DEFAULT_DOCKER_API_VERSION) assert self._socket_path_for_client_session(c) == '/socket' def test_url_compatibility_http_unix_triple_slash(self): - c = APIClient(base_url="http+unix:///socket") + c = APIClient( + base_url="http+unix:///socket", + version=DEFAULT_DOCKER_API_VERSION) assert self._socket_path_for_client_session(c) == '/socket' def test_url_compatibility_http(self): - c = APIClient(base_url="http://hostname:1234") + c = APIClient( + base_url="http://hostname:1234", + version=DEFAULT_DOCKER_API_VERSION) assert c.base_url == "http://hostname:1234" def test_url_compatibility_tcp(self): - c = APIClient(base_url="tcp://hostname:1234") + c = APIClient( + base_url="tcp://hostname:1234", + version=DEFAULT_DOCKER_API_VERSION) assert c.base_url == "http://hostname:1234" @@ -311,7 +318,7 @@ class DockerApiTest(BaseAPIClientTest): fake_request.assert_called_with( 'DELETE', - url_prefix + 'containers/3cc2351ab11b', + url_prefix + 'containers/' + fake_api.FAKE_CONTAINER_ID, params={'v': False, 'link': True, 'force': False}, timeout=DEFAULT_TIMEOUT_SECONDS ) @@ -327,8 +334,7 @@ class DockerApiTest(BaseAPIClientTest): def test_stream_helper_decoding(self): status_code, content = fake_api.fake_responses[url_prefix + 'events']() content_str = json.dumps(content) - if six.PY3: - content_str = content_str.encode('utf-8') + content_str = content_str.encode('utf-8') body = io.BytesIO(content_str) # mock a stream interface @@ -372,7 +378,7 @@ class UnixSocketStreamTest(unittest.TestCase): self.server_socket = self._setup_socket() self.stop_server = False server_thread = threading.Thread(target=self.run_server) - server_thread.setDaemon(True) + server_thread.daemon = True server_thread.start() self.response = None self.request_handler = None @@ -395,7 +401,7 @@ class UnixSocketStreamTest(unittest.TestCase): while not self.stop_server: try: connection, client_address = self.server_socket.accept() - except socket.error: + except OSError: # Probably no connection to accept yet time.sleep(0.01) continue @@ -447,7 +453,9 @@ class UnixSocketStreamTest(unittest.TestCase): b'\r\n' ) + b'\r\n'.join(lines) - with APIClient(base_url="http+unix://" + self.socket_file) as client: + with APIClient( + base_url="http+unix://" + self.socket_file, + version=DEFAULT_DOCKER_API_VERSION) as client: for i in range(5): try: stream = client.build( @@ -477,10 +485,10 @@ class TCPSocketStreamTest(unittest.TestCase): @classmethod def setup_class(cls): - cls.server = six.moves.socketserver.ThreadingTCPServer( + cls.server = socketserver.ThreadingTCPServer( ('', 0), cls.get_handler_class()) cls.thread = threading.Thread(target=cls.server.serve_forever) - cls.thread.setDaemon(True) + cls.thread.daemon = True cls.thread.start() cls.address = 'http://{}:{}'.format( socket.gethostname(), cls.server.server_address[1]) @@ -496,7 +504,7 @@ class TCPSocketStreamTest(unittest.TestCase): stdout_data = cls.stdout_data stderr_data = cls.stderr_data - class Handler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler, object): + class Handler(http.server.BaseHTTPRequestHandler): def do_POST(self): resp_data = self.get_resp_data() self.send_response(101) @@ -522,7 +530,7 @@ class TCPSocketStreamTest(unittest.TestCase): data += stderr_data return data else: - raise Exception('Unknown path {0}'.format(path)) + raise Exception(f'Unknown path {path}') @staticmethod def frame_header(stream, data): @@ -532,7 +540,10 @@ class TCPSocketStreamTest(unittest.TestCase): def request(self, stream=None, tty=None, demux=None): assert stream is not None and tty is not None and demux is not None - with APIClient(base_url=self.address) as client: + with APIClient( + base_url=self.address, + version=DEFAULT_DOCKER_API_VERSION + ) as client: if tty: url = client._url('/tty') else: @@ -597,7 +608,7 @@ class UserAgentTest(unittest.TestCase): self.patcher.stop() def test_default_user_agent(self): - client = APIClient() + client = APIClient(version=DEFAULT_DOCKER_API_VERSION) client.version() assert self.mock_send.call_count == 1 @@ -606,7 +617,9 @@ class UserAgentTest(unittest.TestCase): assert headers['User-Agent'] == expected def test_custom_user_agent(self): - client = APIClient(user_agent='foo/bar') + client = APIClient( + user_agent='foo/bar', + version=DEFAULT_DOCKER_API_VERSION) client.version() assert self.mock_send.call_count == 1 @@ -615,7 +628,7 @@ class UserAgentTest(unittest.TestCase): class DisableSocketTest(unittest.TestCase): - class DummySocket(object): + class DummySocket: def __init__(self, timeout=60): self.timeout = timeout @@ -626,7 +639,7 @@ class DisableSocketTest(unittest.TestCase): return self.timeout def setUp(self): - self.client = APIClient() + self.client = APIClient(version=DEFAULT_DOCKER_API_VERSION) def test_disable_socket_timeout(self): """Test that the timeout is disabled on a generic socket object.""" diff --git a/tests/unit/api_volume_test.py b/tests/unit/api_volume_test.py index 7850c22..a8d9193 100644 --- a/tests/unit/api_volume_test.py +++ b/tests/unit/api_volume_test.py @@ -104,7 +104,7 @@ class VolumeTest(BaseAPIClientTest): args = fake_request.call_args assert args[0][0] == 'GET' - assert args[0][1] == '{0}volumes/{1}'.format(url_prefix, name) + assert args[0][1] == f'{url_prefix}volumes/{name}' def test_remove_volume(self): name = 'perfectcherryblossom' @@ -112,4 +112,4 @@ class VolumeTest(BaseAPIClientTest): args = fake_request.call_args assert args[0][0] == 'DELETE' - assert args[0][1] == '{0}volumes/{1}'.format(url_prefix, name) + assert args[0][1] == f'{url_prefix}volumes/{name}' diff --git a/tests/unit/auth_test.py b/tests/unit/auth_test.py index aac8910..dd5b5f8 100644 --- a/tests/unit/auth_test.py +++ b/tests/unit/auth_test.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import base64 import json import os @@ -10,13 +8,9 @@ import tempfile import unittest from docker import auth, credentials, errors +from unittest import mock import pytest -try: - from unittest import mock -except ImportError: - import mock - class RegressionTest(unittest.TestCase): def test_803_urlsafe_encode(self): @@ -239,7 +233,7 @@ class LoadConfigTest(unittest.TestCase): cfg_path = os.path.join(folder, '.dockercfg') auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') with open(cfg_path, 'w') as f: - f.write('auth = {0}\n'.format(auth_)) + f.write(f'auth = {auth_}\n') f.write('email = sakuya@scarlet.net') cfg = auth.load_config(cfg_path) @@ -297,13 +291,13 @@ class LoadConfigTest(unittest.TestCase): self.addCleanup(shutil.rmtree, folder) dockercfg_path = os.path.join(folder, - '.{0}.dockercfg'.format( + '.{}.dockercfg'.format( random.randrange(100000))) registry = 'https://your.private.registry.io' auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') config = { registry: { - 'auth': '{0}'.format(auth_), + 'auth': f'{auth_}', 'email': 'sakuya@scarlet.net' } } @@ -329,7 +323,7 @@ class LoadConfigTest(unittest.TestCase): auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') config = { registry: { - 'auth': '{0}'.format(auth_), + 'auth': f'{auth_}', 'email': 'sakuya@scarlet.net' } } @@ -357,7 +351,7 @@ class LoadConfigTest(unittest.TestCase): config = { 'auths': { registry: { - 'auth': '{0}'.format(auth_), + 'auth': f'{auth_}', 'email': 'sakuya@scarlet.net' } } @@ -386,7 +380,7 @@ class LoadConfigTest(unittest.TestCase): config = { 'auths': { registry: { - 'auth': '{0}'.format(auth_), + 'auth': f'{auth_}', 'email': 'sakuya@scarlet.net' } } @@ -794,9 +788,9 @@ class InMemoryStore(credentials.Store): } def list(self): - return dict( - [(k, v['Username']) for k, v in self.__store.items()] - ) + return { + k: v['Username'] for k, v in self.__store.items() + } def erase(self, server): del self.__store[server] diff --git a/tests/unit/client_test.py b/tests/unit/client_test.py index cce99c5..e7c7eec 100644 --- a/tests/unit/client_test.py +++ b/tests/unit/client_test.py @@ -1,22 +1,20 @@ import datetime -import docker -from docker.utils import kwargs_from_env -from docker.constants import ( - DEFAULT_DOCKER_API_VERSION, DEFAULT_TIMEOUT_SECONDS -) import os import unittest -from . import fake_api +import docker import pytest +from docker.constants import ( + DEFAULT_DOCKER_API_VERSION, DEFAULT_TIMEOUT_SECONDS, + DEFAULT_MAX_POOL_SIZE, IS_WINDOWS_PLATFORM +) +from docker.utils import kwargs_from_env +from unittest import mock -try: - from unittest import mock -except ImportError: - import mock - +from . import fake_api TEST_CERT_DIR = os.path.join(os.path.dirname(__file__), 'testdata/certs') +POOL_SIZE = 20 class ClientTest(unittest.TestCase): @@ -25,33 +23,33 @@ class ClientTest(unittest.TestCase): def test_events(self, mock_func): since = datetime.datetime(2016, 1, 1, 0, 0) mock_func.return_value = fake_api.get_fake_events()[1] - client = docker.from_env() + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) assert client.events(since=since) == mock_func.return_value mock_func.assert_called_with(since=since) @mock.patch('docker.api.APIClient.info') def test_info(self, mock_func): mock_func.return_value = fake_api.get_fake_info()[1] - client = docker.from_env() + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) assert client.info() == mock_func.return_value mock_func.assert_called_with() @mock.patch('docker.api.APIClient.ping') def test_ping(self, mock_func): mock_func.return_value = True - client = docker.from_env() + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) assert client.ping() is True mock_func.assert_called_with() @mock.patch('docker.api.APIClient.version') def test_version(self, mock_func): mock_func.return_value = fake_api.get_fake_version()[1] - client = docker.from_env() + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) assert client.version() == mock_func.return_value mock_func.assert_called_with() def test_call_api_client_method(self): - client = docker.from_env() + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) with pytest.raises(AttributeError) as cm: client.create_container() s = cm.exconly() @@ -65,7 +63,9 @@ class ClientTest(unittest.TestCase): assert "this method is now on the object APIClient" not in s def test_call_containers(self): - client = docker.DockerClient(**kwargs_from_env()) + client = docker.DockerClient( + version=DEFAULT_DOCKER_API_VERSION, + **kwargs_from_env()) with pytest.raises(TypeError) as cm: client.containers() @@ -74,6 +74,84 @@ class ClientTest(unittest.TestCase): assert "'ContainerCollection' object is not callable" in s assert "docker.APIClient" in s + @pytest.mark.skipif( + IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux' + ) + @mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool") + def test_default_pool_size_unix(self, mock_obj): + client = docker.DockerClient( + version=DEFAULT_DOCKER_API_VERSION + ) + mock_obj.return_value.urlopen.return_value.status = 200 + client.ping() + + base_url = "{base_url}/v{version}/_ping".format( + base_url=client.api.base_url, + version=client.api._version + ) + + mock_obj.assert_called_once_with(base_url, + "/var/run/docker.sock", + 60, + maxsize=DEFAULT_MAX_POOL_SIZE + ) + + @pytest.mark.skipif( + not IS_WINDOWS_PLATFORM, reason='Npipe Connection Pool only on Windows' + ) + @mock.patch("docker.transport.npipeconn.NpipeHTTPConnectionPool") + def test_default_pool_size_win(self, mock_obj): + client = docker.DockerClient( + version=DEFAULT_DOCKER_API_VERSION + ) + mock_obj.return_value.urlopen.return_value.status = 200 + client.ping() + + mock_obj.assert_called_once_with("//./pipe/docker_engine", + 60, + maxsize=DEFAULT_MAX_POOL_SIZE + ) + + @pytest.mark.skipif( + IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux' + ) + @mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool") + def test_pool_size_unix(self, mock_obj): + client = docker.DockerClient( + version=DEFAULT_DOCKER_API_VERSION, + max_pool_size=POOL_SIZE + ) + mock_obj.return_value.urlopen.return_value.status = 200 + client.ping() + + base_url = "{base_url}/v{version}/_ping".format( + base_url=client.api.base_url, + version=client.api._version + ) + + mock_obj.assert_called_once_with(base_url, + "/var/run/docker.sock", + 60, + maxsize=POOL_SIZE + ) + + @pytest.mark.skipif( + not IS_WINDOWS_PLATFORM, reason='Npipe Connection Pool only on Windows' + ) + @mock.patch("docker.transport.npipeconn.NpipeHTTPConnectionPool") + def test_pool_size_win(self, mock_obj): + client = docker.DockerClient( + version=DEFAULT_DOCKER_API_VERSION, + max_pool_size=POOL_SIZE + ) + mock_obj.return_value.urlopen.return_value.status = 200 + client.ping() + + mock_obj.assert_called_once_with("//./pipe/docker_engine", + 60, + maxsize=POOL_SIZE + ) + class FromEnvTest(unittest.TestCase): @@ -90,7 +168,7 @@ class FromEnvTest(unittest.TestCase): os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', DOCKER_CERT_PATH=TEST_CERT_DIR, DOCKER_TLS_VERIFY='1') - client = docker.from_env() + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) assert client.api.base_url == "https://192.168.59.103:2376" def test_from_env_with_version(self): @@ -102,11 +180,85 @@ class FromEnvTest(unittest.TestCase): assert client.api._version == '2.32' def test_from_env_without_version_uses_default(self): - client = docker.from_env() + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) assert client.api._version == DEFAULT_DOCKER_API_VERSION def test_from_env_without_timeout_uses_default(self): - client = docker.from_env() + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) assert client.api.timeout == DEFAULT_TIMEOUT_SECONDS + + @pytest.mark.skipif( + IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux' + ) + @mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool") + def test_default_pool_size_from_env_unix(self, mock_obj): + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + mock_obj.return_value.urlopen.return_value.status = 200 + client.ping() + + base_url = "{base_url}/v{version}/_ping".format( + base_url=client.api.base_url, + version=client.api._version + ) + + mock_obj.assert_called_once_with(base_url, + "/var/run/docker.sock", + 60, + maxsize=DEFAULT_MAX_POOL_SIZE + ) + + @pytest.mark.skipif( + not IS_WINDOWS_PLATFORM, reason='Npipe Connection Pool only on Windows' + ) + @mock.patch("docker.transport.npipeconn.NpipeHTTPConnectionPool") + def test_default_pool_size_from_env_win(self, mock_obj): + client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + mock_obj.return_value.urlopen.return_value.status = 200 + client.ping() + + mock_obj.assert_called_once_with("//./pipe/docker_engine", + 60, + maxsize=DEFAULT_MAX_POOL_SIZE + ) + + @pytest.mark.skipif( + IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux' + ) + @mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool") + def test_pool_size_from_env_unix(self, mock_obj): + client = docker.from_env( + version=DEFAULT_DOCKER_API_VERSION, + max_pool_size=POOL_SIZE + ) + mock_obj.return_value.urlopen.return_value.status = 200 + client.ping() + + base_url = "{base_url}/v{version}/_ping".format( + base_url=client.api.base_url, + version=client.api._version + ) + + mock_obj.assert_called_once_with(base_url, + "/var/run/docker.sock", + 60, + maxsize=POOL_SIZE + ) + + @pytest.mark.skipif( + not IS_WINDOWS_PLATFORM, reason='Npipe Connection Pool only on Windows' + ) + @mock.patch("docker.transport.npipeconn.NpipeHTTPConnectionPool") + def test_pool_size_from_env_win(self, mock_obj): + client = docker.from_env( + version=DEFAULT_DOCKER_API_VERSION, + max_pool_size=POOL_SIZE + ) + mock_obj.return_value.urlopen.return_value.status = 200 + client.ping() + + mock_obj.assert_called_once_with("//./pipe/docker_engine", + 60, + maxsize=POOL_SIZE + ) diff --git a/tests/unit/context_test.py b/tests/unit/context_test.py new file mode 100644 index 0000000..6d6d672 --- /dev/null +++ b/tests/unit/context_test.py @@ -0,0 +1,49 @@ +import unittest +import docker +import pytest +from docker.constants import DEFAULT_UNIX_SOCKET +from docker.constants import DEFAULT_NPIPE +from docker.constants import IS_WINDOWS_PLATFORM +from docker.context import ContextAPI, Context + + +class BaseContextTest(unittest.TestCase): + @pytest.mark.skipif( + IS_WINDOWS_PLATFORM, reason='Linux specific path check' + ) + def test_url_compatibility_on_linux(self): + c = Context("test") + assert c.Host == DEFAULT_UNIX_SOCKET.strip("http+") + + @pytest.mark.skipif( + not IS_WINDOWS_PLATFORM, reason='Windows specific path check' + ) + def test_url_compatibility_on_windows(self): + c = Context("test") + assert c.Host == DEFAULT_NPIPE + + def test_fail_on_default_context_create(self): + with pytest.raises(docker.errors.ContextException): + ContextAPI.create_context("default") + + def test_default_in_context_list(self): + found = False + ctx = ContextAPI.contexts() + for c in ctx: + if c.Name == "default": + found = True + assert found is True + + def test_get_current_context(self): + assert ContextAPI.get_current_context().Name == "default" + + def test_https_host(self): + c = Context("test", host="tcp://testdomain:8080", tls=True) + assert c.Host == "https://testdomain:8080" + + def test_context_inspect_without_params(self): + ctx = ContextAPI.inspect_context() + assert ctx["Name"] == "default" + assert ctx["Metadata"]["StackOrchestrator"] == "swarm" + assert ctx["Endpoints"]["docker"]["Host"] in [ + DEFAULT_NPIPE, DEFAULT_UNIX_SOCKET.strip("http+")] diff --git a/tests/unit/dockertypes_test.py b/tests/unit/dockertypes_test.py index 0689d07..f3d562e 100644 --- a/tests/unit/dockertypes_test.py +++ b/tests/unit/dockertypes_test.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import unittest import pytest @@ -11,11 +9,7 @@ from docker.types import ( IPAMPool, LogConfig, Mount, ServiceMode, Ulimit, ) from docker.types.services import convert_service_ports - -try: - from unittest import mock -except: # noqa: E722 - import mock +from unittest import mock def create_host_config(*args, **kwargs): @@ -331,10 +325,26 @@ class ServiceModeTest(unittest.TestCase): assert mode.mode == 'global' assert mode.replicas is None + def test_replicated_job_simple(self): + mode = ServiceMode('replicated-job') + assert mode == {'ReplicatedJob': {}} + assert mode.mode == 'ReplicatedJob' + assert mode.replicas is None + + def test_global_job_simple(self): + mode = ServiceMode('global-job') + assert mode == {'GlobalJob': {}} + assert mode.mode == 'GlobalJob' + assert mode.replicas is None + def test_global_replicas_error(self): with pytest.raises(InvalidArgument): ServiceMode('global', 21) + def test_global_job_replicas_simple(self): + with pytest.raises(InvalidArgument): + ServiceMode('global-job', 21) + def test_replicated_replicas(self): mode = ServiceMode('replicated', 21) assert mode == {'replicated': {'Replicas': 21}} diff --git a/tests/unit/errors_test.py b/tests/unit/errors_test.py index 2134f86..f8c3a66 100644 --- a/tests/unit/errors_test.py +++ b/tests/unit/errors_test.py @@ -101,17 +101,17 @@ class APIErrorTest(unittest.TestCase): assert err.is_error() is True def test_create_error_from_exception(self): - resp = requests.Response() - resp.status_code = 500 - err = APIError('') + resp = requests.Response() + resp.status_code = 500 + err = APIError('') + try: + resp.raise_for_status() + except requests.exceptions.HTTPError as e: try: - resp.raise_for_status() - except requests.exceptions.HTTPError as e: - try: - create_api_error_from_http_exception(e) - except APIError as e: - err = e - assert err.is_server_error() is True + create_api_error_from_http_exception(e) + except APIError as e: + err = e + assert err.is_server_error() is True class ContainerErrorTest(unittest.TestCase): @@ -126,7 +126,7 @@ class ContainerErrorTest(unittest.TestCase): err = ContainerError(container, exit_status, command, image, stderr) msg = ("Command '{}' in image '{}' returned non-zero exit status {}" - ).format(command, image, exit_status, stderr) + ).format(command, image, exit_status) assert str(err) == msg def test_container_with_stderr(self): diff --git a/tests/unit/fake_api.py b/tests/unit/fake_api.py index e609b64..6acfb64 100644 --- a/tests/unit/fake_api.py +++ b/tests/unit/fake_api.py @@ -1,12 +1,13 @@ -from . import fake_stat from docker import constants -CURRENT_VERSION = 'v{0}'.format(constants.DEFAULT_DOCKER_API_VERSION) +from . import fake_stat + +CURRENT_VERSION = f'v{constants.DEFAULT_DOCKER_API_VERSION}' -FAKE_CONTAINER_ID = '3cc2351ab11b' -FAKE_IMAGE_ID = 'e9aa60c60128' -FAKE_EXEC_ID = 'd5d177f121dc' -FAKE_NETWORK_ID = '33fb6a3462b8' +FAKE_CONTAINER_ID = '81cf499cc928ce3fedc250a080d2b9b978df20e4517304c45211e8a68b33e254' # noqa: E501 +FAKE_IMAGE_ID = 'sha256:fe7a8fc91d3f17835cbb3b86a1c60287500ab01a53bc79c4497d09f07a3f0688' # noqa: E501 +FAKE_EXEC_ID = 'b098ec855f10434b5c7c973c78484208223a83f663ddaefb0f02a242840cb1c7' # noqa: E501 +FAKE_NETWORK_ID = '1999cfb42e414483841a125ade3c276c3cb80cb3269b14e339354ac63a31b02c' # noqa: E501 FAKE_IMAGE_NAME = 'test_image' FAKE_TARBALL_PATH = '/path/to/tarball' FAKE_REPO_NAME = 'repo' @@ -16,6 +17,8 @@ FAKE_URL = 'myurl' FAKE_PATH = '/path' FAKE_VOLUME_NAME = 'perfectcherryblossom' FAKE_NODE_ID = '24ifsmvkjbyhk' +FAKE_SECRET_ID = 'epdyrw4tsi03xy3deu8g8ly6o' +FAKE_SECRET_NAME = 'super_secret' # Each method is prefixed with HTTP method (get, post...) # for clarity and readability @@ -511,102 +514,108 @@ def post_fake_network_disconnect(): return 200, None +def post_fake_secret(): + status_code = 200 + response = {'ID': FAKE_SECRET_ID} + return status_code, response + + # Maps real api url to fake response callback prefix = 'http+docker://localhost' if constants.IS_WINDOWS_PLATFORM: prefix = 'http+docker://localnpipe' fake_responses = { - '{0}/version'.format(prefix): + f'{prefix}/version': get_fake_version, - '{1}/{0}/version'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/version': get_fake_version, - '{1}/{0}/info'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/info': get_fake_info, - '{1}/{0}/auth'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/auth': post_fake_auth, - '{1}/{0}/_ping'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/_ping': get_fake_ping, - '{1}/{0}/images/search'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/search': get_fake_search, - '{1}/{0}/images/json'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/json': get_fake_images, - '{1}/{0}/images/test_image/history'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/test_image/history': get_fake_image_history, - '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/create': post_fake_import_image, - '{1}/{0}/containers/json'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/json': get_fake_containers, - '{1}/{0}/containers/3cc2351ab11b/start'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/start': post_fake_start_container, - '{1}/{0}/containers/3cc2351ab11b/resize'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/resize': post_fake_resize_container, - '{1}/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/json': get_fake_inspect_container, - '{1}/{0}/containers/3cc2351ab11b/rename'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/rename': post_fake_rename_container, - '{1}/{0}/images/e9aa60c60128/tag'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/{FAKE_IMAGE_ID}/tag': post_fake_tag_image, - '{1}/{0}/containers/3cc2351ab11b/wait'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/wait': get_fake_wait, - '{1}/{0}/containers/3cc2351ab11b/logs'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/logs': get_fake_logs, - '{1}/{0}/containers/3cc2351ab11b/changes'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/changes': get_fake_diff, - '{1}/{0}/containers/3cc2351ab11b/export'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/export': get_fake_export, - '{1}/{0}/containers/3cc2351ab11b/update'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/update': post_fake_update_container, - '{1}/{0}/containers/3cc2351ab11b/exec'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/exec': post_fake_exec_create, - '{1}/{0}/exec/d5d177f121dc/start'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/exec/{FAKE_EXEC_ID}/start': post_fake_exec_start, - '{1}/{0}/exec/d5d177f121dc/json'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/exec/{FAKE_EXEC_ID}/json': get_fake_exec_inspect, - '{1}/{0}/exec/d5d177f121dc/resize'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/exec/{FAKE_EXEC_ID}/resize': post_fake_exec_resize, - '{1}/{0}/containers/3cc2351ab11b/stats'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/stats': get_fake_stats, - '{1}/{0}/containers/3cc2351ab11b/top'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/top': get_fake_top, - '{1}/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/stop': post_fake_stop_container, - '{1}/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/kill': post_fake_kill_container, - '{1}/{0}/containers/3cc2351ab11b/pause'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/pause': post_fake_pause_container, - '{1}/{0}/containers/3cc2351ab11b/unpause'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/unpause': post_fake_unpause_container, - '{1}/{0}/containers/3cc2351ab11b/restart'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}/restart': post_fake_restart_container, - '{1}/{0}/containers/3cc2351ab11b'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/{FAKE_CONTAINER_ID}': delete_fake_remove_container, - '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/create': post_fake_image_create, - '{1}/{0}/images/e9aa60c60128'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/{FAKE_IMAGE_ID}': delete_fake_remove_image, - '{1}/{0}/images/e9aa60c60128/get'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/{FAKE_IMAGE_ID}/get': get_fake_get_image, - '{1}/{0}/images/load'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/load': post_fake_load_image, - '{1}/{0}/images/test_image/json'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/test_image/json': get_fake_inspect_image, - '{1}/{0}/images/test_image/insert'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/test_image/insert': get_fake_insert_image, - '{1}/{0}/images/test_image/push'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/images/test_image/push': post_fake_push, - '{1}/{0}/commit'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/commit': post_fake_commit, - '{1}/{0}/containers/create'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/containers/create': post_fake_create_container, - '{1}/{0}/build'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/build': post_fake_build_container, - '{1}/{0}/events'.format(CURRENT_VERSION, prefix): + f'{prefix}/{CURRENT_VERSION}/events': get_fake_events, - ('{1}/{0}/volumes'.format(CURRENT_VERSION, prefix), 'GET'): + (f'{prefix}/{CURRENT_VERSION}/volumes', 'GET'): get_fake_volume_list, - ('{1}/{0}/volumes/create'.format(CURRENT_VERSION, prefix), 'POST'): + (f'{prefix}/{CURRENT_VERSION}/volumes/create', 'POST'): get_fake_volume, ('{1}/{0}/volumes/{2}'.format( CURRENT_VERSION, prefix, FAKE_VOLUME_NAME @@ -620,11 +629,11 @@ fake_responses = { CURRENT_VERSION, prefix, FAKE_NODE_ID ), 'POST'): post_fake_update_node, - ('{1}/{0}/swarm/join'.format(CURRENT_VERSION, prefix), 'POST'): + (f'{prefix}/{CURRENT_VERSION}/swarm/join', 'POST'): post_fake_join_swarm, - ('{1}/{0}/networks'.format(CURRENT_VERSION, prefix), 'GET'): + (f'{prefix}/{CURRENT_VERSION}/networks', 'GET'): get_fake_network_list, - ('{1}/{0}/networks/create'.format(CURRENT_VERSION, prefix), 'POST'): + (f'{prefix}/{CURRENT_VERSION}/networks/create', 'POST'): post_fake_network, ('{1}/{0}/networks/{2}'.format( CURRENT_VERSION, prefix, FAKE_NETWORK_ID @@ -642,4 +651,6 @@ fake_responses = { CURRENT_VERSION, prefix, FAKE_NETWORK_ID ), 'POST'): post_fake_network_disconnect, + f'{prefix}/{CURRENT_VERSION}/secrets/create': + post_fake_secret, } diff --git a/tests/unit/fake_api_client.py b/tests/unit/fake_api_client.py index 2147bfd..95cf63b 100644 --- a/tests/unit/fake_api_client.py +++ b/tests/unit/fake_api_client.py @@ -1,20 +1,17 @@ import copy -import docker +import docker +from docker.constants import DEFAULT_DOCKER_API_VERSION +from unittest import mock from . import fake_api -try: - from unittest import mock -except ImportError: - import mock - class CopyReturnMagicMock(mock.MagicMock): """ A MagicMock which deep copies every return value. """ def _mock_call(self, *args, **kwargs): - ret = super(CopyReturnMagicMock, self)._mock_call(*args, **kwargs) + ret = super()._mock_call(*args, **kwargs) if isinstance(ret, (dict, list)): ret = copy.deepcopy(ret) return ret @@ -30,7 +27,7 @@ def make_fake_api_client(overrides=None): if overrides is None: overrides = {} - api_client = docker.APIClient() + api_client = docker.APIClient(version=DEFAULT_DOCKER_API_VERSION) mock_attrs = { 'build.return_value': fake_api.FAKE_IMAGE_ID, 'commit.return_value': fake_api.post_fake_commit()[1], @@ -39,6 +36,7 @@ def make_fake_api_client(overrides=None): fake_api.post_fake_create_container()[1], 'create_host_config.side_effect': api_client.create_host_config, 'create_network.return_value': fake_api.post_fake_network()[1], + 'create_secret.return_value': fake_api.post_fake_secret()[1], 'exec_create.return_value': fake_api.post_fake_exec_create()[1], 'exec_start.return_value': fake_api.post_fake_exec_start()[1], 'images.return_value': fake_api.get_fake_images()[1], @@ -50,6 +48,7 @@ def make_fake_api_client(overrides=None): 'networks.return_value': fake_api.get_fake_network_list()[1], 'start.return_value': None, 'wait.return_value': {'StatusCode': 0}, + 'version.return_value': fake_api.get_fake_version() } mock_attrs.update(overrides) mock_client = CopyReturnMagicMock(**mock_attrs) @@ -62,6 +61,6 @@ def make_fake_client(overrides=None): """ Returns a Client with a fake APIClient. """ - client = docker.DockerClient() + client = docker.DockerClient(version=DEFAULT_DOCKER_API_VERSION) client.api = make_fake_api_client(overrides) return client diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index da5f0ab..101708e 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -39,6 +39,7 @@ class ContainerCollectionTest(unittest.TestCase): cap_add=['foo'], cap_drop=['bar'], cgroup_parent='foobar', + cgroupns='host', cpu_period=1, cpu_quota=2, cpu_shares=5, @@ -77,6 +78,7 @@ class ContainerCollectionTest(unittest.TestCase): oom_score_adj=5, pid_mode='host', pids_limit=500, + platform='linux', ports={ 1111: 4567, 2222: None @@ -134,6 +136,7 @@ class ContainerCollectionTest(unittest.TestCase): 'BlkioWeight': 2, 'CapAdd': ['foo'], 'CapDrop': ['bar'], + 'CgroupnsMode': 'host', 'CgroupParent': 'foobar', 'CpuPeriod': 1, 'CpuQuota': 2, @@ -186,6 +189,7 @@ class ContainerCollectionTest(unittest.TestCase): name='somename', network_disabled=False, networking_config={'foo': None}, + platform='linux', ports=[('1111', 'tcp'), ('2222', 'tcp')], stdin_open=True, stop_signal=9, @@ -233,7 +237,7 @@ class ContainerCollectionTest(unittest.TestCase): assert container.id == FAKE_CONTAINER_ID client.api.pull.assert_called_with( - 'alpine', platform=None, tag=None, stream=True + 'alpine', platform=None, tag='latest', all_tags=False, stream=True ) def test_run_with_error(self): @@ -314,6 +318,33 @@ class ContainerCollectionTest(unittest.TestCase): 'NetworkMode': 'default'} ) + def test_run_platform(self): + client = make_fake_client() + + # raise exception on first call, then return normal value + client.api.create_container.side_effect = [ + docker.errors.ImageNotFound(""), + client.api.create_container.return_value + ] + + client.containers.run(image='alpine', platform='linux/arm64') + + client.api.pull.assert_called_with( + 'alpine', + tag='latest', + all_tags=False, + stream=True, + platform='linux/arm64', + ) + + client.api.create_container.assert_called_with( + detach=False, + platform='linux/arm64', + image='alpine', + command=None, + host_config={'NetworkMode': 'default'}, + ) + def test_create(self): client = make_fake_client() container = client.containers.create( @@ -377,6 +408,11 @@ class ContainerCollectionTest(unittest.TestCase): class ContainerTest(unittest.TestCase): + def test_short_id(self): + container = Container(attrs={'Id': '8497fe9244dd45cac543eb3c37d8605077' + '6800eebef1f3ec2ee111e8ccf12db6'}) + assert container.short_id == '8497fe9244dd' + def test_name(self): client = make_fake_client() container = client.containers.get(FAKE_CONTAINER_ID) @@ -450,7 +486,7 @@ class ContainerTest(unittest.TestCase): container = client.containers.get(FAKE_CONTAINER_ID) container.get_archive('foo') client.api.get_archive.assert_called_with( - FAKE_CONTAINER_ID, 'foo', DEFAULT_DATA_CHUNK_SIZE + FAKE_CONTAINER_ID, 'foo', DEFAULT_DATA_CHUNK_SIZE, False ) def test_image(self): diff --git a/tests/unit/models_images_test.py b/tests/unit/models_images_test.py index fd894ab..3478c3f 100644 --- a/tests/unit/models_images_test.py +++ b/tests/unit/models_images_test.py @@ -44,9 +44,25 @@ class ImageCollectionTest(unittest.TestCase): def test_pull(self): client = make_fake_client() - image = client.images.pull('test_image:latest') + image = client.images.pull('test_image:test') client.api.pull.assert_called_with( - 'test_image', tag='latest', stream=True + 'test_image', tag='test', all_tags=False, stream=True + ) + client.api.inspect_image.assert_called_with('test_image:test') + assert isinstance(image, Image) + assert image.id == FAKE_IMAGE_ID + + def test_pull_tag_precedence(self): + client = make_fake_client() + image = client.images.pull('test_image:latest', tag='test') + client.api.pull.assert_called_with( + 'test_image', tag='test', all_tags=False, stream=True + ) + client.api.inspect_image.assert_called_with('test_image:test') + + image = client.images.pull('test_image') + client.api.pull.assert_called_with( + 'test_image', tag='latest', all_tags=False, stream=True ) client.api.inspect_image.assert_called_with('test_image:latest') assert isinstance(image, Image) @@ -54,9 +70,9 @@ class ImageCollectionTest(unittest.TestCase): def test_pull_multiple(self): client = make_fake_client() - images = client.images.pull('test_image') + images = client.images.pull('test_image', all_tags=True) client.api.pull.assert_called_with( - 'test_image', tag=None, stream=True + 'test_image', tag='latest', all_tags=True, stream=True ) client.api.images.assert_called_with( all=False, name='test_image', filters=None @@ -96,16 +112,21 @@ class ImageCollectionTest(unittest.TestCase): client.images.search('test') client.api.search.assert_called_with('test') + def test_search_limit(self): + client = make_fake_client() + client.images.search('test', limit=5) + client.api.search.assert_called_with('test', limit=5) + class ImageTest(unittest.TestCase): def test_short_id(self): image = Image(attrs={'Id': 'sha256:b6846070672ce4e8f1f91564ea6782bd675' 'f69d65a6f73ef6262057ad0a15dcd'}) - assert image.short_id == 'sha256:b684607067' + assert image.short_id == 'sha256:b6846070672c' image = Image(attrs={'Id': 'b6846070672ce4e8f1f91564ea6782bd675' 'f69d65a6f73ef6262057ad0a15dcd'}) - assert image.short_id == 'b684607067' + assert image.short_id == 'b6846070672c' def test_tags(self): image = Image(attrs={ @@ -129,6 +150,16 @@ class ImageTest(unittest.TestCase): image.history() client.api.history.assert_called_with(FAKE_IMAGE_ID) + def test_remove(self): + client = make_fake_client() + image = client.images.get(FAKE_IMAGE_ID) + image.remove() + client.api.remove_image.assert_called_with( + FAKE_IMAGE_ID, + force=False, + noprune=False, + ) + def test_save(self): client = make_fake_client() image = client.images.get(FAKE_IMAGE_ID) diff --git a/tests/unit/models_resources_test.py b/tests/unit/models_resources_test.py index 5af24ee..11dea29 100644 --- a/tests/unit/models_resources_test.py +++ b/tests/unit/models_resources_test.py @@ -16,7 +16,7 @@ class ModelTest(unittest.TestCase): def test_hash(self): client = make_fake_client() container1 = client.containers.get(FAKE_CONTAINER_ID) - my_set = set([container1]) + my_set = {container1} assert len(my_set) == 1 container2 = client.containers.get(FAKE_CONTAINER_ID) diff --git a/tests/unit/models_secrets_test.py b/tests/unit/models_secrets_test.py new file mode 100644 index 0000000..1c261a8 --- /dev/null +++ b/tests/unit/models_secrets_test.py @@ -0,0 +1,11 @@ +import unittest + +from .fake_api_client import make_fake_client +from .fake_api import FAKE_SECRET_NAME + + +class CreateServiceTest(unittest.TestCase): + def test_secrets_repr(self): + client = make_fake_client() + secret = client.secrets.create(name="super_secret", data="secret") + assert secret.__repr__() == f"<Secret: '{FAKE_SECRET_NAME}'>" diff --git a/tests/unit/models_services_test.py b/tests/unit/models_services_test.py index a4ac50c..45c63ac 100644 --- a/tests/unit/models_services_test.py +++ b/tests/unit/models_services_test.py @@ -11,6 +11,7 @@ class CreateServiceKwargsTest(unittest.TestCase): 'labels': {'key': 'value'}, 'hostname': 'test_host', 'mode': 'global', + 'rollback_config': {'rollback': 'config'}, 'update_config': {'update': 'config'}, 'networks': ['somenet'], 'endpoint_spec': {'blah': 'blah'}, @@ -28,6 +29,8 @@ class CreateServiceKwargsTest(unittest.TestCase): 'constraints': ['foo=bar'], 'preferences': ['bar=baz'], 'platforms': [('x86_64', 'linux')], + 'maxreplicas': 1, + 'sysctls': {'foo': 'bar'} }) task_template = kwargs.pop('task_template') @@ -36,24 +39,26 @@ class CreateServiceKwargsTest(unittest.TestCase): 'name': 'somename', 'labels': {'key': 'value'}, 'mode': 'global', + 'rollback_config': {'rollback': 'config'}, 'update_config': {'update': 'config'}, 'endpoint_spec': {'blah': 'blah'}, } - assert set(task_template.keys()) == set([ + assert set(task_template.keys()) == { 'ContainerSpec', 'Resources', 'RestartPolicy', 'Placement', 'LogDriver', 'Networks' - ]) + } assert task_template['Placement'] == { 'Constraints': ['foo=bar'], 'Preferences': ['bar=baz'], 'Platforms': [{'Architecture': 'x86_64', 'OS': 'linux'}], + 'MaxReplicas': 1, } assert task_template['LogDriver'] == { 'Name': 'logdriver', 'Options': {'foo': 'bar'} } assert task_template['Networks'] == [{'Target': 'somenet'}] - assert set(task_template['ContainerSpec'].keys()) == set([ + assert set(task_template['ContainerSpec'].keys()) == { 'Image', 'Command', 'Args', 'Hostname', 'Env', 'Dir', 'User', - 'Labels', 'Mounts', 'StopGracePeriod' - ]) + 'Labels', 'Mounts', 'StopGracePeriod', 'Sysctls' + } diff --git a/tests/unit/sshadapter_test.py b/tests/unit/sshadapter_test.py new file mode 100644 index 0000000..874239a --- /dev/null +++ b/tests/unit/sshadapter_test.py @@ -0,0 +1,39 @@ +import unittest +import docker +from docker.transport.sshconn import SSHSocket + + +class SSHAdapterTest(unittest.TestCase): + @staticmethod + def test_ssh_hostname_prefix_trim(): + conn = docker.transport.SSHHTTPAdapter( + base_url="ssh://user@hostname:1234", shell_out=True) + assert conn.ssh_host == "user@hostname:1234" + + @staticmethod + def test_ssh_parse_url(): + c = SSHSocket(host="user@hostname:1234") + assert c.host == "hostname" + assert c.port == "1234" + assert c.user == "user" + + @staticmethod + def test_ssh_parse_hostname_only(): + c = SSHSocket(host="hostname") + assert c.host == "hostname" + assert c.port is None + assert c.user is None + + @staticmethod + def test_ssh_parse_user_and_hostname(): + c = SSHSocket(host="user@hostname") + assert c.host == "hostname" + assert c.port is None + assert c.user == "user" + + @staticmethod + def test_ssh_parse_hostname_and_port(): + c = SSHSocket(host="hostname:22") + assert c.host == "hostname" + assert c.port == "22" + assert c.user is None diff --git a/tests/unit/ssladapter_test.py b/tests/unit/ssladapter_test.py index 73b7336..d3f2407 100644 --- a/tests/unit/ssladapter_test.py +++ b/tests/unit/ssladapter_test.py @@ -1,15 +1,8 @@ import unittest -from docker.transport import ssladapter -import pytest +from ssl import match_hostname, CertificateError -try: - from backports.ssl_match_hostname import ( - match_hostname, CertificateError - ) -except ImportError: - from ssl import ( - match_hostname, CertificateError - ) +import pytest +from docker.transport import ssladapter try: from ssl import OP_NO_SSLv3, OP_NO_SSLv2, OP_NO_TLSv1 @@ -32,30 +25,30 @@ class SSLAdapterTest(unittest.TestCase): class MatchHostnameTest(unittest.TestCase): cert = { 'issuer': ( - (('countryName', u'US'),), - (('stateOrProvinceName', u'California'),), - (('localityName', u'San Francisco'),), - (('organizationName', u'Docker Inc'),), - (('organizationalUnitName', u'Docker-Python'),), - (('commonName', u'localhost'),), - (('emailAddress', u'info@docker.com'),) + (('countryName', 'US'),), + (('stateOrProvinceName', 'California'),), + (('localityName', 'San Francisco'),), + (('organizationName', 'Docker Inc'),), + (('organizationalUnitName', 'Docker-Python'),), + (('commonName', 'localhost'),), + (('emailAddress', 'info@docker.com'),) ), 'notAfter': 'Mar 25 23:08:23 2030 GMT', - 'notBefore': u'Mar 25 23:08:23 2016 GMT', - 'serialNumber': u'BD5F894C839C548F', + 'notBefore': 'Mar 25 23:08:23 2016 GMT', + 'serialNumber': 'BD5F894C839C548F', 'subject': ( - (('countryName', u'US'),), - (('stateOrProvinceName', u'California'),), - (('localityName', u'San Francisco'),), - (('organizationName', u'Docker Inc'),), - (('organizationalUnitName', u'Docker-Python'),), - (('commonName', u'localhost'),), - (('emailAddress', u'info@docker.com'),) + (('countryName', 'US'),), + (('stateOrProvinceName', 'California'),), + (('localityName', 'San Francisco'),), + (('organizationName', 'Docker Inc'),), + (('organizationalUnitName', 'Docker-Python'),), + (('commonName', 'localhost'),), + (('emailAddress', 'info@docker.com'),) ), 'subjectAltName': ( - ('DNS', u'localhost'), - ('DNS', u'*.gensokyo.jp'), - ('IP Address', u'127.0.0.1'), + ('DNS', 'localhost'), + ('DNS', '*.gensokyo.jp'), + ('IP Address', '127.0.0.1'), ), 'version': 3 } diff --git a/tests/unit/swarm_test.py b/tests/unit/swarm_test.py index 4385380..aee1b9e 100644 --- a/tests/unit/swarm_test.py +++ b/tests/unit/swarm_test.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import json from . import fake_api diff --git a/tests/unit/utils_build_test.py b/tests/unit/utils_build_test.py index 012f15b..fa7d833 100644 --- a/tests/unit/utils_build_test.py +++ b/tests/unit/utils_build_test.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import os.path import shutil @@ -82,7 +80,7 @@ class ExcludePathsTest(unittest.TestCase): assert sorted(paths) == sorted(set(paths)) def test_wildcard_exclude(self): - assert self.exclude(['*']) == set(['Dockerfile', '.dockerignore']) + assert self.exclude(['*']) == {'Dockerfile', '.dockerignore'} def test_exclude_dockerfile_dockerignore(self): """ @@ -99,18 +97,18 @@ class ExcludePathsTest(unittest.TestCase): If we're using a custom Dockerfile, make sure that's not excluded. """ - assert self.exclude(['*'], dockerfile='Dockerfile.alt') == set( - ['Dockerfile.alt', '.dockerignore'] - ) + assert self.exclude(['*'], dockerfile='Dockerfile.alt') == { + 'Dockerfile.alt', '.dockerignore' + } assert self.exclude( ['*'], dockerfile='foo/Dockerfile3' - ) == convert_paths(set(['foo/Dockerfile3', '.dockerignore'])) + ) == convert_paths({'foo/Dockerfile3', '.dockerignore'}) # https://github.com/docker/docker-py/issues/1956 assert self.exclude( ['*'], dockerfile='./foo/Dockerfile3' - ) == convert_paths(set(['foo/Dockerfile3', '.dockerignore'])) + ) == convert_paths({'foo/Dockerfile3', '.dockerignore'}) def test_exclude_dockerfile_child(self): includes = self.exclude(['foo/'], dockerfile='foo/Dockerfile3') @@ -119,56 +117,56 @@ class ExcludePathsTest(unittest.TestCase): def test_single_filename(self): assert self.exclude(['a.py']) == convert_paths( - self.all_paths - set(['a.py']) + self.all_paths - {'a.py'} ) def test_single_filename_leading_dot_slash(self): assert self.exclude(['./a.py']) == convert_paths( - self.all_paths - set(['a.py']) + self.all_paths - {'a.py'} ) # As odd as it sounds, a filename pattern with a trailing slash on the # end *will* result in that file being excluded. def test_single_filename_trailing_slash(self): assert self.exclude(['a.py/']) == convert_paths( - self.all_paths - set(['a.py']) + self.all_paths - {'a.py'} ) def test_wildcard_filename_start(self): assert self.exclude(['*.py']) == convert_paths( - self.all_paths - set(['a.py', 'b.py', 'cde.py']) + self.all_paths - {'a.py', 'b.py', 'cde.py'} ) def test_wildcard_with_exception(self): assert self.exclude(['*.py', '!b.py']) == convert_paths( - self.all_paths - set(['a.py', 'cde.py']) + self.all_paths - {'a.py', 'cde.py'} ) def test_wildcard_with_wildcard_exception(self): assert self.exclude(['*.*', '!*.go']) == convert_paths( - self.all_paths - set([ + self.all_paths - { 'a.py', 'b.py', 'cde.py', 'Dockerfile.alt', - ]) + } ) def test_wildcard_filename_end(self): assert self.exclude(['a.*']) == convert_paths( - self.all_paths - set(['a.py', 'a.go']) + self.all_paths - {'a.py', 'a.go'} ) def test_question_mark(self): assert self.exclude(['?.py']) == convert_paths( - self.all_paths - set(['a.py', 'b.py']) + self.all_paths - {'a.py', 'b.py'} ) def test_single_subdir_single_filename(self): assert self.exclude(['foo/a.py']) == convert_paths( - self.all_paths - set(['foo/a.py']) + self.all_paths - {'foo/a.py'} ) def test_single_subdir_single_filename_leading_slash(self): assert self.exclude(['/foo/a.py']) == convert_paths( - self.all_paths - set(['foo/a.py']) + self.all_paths - {'foo/a.py'} ) def test_exclude_include_absolute_path(self): @@ -176,57 +174,57 @@ class ExcludePathsTest(unittest.TestCase): assert exclude_paths( base, ['/*', '!/*.py'] - ) == set(['a.py', 'b.py']) + ) == {'a.py', 'b.py'} def test_single_subdir_with_path_traversal(self): assert self.exclude(['foo/whoops/../a.py']) == convert_paths( - self.all_paths - set(['foo/a.py']) + self.all_paths - {'foo/a.py'} ) def test_single_subdir_wildcard_filename(self): assert self.exclude(['foo/*.py']) == convert_paths( - self.all_paths - set(['foo/a.py', 'foo/b.py']) + self.all_paths - {'foo/a.py', 'foo/b.py'} ) def test_wildcard_subdir_single_filename(self): assert self.exclude(['*/a.py']) == convert_paths( - self.all_paths - set(['foo/a.py', 'bar/a.py']) + self.all_paths - {'foo/a.py', 'bar/a.py'} ) def test_wildcard_subdir_wildcard_filename(self): assert self.exclude(['*/*.py']) == convert_paths( - self.all_paths - set(['foo/a.py', 'foo/b.py', 'bar/a.py']) + self.all_paths - {'foo/a.py', 'foo/b.py', 'bar/a.py'} ) def test_directory(self): assert self.exclude(['foo']) == convert_paths( - self.all_paths - set([ + self.all_paths - { 'foo', 'foo/a.py', 'foo/b.py', 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' - ]) + } ) def test_directory_with_trailing_slash(self): assert self.exclude(['foo']) == convert_paths( - self.all_paths - set([ + self.all_paths - { 'foo', 'foo/a.py', 'foo/b.py', 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' - ]) + } ) def test_directory_with_single_exception(self): assert self.exclude(['foo', '!foo/bar/a.py']) == convert_paths( - self.all_paths - set([ + self.all_paths - { 'foo/a.py', 'foo/b.py', 'foo', 'foo/bar', 'foo/Dockerfile3' - ]) + } ) def test_directory_with_subdir_exception(self): assert self.exclude(['foo', '!foo/bar']) == convert_paths( - self.all_paths - set([ + self.all_paths - { 'foo/a.py', 'foo/b.py', 'foo', 'foo/Dockerfile3' - ]) + } ) @pytest.mark.skipif( @@ -234,21 +232,21 @@ class ExcludePathsTest(unittest.TestCase): ) def test_directory_with_subdir_exception_win32_pathsep(self): assert self.exclude(['foo', '!foo\\bar']) == convert_paths( - self.all_paths - set([ + self.all_paths - { 'foo/a.py', 'foo/b.py', 'foo', 'foo/Dockerfile3' - ]) + } ) def test_directory_with_wildcard_exception(self): assert self.exclude(['foo', '!foo/*.py']) == convert_paths( - self.all_paths - set([ + self.all_paths - { 'foo/bar', 'foo/bar/a.py', 'foo', 'foo/Dockerfile3' - ]) + } ) def test_subdirectory(self): assert self.exclude(['foo/bar']) == convert_paths( - self.all_paths - set(['foo/bar', 'foo/bar/a.py']) + self.all_paths - {'foo/bar', 'foo/bar/a.py'} ) @pytest.mark.skipif( @@ -256,52 +254,52 @@ class ExcludePathsTest(unittest.TestCase): ) def test_subdirectory_win32_pathsep(self): assert self.exclude(['foo\\bar']) == convert_paths( - self.all_paths - set(['foo/bar', 'foo/bar/a.py']) + self.all_paths - {'foo/bar', 'foo/bar/a.py'} ) def test_double_wildcard(self): assert self.exclude(['**/a.py']) == convert_paths( - self.all_paths - set( - ['a.py', 'foo/a.py', 'foo/bar/a.py', 'bar/a.py'] - ) + self.all_paths - { + 'a.py', 'foo/a.py', 'foo/bar/a.py', 'bar/a.py' + } ) assert self.exclude(['foo/**/bar']) == convert_paths( - self.all_paths - set(['foo/bar', 'foo/bar/a.py']) + self.all_paths - {'foo/bar', 'foo/bar/a.py'} ) def test_single_and_double_wildcard(self): assert self.exclude(['**/target/*/*']) == convert_paths( - self.all_paths - set( - ['target/subdir/file.txt', - 'subdir/target/subdir/file.txt', - 'subdir/subdir2/target/subdir/file.txt'] - ) + self.all_paths - { + 'target/subdir/file.txt', + 'subdir/target/subdir/file.txt', + 'subdir/subdir2/target/subdir/file.txt' + } ) def test_trailing_double_wildcard(self): assert self.exclude(['subdir/**']) == convert_paths( - self.all_paths - set( - ['subdir/file.txt', - 'subdir/target/file.txt', - 'subdir/target/subdir/file.txt', - 'subdir/subdir2/file.txt', - 'subdir/subdir2/target/file.txt', - 'subdir/subdir2/target/subdir/file.txt', - 'subdir/target', - 'subdir/target/subdir', - 'subdir/subdir2', - 'subdir/subdir2/target', - 'subdir/subdir2/target/subdir'] - ) + self.all_paths - { + 'subdir/file.txt', + 'subdir/target/file.txt', + 'subdir/target/subdir/file.txt', + 'subdir/subdir2/file.txt', + 'subdir/subdir2/target/file.txt', + 'subdir/subdir2/target/subdir/file.txt', + 'subdir/target', + 'subdir/target/subdir', + 'subdir/subdir2', + 'subdir/subdir2/target', + 'subdir/subdir2/target/subdir' + } ) def test_double_wildcard_with_exception(self): assert self.exclude(['**', '!bar', '!foo/bar']) == convert_paths( - set([ + { 'foo/bar', 'foo/bar/a.py', 'bar', 'bar/a.py', 'Dockerfile', '.dockerignore', - ]) + } ) def test_include_wildcard(self): @@ -324,7 +322,7 @@ class ExcludePathsTest(unittest.TestCase): assert exclude_paths( base, ['*.md', '!README*.md', 'README-secret.md'] - ) == set(['README.md', 'README-bis.md']) + ) == {'README.md', 'README-bis.md'} def test_parent_directory(self): base = make_tree( @@ -335,12 +333,12 @@ class ExcludePathsTest(unittest.TestCase): # Dockerignore reference stipulates that absolute paths are # equivalent to relative paths, hence /../foo should be # equivalent to ../foo. It also stipulates that paths are run - # through Go's filepath.Clean, which explicitely "replace + # through Go's filepath.Clean, which explicitly "replace # "/.." by "/" at the beginning of a path". assert exclude_paths( base, ['../a.py', '/../b.py'] - ) == set(['c.py']) + ) == {'c.py'} class TarTest(unittest.TestCase): @@ -374,14 +372,14 @@ class TarTest(unittest.TestCase): '.dockerignore', ] - expected_names = set([ + expected_names = { 'Dockerfile', '.dockerignore', 'a.go', 'b.py', 'bar', 'bar/a.py', - ]) + } base = make_tree(dirs, files) self.addCleanup(shutil.rmtree, base) @@ -413,7 +411,7 @@ class TarTest(unittest.TestCase): with pytest.raises(IOError) as ei: tar(base) - assert 'Can not read file in context: {}'.format(full_path) in ( + assert f'Can not read file in context: {full_path}' in ( ei.exconly() ) diff --git a/tests/unit/utils_config_test.py b/tests/unit/utils_config_test.py index b0934f9..27d5a7c 100644 --- a/tests/unit/utils_config_test.py +++ b/tests/unit/utils_config_test.py @@ -5,14 +5,10 @@ import tempfile import json from pytest import mark, fixture +from unittest import mock from docker.utils import config -try: - from unittest import mock -except ImportError: - import mock - class FindConfigFileTest(unittest.TestCase): diff --git a/tests/unit/utils_json_stream_test.py b/tests/unit/utils_json_stream_test.py index f7aefd0..821ebe4 100644 --- a/tests/unit/utils_json_stream_test.py +++ b/tests/unit/utils_json_stream_test.py @@ -1,11 +1,7 @@ -# encoding: utf-8 -from __future__ import absolute_import -from __future__ import unicode_literals - from docker.utils.json_stream import json_splitter, stream_as_text, json_stream -class TestJsonSplitter(object): +class TestJsonSplitter: def test_json_splitter_no_object(self): data = '{"foo": "bar' @@ -20,7 +16,7 @@ class TestJsonSplitter(object): assert json_splitter(data) == ({'foo': 'bar'}, '{"next": "obj"}') -class TestStreamAsText(object): +class TestStreamAsText: def test_stream_with_non_utf_unicode_character(self): stream = [b'\xed\xf3\xf3'] @@ -28,12 +24,12 @@ class TestStreamAsText(object): assert output == '���' def test_stream_with_utf_character(self): - stream = ['ěĝ'.encode('utf-8')] + stream = ['ěĝ'.encode()] output, = stream_as_text(stream) assert output == 'ěĝ' -class TestJsonStream(object): +class TestJsonStream: def test_with_falsy_entries(self): stream = [ diff --git a/tests/unit/utils_proxy_test.py b/tests/unit/utils_proxy_test.py index ff0e14b..2da6040 100644 --- a/tests/unit/utils_proxy_test.py +++ b/tests/unit/utils_proxy_test.py @@ -1,7 +1,4 @@ -# -*- coding: utf-8 -*- - import unittest -import six from docker.utils.proxy import ProxyConfig @@ -65,7 +62,7 @@ class ProxyConfigTest(unittest.TestCase): # Proxy config is non null, env is None. self.assertSetEqual( set(CONFIG.inject_proxy_environment(None)), - set(['{}={}'.format(k, v) for k, v in six.iteritems(ENV)])) + {f'{k}={v}' for k, v in ENV.items()}) # Proxy config is null, env is None. self.assertIsNone(ProxyConfig().inject_proxy_environment(None), None) @@ -74,7 +71,7 @@ class ProxyConfigTest(unittest.TestCase): # Proxy config is non null, env is non null actual = CONFIG.inject_proxy_environment(env) - expected = ['{}={}'.format(k, v) for k, v in six.iteritems(ENV)] + env + expected = [f'{k}={v}' for k, v in ENV.items()] + env # It's important that the first 8 variables are the ones from the proxy # config, and the last 2 are the ones from the input environment self.assertSetEqual(set(actual[:8]), set(expected[:8])) diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index d9cb002..12cb7bd 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -1,31 +1,22 @@ -# -*- coding: utf-8 -*- - import base64 import json import os import os.path import shutil -import sys import tempfile import unittest - +import pytest from docker.api.client import APIClient -from docker.constants import IS_WINDOWS_PLATFORM +from docker.constants import IS_WINDOWS_PLATFORM, DEFAULT_DOCKER_API_VERSION from docker.errors import DockerException -from docker.utils import ( - convert_filters, convert_volume_binds, decode_json_header, kwargs_from_env, - parse_bytes, parse_devices, parse_env_file, parse_host, - parse_repository_tag, split_command, update_headers, -) - +from docker.utils import (convert_filters, convert_volume_binds, + decode_json_header, kwargs_from_env, parse_bytes, + parse_devices, parse_env_file, parse_host, + parse_repository_tag, split_command, update_headers) from docker.utils.ports import build_port_bindings, split_port from docker.utils.utils import format_environment -import pytest - -import six - TEST_CERT_DIR = os.path.join( os.path.dirname(__file__), 'testdata/certs', @@ -41,7 +32,7 @@ class DecoratorsTest(unittest.TestCase): def f(self, headers=None): return headers - client = APIClient() + client = APIClient(version=DEFAULT_DOCKER_API_VERSION) client._general_configs = {} g = update_headers(f) @@ -92,6 +83,7 @@ class KwargsFromEnvTest(unittest.TestCase): assert kwargs['tls'].verify parsed_host = parse_host(kwargs['base_url'], IS_WINDOWS_PLATFORM, True) + kwargs['version'] = DEFAULT_DOCKER_API_VERSION try: client = APIClient(**kwargs) assert parsed_host == client.base_url @@ -112,6 +104,7 @@ class KwargsFromEnvTest(unittest.TestCase): assert kwargs['tls'].assert_hostname is True assert kwargs['tls'].verify is False parsed_host = parse_host(kwargs['base_url'], IS_WINDOWS_PLATFORM, True) + kwargs['version'] = DEFAULT_DOCKER_API_VERSION try: client = APIClient(**kwargs) assert parsed_host == client.base_url @@ -199,22 +192,22 @@ class ConverVolumeBindsTest(unittest.TestCase): assert convert_volume_binds(data) == ['/mnt/vol1:/data:rw'] def test_convert_volume_binds_unicode_bytes_input(self): - expected = [u'/mnt/지연:/unicode/박:rw'] + expected = ['/mnt/지연:/unicode/박:rw'] data = { - u'/mnt/지연'.encode('utf-8'): { - 'bind': u'/unicode/박'.encode('utf-8'), + '/mnt/지연'.encode(): { + 'bind': '/unicode/박'.encode(), 'mode': 'rw' } } assert convert_volume_binds(data) == expected def test_convert_volume_binds_unicode_unicode_input(self): - expected = [u'/mnt/지연:/unicode/박:rw'] + expected = ['/mnt/지연:/unicode/박:rw'] data = { - u'/mnt/지연': { - 'bind': u'/unicode/박', + '/mnt/지연': { + 'bind': '/unicode/박', 'mode': 'rw' } } @@ -303,17 +296,24 @@ class ParseHostTest(unittest.TestCase): '[fd12::82d1]:2375/docker/engine': ( 'http://[fd12::82d1]:2375/docker/engine' ), + 'ssh://[fd12::82d1]': 'ssh://[fd12::82d1]:22', + 'ssh://user@[fd12::82d1]:8765': 'ssh://user@[fd12::82d1]:8765', 'ssh://': 'ssh://127.0.0.1:22', 'ssh://user@localhost:22': 'ssh://user@localhost:22', 'ssh://user@remote': 'ssh://user@remote:22', } for host in invalid_hosts: - with pytest.raises(DockerException): + msg = f'Should have failed to parse invalid host: {host}' + with self.assertRaises(DockerException, msg=msg): parse_host(host, None) for host, expected in valid_hosts.items(): - assert parse_host(host, None) == expected + self.assertEqual( + parse_host(host, None), + expected, + msg=f'Failed to parse valid host: {host}', + ) def test_parse_host_empty_value(self): unix_socket = 'http+unix:///var/run/docker.sock' @@ -363,14 +363,14 @@ class ParseRepositoryTagTest(unittest.TestCase): ) def test_index_image_sha(self): - assert parse_repository_tag("root@sha256:{0}".format(self.sha)) == ( - "root", "sha256:{0}".format(self.sha) + assert parse_repository_tag(f"root@sha256:{self.sha}") == ( + "root", f"sha256:{self.sha}" ) def test_private_reg_image_sha(self): assert parse_repository_tag( - "url:5000/repo@sha256:{0}".format(self.sha) - ) == ("url:5000/repo", "sha256:{0}".format(self.sha)) + f"url:5000/repo@sha256:{self.sha}" + ) == ("url:5000/repo", f"sha256:{self.sha}") class ParseDeviceTest(unittest.TestCase): @@ -447,11 +447,7 @@ class ParseBytesTest(unittest.TestCase): parse_bytes("127.0.0.1K") def test_parse_bytes_float(self): - with pytest.raises(DockerException): - parse_bytes("1.5k") - - def test_parse_bytes_maxint(self): - assert parse_bytes("{0}k".format(sys.maxsize)) == sys.maxsize * 1024 + assert parse_bytes("1.5k") == 1536 class UtilsTest(unittest.TestCase): @@ -471,20 +467,13 @@ class UtilsTest(unittest.TestCase): def test_decode_json_header(self): obj = {'a': 'b', 'c': 1} data = None - if six.PY3: - data = base64.urlsafe_b64encode(bytes(json.dumps(obj), 'utf-8')) - else: - data = base64.urlsafe_b64encode(json.dumps(obj)) + data = base64.urlsafe_b64encode(bytes(json.dumps(obj), 'utf-8')) decoded_data = decode_json_header(data) assert obj == decoded_data class SplitCommandTest(unittest.TestCase): def test_split_command_with_unicode(self): - assert split_command(u'echo μμ') == ['echo', 'μμ'] - - @pytest.mark.skipif(six.PY3, reason="shlex doesn't support bytes in py3") - def test_split_command_with_bytes(self): assert split_command('echo μμ') == ['echo', 'μμ'] @@ -549,6 +538,12 @@ class PortsTest(unittest.TestCase): assert internal_port == ["2000"] assert external_port == [("2001:abcd:ef00::2", "1000")] + def test_split_port_with_ipv6_square_brackets_address(self): + internal_port, external_port = split_port( + "[2001:abcd:ef00::2]:1000:2000") + assert internal_port == ["2000"] + assert external_port == [("2001:abcd:ef00::2", "1000")] + def test_split_port_invalid(self): with pytest.raises(ValueError): split_port("0.0.0.0:1000:2000:tcp") @@ -628,7 +623,7 @@ class FormatEnvironmentTest(unittest.TestCase): env_dict = { 'ARTIST_NAME': b'\xec\x86\xa1\xec\xa7\x80\xec\x9d\x80' } - assert format_environment(env_dict) == [u'ARTIST_NAME=송지은'] + assert format_environment(env_dict) == ['ARTIST_NAME=송지은'] def test_format_env_no_value(self): env_dict = { |