From cec3fe7c318fd22386ff5b18519609ab72db696b Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 13 Oct 2016 16:40:55 -0700 Subject: [PATCH 1/2] Update tests to avoid failures on Windows platforms Signed-off-by: Joffrey F --- tests/integration/build_test.py | 12 +-- tests/integration/conftest.py | 4 +- tests/integration/container_test.py | 45 +++++---- tests/integration/image_test.py | 8 +- tests/integration/network_test.py | 18 ++-- tests/unit/api_test.py | 5 +- tests/unit/container_test.py | 8 +- tests/unit/fake_api.py | 3 + tests/unit/utils_test.py | 140 +++++++++++++++++----------- 9 files changed, 141 insertions(+), 102 deletions(-) diff --git a/tests/integration/build_test.py b/tests/integration/build_test.py index 8dcbd571..699345fc 100644 --- a/tests/integration/build_test.py +++ b/tests/integration/build_test.py @@ -1,5 +1,4 @@ import io -import json import os import shutil import tempfile @@ -22,14 +21,11 @@ class BuildTest(BaseIntegrationTest): 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' ' /tmp/silence.tar.gz' ]).encode('ascii')) - stream = self.client.build(fileobj=script, stream=True) - logs = '' + stream = self.client.build(fileobj=script, stream=True, decode=True) + logs = [] for chunk in stream: - if six.PY3: - chunk = chunk.decode('utf-8') - json.loads(chunk) # ensure chunk is a single, valid JSON blob - logs += chunk - self.assertNotEqual(logs, '') + logs.append(chunk) + assert len(logs) > 0 def test_build_from_stringio(self): if six.PY3: diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index b0be9665..c488f906 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -1,6 +1,5 @@ from __future__ import print_function -import json import sys import warnings @@ -18,8 +17,7 @@ def setup_test_session(): c.inspect_image(BUSYBOX) except docker.errors.NotFound: print("\npulling {0}".format(BUSYBOX), file=sys.stderr) - for data in c.pull(BUSYBOX, stream=True): - data = json.loads(data.decode('utf-8')) + for data in c.pull(BUSYBOX, stream=True, decode=True): status = data.get("status") progress = data.get("progress") detail = "{0} - {1}".format(status, progress) diff --git a/tests/integration/container_test.py b/tests/integration/container_test.py index e703f180..5ced082d 100644 --- a/tests/integration/container_test.py +++ b/tests/integration/container_test.py @@ -3,6 +3,7 @@ import signal import tempfile import docker +from docker.constants import IS_WINDOWS_PLATFORM from docker.utils.socket import next_frame_size from docker.utils.socket import read_exactly import pytest @@ -523,13 +524,13 @@ class ArchiveTest(BaseIntegrationTest): def test_copy_file_to_container(self): data = b'Deaf To All But The Song' - with tempfile.NamedTemporaryFile() as test_file: + with tempfile.NamedTemporaryFile(delete=False) as test_file: test_file.write(data) test_file.seek(0) ctnr = self.client.create_container( BUSYBOX, 'cat {0}'.format( - os.path.join('/vol1', os.path.basename(test_file.name)) + os.path.join('/vol1/', os.path.basename(test_file.name)) ), volumes=['/vol1'] ) @@ -821,11 +822,12 @@ class KillTest(BaseIntegrationTest): self.assertEqual(state['Running'], False) def test_kill_with_signal(self): - container = self.client.create_container(BUSYBOX, ['sleep', '60']) - id = container['Id'] - self.client.start(id) + id = self.client.create_container(BUSYBOX, ['sleep', '60']) self.tmp_containers.append(id) - self.client.kill(id, signal=signal.SIGKILL) + self.client.start(id) + self.client.kill( + id, signal=signal.SIGKILL if not IS_WINDOWS_PLATFORM else 9 + ) exitcode = self.client.wait(id) self.assertNotEqual(exitcode, 0) container_info = self.client.inspect_container(id) @@ -901,28 +903,34 @@ class PortTest(BaseIntegrationTest): class ContainerTopTest(BaseIntegrationTest): def test_top(self): container = self.client.create_container( - BUSYBOX, ['sleep', '60']) + BUSYBOX, ['sleep', '60'] + ) - id = container['Id'] + self.tmp_containers.append(container) self.client.start(container) - res = self.client.top(container['Id']) - self.assertEqual( - res['Titles'], - ['UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD'] - ) - self.assertEqual(len(res['Processes']), 1) - self.assertEqual(res['Processes'][0][7], 'sleep 60') - self.client.kill(id) + res = self.client.top(container) + if IS_WINDOWS_PLATFORM: + assert res['Titles'] == ['PID', 'USER', 'TIME', 'COMMAND'] + else: + assert res['Titles'] == [ + 'UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD' + ] + assert len(res['Processes']) == 1 + assert res['Processes'][0][-1] == 'sleep 60' + self.client.kill(container) + @pytest.mark.skipif( + IS_WINDOWS_PLATFORM, reason='No psargs support on windows' + ) def test_top_with_psargs(self): container = self.client.create_container( BUSYBOX, ['sleep', '60']) - id = container['Id'] + self.tmp_containers.append(container) self.client.start(container) - res = self.client.top(container['Id'], 'waux') + res = self.client.top(container, 'waux') self.assertEqual( res['Titles'], ['USER', 'PID', '%CPU', '%MEM', 'VSZ', 'RSS', @@ -930,7 +938,6 @@ class ContainerTopTest(BaseIntegrationTest): ) self.assertEqual(len(res['Processes']), 1) self.assertEqual(res['Processes'][0][10], 'sleep 60') - self.client.kill(id) class RestartContainerTest(BaseIntegrationTest): diff --git a/tests/integration/image_test.py b/tests/integration/image_test.py index 84ddb4fa..31d2218b 100644 --- a/tests/integration/image_test.py +++ b/tests/integration/image_test.py @@ -55,12 +55,10 @@ class PullImageTest(BaseIntegrationTest): self.client.remove_image('hello-world') except docker.errors.APIError: pass - stream = self.client.pull('hello-world', stream=True) + stream = self.client.pull('hello-world', stream=True, decode=True) self.tmp_imgs.append('hello-world') for chunk in stream: - if six.PY3: - chunk = chunk.decode('utf-8') - json.loads(chunk) # ensure chunk is a single, valid JSON blob + assert isinstance(chunk, dict) self.assertGreaterEqual( len(self.client.images('hello-world')), 1 ) @@ -150,7 +148,7 @@ class ImportImageTest(BaseIntegrationTest): @contextlib.contextmanager def dummy_tar_file(self, n_bytes): '''Yields the name of a valid tar file of size n_bytes.''' - with tempfile.NamedTemporaryFile() as tar_file: + with tempfile.NamedTemporaryFile(delete=False) as tar_file: self.write_dummy_tar_content(n_bytes, tar_file) tar_file.seek(0) yield tar_file.name diff --git a/tests/integration/network_test.py b/tests/integration/network_test.py index ea5db06f..2ff5f029 100644 --- a/tests/integration/network_test.py +++ b/tests/integration/network_test.py @@ -72,15 +72,15 @@ class TestNetworks(BaseIntegrationTest): assert ipam['Driver'] == 'default' assert ipam['Config'] == [{ - 'Subnet': "172.28.0.0/16", - 'IPRange': "172.28.5.0/24", - 'Gateway': "172.28.5.254", - 'AuxiliaryAddresses': { - "a": "172.28.1.5", - "b": "172.28.1.6", - "c": "172.28.1.7", - }, - }] + 'Subnet': "172.28.0.0/16", + 'IPRange': "172.28.5.0/24", + 'Gateway': "172.28.5.254", + 'AuxiliaryAddresses': { + "a": "172.28.1.5", + "b": "172.28.1.6", + "c": "172.28.1.7", + }, + }] @requires_api_version('1.21') def test_create_network_with_host_driver_fails(self): diff --git a/tests/unit/api_test.py b/tests/unit/api_test.py index c9706fbb..94092dd2 100644 --- a/tests/unit/api_test.py +++ b/tests/unit/api_test.py @@ -86,7 +86,7 @@ def fake_delete(self, url, *args, **kwargs): def fake_read_from_socket(self, response, stream): return six.binary_type() -url_base = 'http+docker://localunixsocket/' +url_base = '{0}/'.format(fake_api.prefix) url_prefix = '{0}v{1}/'.format( url_base, docker.constants.DEFAULT_DOCKER_API_VERSION) @@ -422,6 +422,9 @@ class StreamTest(base.Cleanup, base.BaseTestCase): data += connection.recv(2048) + @pytest.mark.skipif( + docker.constants.IS_WINDOWS_PLATFORM, reason='Unix only' + ) def test_early_stream_response(self): self.request_handler = self.early_response_sending_handler lines = [] diff --git a/tests/unit/container_test.py b/tests/unit/container_test.py index 779ed699..51e8cbba 100644 --- a/tests/unit/container_test.py +++ b/tests/unit/container_test.py @@ -270,8 +270,8 @@ class CreateContainerTest(DockerClientTest): {'Content-Type': 'application/json'}) def test_create_container_with_cpu_shares(self): - self.client.create_container('busybox', 'ls', - cpu_shares=5) + with pytest.deprecated_call(): + self.client.create_container('busybox', 'ls', cpu_shares=5) args = fake_request.call_args self.assertEqual(args[0][1], @@ -316,8 +316,8 @@ class CreateContainerTest(DockerClientTest): {'Content-Type': 'application/json'}) def test_create_container_with_cpuset(self): - self.client.create_container('busybox', 'ls', - cpuset='0,1') + with pytest.deprecated_call(): + self.client.create_container('busybox', 'ls', cpuset='0,1') args = fake_request.call_args self.assertEqual(args[0][1], diff --git a/tests/unit/fake_api.py b/tests/unit/fake_api.py index 1e9d318d..65a8c424 100644 --- a/tests/unit/fake_api.py +++ b/tests/unit/fake_api.py @@ -408,6 +408,9 @@ def post_fake_update_container(): # Maps real api url to fake response callback prefix = 'http+docker://localunixsocket' +if constants.IS_WINDOWS_PLATFORM: + prefix = 'http+docker://localnpipe' + fake_responses = { '{0}/version'.format(prefix): get_fake_raw_version, diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index a06cbea3..290874f4 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -13,7 +13,9 @@ import pytest import six from docker.client import Client -from docker.constants import DEFAULT_DOCKER_API_VERSION +from docker.constants import ( + DEFAULT_DOCKER_API_VERSION, IS_WINDOWS_PLATFORM +) from docker.errors import DockerException, InvalidVersion from docker.utils import ( parse_repository_tag, parse_host, convert_filters, kwargs_from_env, @@ -809,6 +811,12 @@ class PortsTest(base.BaseTestCase): self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) +def convert_paths(collection): + if not IS_WINDOWS_PLATFORM: + return collection + return set(map(lambda x: x.replace('/', '\\'), collection)) + + class ExcludePathsTest(base.BaseTestCase): dirs = [ 'foo', @@ -843,7 +851,7 @@ class ExcludePathsTest(base.BaseTestCase): return set(exclude_paths(self.base, patterns, dockerfile=dockerfile)) def test_no_excludes(self): - assert self.exclude(['']) == self.all_paths + assert self.exclude(['']) == convert_paths(self.all_paths) def test_no_dupes(self): paths = exclude_paths(self.base, ['!a.py']) @@ -858,7 +866,9 @@ class ExcludePathsTest(base.BaseTestCase): Dockerfile and/or .dockerignore, don't exclude them from the actual tar file. """ - assert self.exclude(['Dockerfile', '.dockerignore']) == self.all_paths + assert self.exclude(['Dockerfile', '.dockerignore']) == convert_paths( + self.all_paths + ) def test_exclude_custom_dockerfile(self): """ @@ -877,94 +887,116 @@ class ExcludePathsTest(base.BaseTestCase): assert 'foo/a.py' not in includes def test_single_filename(self): - assert self.exclude(['a.py']) == self.all_paths - set(['a.py']) + assert self.exclude(['a.py']) == convert_paths( + self.all_paths - set(['a.py']) + ) def test_single_filename_leading_dot_slash(self): - assert self.exclude(['./a.py']) == self.all_paths - set(['a.py']) + assert self.exclude(['./a.py']) == convert_paths( + self.all_paths - set(['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/']) == self.all_paths - set(['a.py']) + assert self.exclude(['a.py/']) == convert_paths( + self.all_paths - set(['a.py']) + ) def test_wildcard_filename_start(self): - assert self.exclude(['*.py']) == self.all_paths - set([ - 'a.py', 'b.py', 'cde.py', - ]) + assert self.exclude(['*.py']) == convert_paths( + self.all_paths - set(['a.py', 'b.py', 'cde.py']) + ) def test_wildcard_with_exception(self): - assert self.exclude(['*.py', '!b.py']) == self.all_paths - set([ - 'a.py', 'cde.py', - ]) + assert self.exclude(['*.py', '!b.py']) == convert_paths( + self.all_paths - set(['a.py', 'cde.py']) + ) def test_wildcard_with_wildcard_exception(self): - assert self.exclude(['*.*', '!*.go']) == self.all_paths - set([ - 'a.py', 'b.py', 'cde.py', 'Dockerfile.alt', - ]) + assert self.exclude(['*.*', '!*.go']) == convert_paths( + self.all_paths - set([ + 'a.py', 'b.py', 'cde.py', 'Dockerfile.alt', + ]) + ) def test_wildcard_filename_end(self): - assert self.exclude(['a.*']) == self.all_paths - set(['a.py', 'a.go']) + assert self.exclude(['a.*']) == convert_paths( + self.all_paths - set(['a.py', 'a.go']) + ) def test_question_mark(self): - assert self.exclude(['?.py']) == self.all_paths - set(['a.py', 'b.py']) + assert self.exclude(['?.py']) == convert_paths( + self.all_paths - set(['a.py', 'b.py']) + ) def test_single_subdir_single_filename(self): - assert self.exclude(['foo/a.py']) == self.all_paths - set(['foo/a.py']) + assert self.exclude(['foo/a.py']) == convert_paths( + self.all_paths - set(['foo/a.py']) + ) def test_single_subdir_with_path_traversal(self): - assert self.exclude(['foo/whoops/../a.py']) == self.all_paths - set([ - 'foo/a.py', - ]) + assert self.exclude(['foo/whoops/../a.py']) == convert_paths( + self.all_paths - set(['foo/a.py']) + ) def test_single_subdir_wildcard_filename(self): - assert self.exclude(['foo/*.py']) == self.all_paths - set([ - 'foo/a.py', 'foo/b.py', - ]) + assert self.exclude(['foo/*.py']) == convert_paths( + self.all_paths - set(['foo/a.py', 'foo/b.py']) + ) def test_wildcard_subdir_single_filename(self): - assert self.exclude(['*/a.py']) == self.all_paths - set([ - 'foo/a.py', 'bar/a.py', - ]) + assert self.exclude(['*/a.py']) == convert_paths( + self.all_paths - set(['foo/a.py', 'bar/a.py']) + ) def test_wildcard_subdir_wildcard_filename(self): - assert self.exclude(['*/*.py']) == self.all_paths - set([ - 'foo/a.py', 'foo/b.py', 'bar/a.py', - ]) + assert self.exclude(['*/*.py']) == convert_paths( + self.all_paths - set(['foo/a.py', 'foo/b.py', 'bar/a.py']) + ) def test_directory(self): - assert self.exclude(['foo']) == self.all_paths - set([ - 'foo', 'foo/a.py', 'foo/b.py', - 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' - ]) + assert self.exclude(['foo']) == convert_paths( + self.all_paths - set([ + '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']) == self.all_paths - set([ - 'foo', 'foo/a.py', 'foo/b.py', - 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' - ]) + assert self.exclude(['foo']) == convert_paths( + self.all_paths - set([ + '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']) == self.all_paths - set([ - 'foo/a.py', 'foo/b.py', 'foo', 'foo/bar', - 'foo/Dockerfile3' - ]) + assert self.exclude(['foo', '!foo/bar/a.py']) == convert_paths( + self.all_paths - set([ + 'foo/a.py', 'foo/b.py', 'foo', 'foo/bar', + 'foo/Dockerfile3' + ]) + ) def test_directory_with_subdir_exception(self): - assert self.exclude(['foo', '!foo/bar']) == self.all_paths - set([ - 'foo/a.py', 'foo/b.py', 'foo', - 'foo/Dockerfile3' - ]) + assert self.exclude(['foo', '!foo/bar']) == convert_paths( + self.all_paths - set([ + 'foo/a.py', 'foo/b.py', 'foo', 'foo/Dockerfile3' + ]) + ) def test_directory_with_wildcard_exception(self): - assert self.exclude(['foo', '!foo/*.py']) == self.all_paths - set([ - 'foo/bar', 'foo/bar/a.py', 'foo', - 'foo/Dockerfile3' - ]) + assert self.exclude(['foo', '!foo/*.py']) == convert_paths( + self.all_paths - set([ + 'foo/bar', 'foo/bar/a.py', 'foo', 'foo/Dockerfile3' + ]) + ) def test_subdirectory(self): - assert self.exclude(['foo/bar']) == self.all_paths - set([ - 'foo/bar', 'foo/bar/a.py', - ]) + assert self.exclude(['foo/bar']) == convert_paths( + self.all_paths - set(['foo/bar', 'foo/bar/a.py']) + ) class TarTest(base.Cleanup, base.BaseTestCase): @@ -1023,6 +1055,7 @@ class TarTest(base.Cleanup, base.BaseTestCase): tar_data = tarfile.open(fileobj=archive) self.assertEqual(sorted(tar_data.getnames()), ['bar', 'foo']) + @pytest.mark.skipif(IS_WINDOWS_PLATFORM, reason='No symlinks on Windows') def test_tar_with_file_symlinks(self): base = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, base) @@ -1036,6 +1069,7 @@ class TarTest(base.Cleanup, base.BaseTestCase): sorted(tar_data.getnames()), ['bar', 'bar/foo', 'foo'] ) + @pytest.mark.skipif(IS_WINDOWS_PLATFORM, reason='No symlinks on Windows') def test_tar_with_directory_symlinks(self): base = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, base) From 9b35c74f0e7626e360dfa2f00202d23b8d08b7a5 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 13 Oct 2016 16:45:01 -0700 Subject: [PATCH 2/2] Fix dockerignore exclusion logic on Windows Signed-off-by: Joffrey F --- docker/utils/utils.py | 4 ++-- tests/integration/container_test.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docker/utils/utils.py b/docker/utils/utils.py index f2f7c268..d89aecf3 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -209,8 +209,8 @@ def match_path(path, pattern): if pattern: pattern = os.path.relpath(pattern) - pattern_components = pattern.split('/') - path_components = path.split('/')[:len(pattern_components)] + pattern_components = pattern.split(os.path.sep) + path_components = path.split(os.path.sep)[:len(pattern_components)] return fnmatch('/'.join(path_components), pattern) diff --git a/tests/integration/container_test.py b/tests/integration/container_test.py index 5ced082d..838ec364 100644 --- a/tests/integration/container_test.py +++ b/tests/integration/container_test.py @@ -414,6 +414,9 @@ class VolumeBindTest(BaseIntegrationTest): ['touch', os.path.join(self.mount_dest, self.filename)], ) + @pytest.mark.xfail( + IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform' + ) def test_create_with_binds_rw(self): container = self.run_with_volume( @@ -429,6 +432,9 @@ class VolumeBindTest(BaseIntegrationTest): inspect_data = self.client.inspect_container(container) self.check_container_data(inspect_data, True) + @pytest.mark.xfail( + IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform' + ) def test_create_with_binds_ro(self): self.run_with_volume( False,