From 18dd1f0d45cc6c3b7adb71eab2d4e140e69e522a Mon Sep 17 00:00:00 2001 From: tamarrow Date: Fri, 24 Jun 2016 18:41:17 -0700 Subject: [PATCH] make tests pass on windows (#618) --- cli/bin/test-binary.sh | 7 +-- cli/dcoscli/subcommand.py | 4 +- cli/tests/integrations/common.py | 3 +- cli/tests/integrations/test_marathon.py | 3 ++ cli/tests/integrations/test_node.py | 15 +++++++ cli/tests/integrations/test_package.py | 60 +++++++++++++++---------- cli/tests/integrations/test_service.py | 31 ++++++++++--- cli/tests/integrations/test_ssl.py | 6 ++- cli/tests/integrations/test_task.py | 13 ++++-- dcos/config.py | 20 ++++++--- 10 files changed, 117 insertions(+), 45 deletions(-) diff --git a/cli/bin/test-binary.sh b/cli/bin/test-binary.sh index ebea814..952c57c 100755 --- a/cli/bin/test-binary.sh +++ b/cli/bin/test-binary.sh @@ -5,11 +5,12 @@ BASEDIR=`dirname $0`/.. cd $BASEDIR PATH=$(pwd)/dist:$PATH -cp tests/data/dcos.toml $DCOS_CONFIG if [ -f "$BASEDIR/env/bin/activate" ]; then - source $BASEDIR/env/bin/activate + cp tests/data/dcos.toml $DCOS_CONFIG + source $BASEDIR/env/bin/activate else - $BASEDIR/env/Scripts/activate + export DCOS_CONFIG=tests/data/dcos.toml + $BASEDIR/env/Scripts/activate fi py.test tests/integrations deactivate diff --git a/cli/dcoscli/subcommand.py b/cli/dcoscli/subcommand.py index 8e97168..5f31d1c 100644 --- a/cli/dcoscli/subcommand.py +++ b/cli/dcoscli/subcommand.py @@ -42,7 +42,7 @@ def default_command_info(command): """ doc = default_command_documentation(command) - return doc.split('\n')[1].strip(".").lstrip() + return doc.splitlines()[1].strip(".").lstrip() def default_command_documentation(command): @@ -54,7 +54,7 @@ def default_command_documentation(command): :rtype: str """ - return default_doc(command).rstrip('\n') + return default_doc(command).rstrip('\r\n') class SubcommandMain(): diff --git a/cli/tests/integrations/common.py b/cli/tests/integrations/common.py index cda523f..b4bd404 100644 --- a/cli/tests/integrations/common.py +++ b/cli/tests/integrations/common.py @@ -2,7 +2,6 @@ import collections import contextlib import json import os -import pty import subprocess import time @@ -466,6 +465,8 @@ def popen_tty(cmd): :rtype: (Popen, int) """ + + import pty master, slave = pty.openpty() proc = subprocess.Popen(cmd, stdin=slave, diff --git a/cli/tests/integrations/test_marathon.py b/cli/tests/integrations/test_marathon.py index b61a2e3..92084b7 100644 --- a/cli/tests/integrations/test_marathon.py +++ b/cli/tests/integrations/test_marathon.py @@ -2,6 +2,7 @@ import contextlib import json import os import re +import sys import threading from dcos import constants @@ -695,6 +696,8 @@ def test_app_locked_error(): b'Override with --force.\n')) +@pytest.mark.skipif(sys.platform == 'win32', + reason="No pseudo terminal on windows") def test_app_add_no_tty(): proc, master = popen_tty('dcos marathon app add') diff --git a/cli/tests/integrations/test_node.py b/cli/tests/integrations/test_node.py index 535abcd..86dfdcc 100644 --- a/cli/tests/integrations/test_node.py +++ b/cli/tests/integrations/test_node.py @@ -1,12 +1,15 @@ import json import os import re +import sys import dcos.util as util import six from dcos import mesos from dcos.util import create_schema +import pytest + from ..fixtures.node import slave_fixture from .common import assert_command, assert_lines, exec_command, ssh_output @@ -91,15 +94,21 @@ def test_node_log_invalid_lines(): returncode=1) +@pytest.mark.skipif(sys.platform == 'win32', + reason='No pseudo terminal on windows') def test_node_ssh_leader(): _node_ssh(['--leader']) +@pytest.mark.skipif(sys.platform == 'win32', + reason='No pseudo terminal on windows') def test_node_ssh_slave(): slave_id = mesos.DCOSClient().get_state_summary()['slaves'][0]['id'] _node_ssh(['--mesos-id={}'.format(slave_id), '--master-proxy']) +@pytest.mark.skipif(sys.platform == 'win32', + reason='No pseudo terminal on windows') def test_node_ssh_option(): stdout, stderr, _ = _node_ssh_output( ['--leader', '--option', 'Protocol=0']) @@ -107,6 +116,8 @@ def test_node_ssh_option(): assert b'ignoring bad proto spec' in stderr +@pytest.mark.skipif(sys.platform == 'win32', + reason='No pseudo terminal on windows') def test_node_ssh_config_file(): stdout, stderr, _ = _node_ssh_output( ['--leader', '--config-file', 'tests/data/node/ssh_config']) @@ -114,6 +125,8 @@ def test_node_ssh_config_file(): assert b'ignoring bad proto spec' in stderr +@pytest.mark.skipif(sys.platform == 'win32', + reason='No pseudo terminal on windows') def test_node_ssh_user(): stdout, stderr, _ = _node_ssh_output( ['--master-proxy', '--leader', '--user=bogus', '--option', @@ -137,6 +150,8 @@ def test_node_ssh_master_proxy_no_agent(): env=env) +@pytest.mark.skipif(sys.platform == 'win32', + reason='No pseudo terminal on windows') def test_node_ssh_master_proxy(): _node_ssh(['--leader', '--master-proxy']) diff --git a/cli/tests/integrations/test_package.py b/cli/tests/integrations/test_package.py index 6acf917..310ffb1 100644 --- a/cli/tests/integrations/test_package.py +++ b/cli/tests/integrations/test_package.py @@ -1,8 +1,8 @@ import base64 import contextlib import json +import sys -import pkg_resources import six from dcos import subcommand @@ -39,11 +39,9 @@ def zk_znode(request): def test_package(): - stdout = pkg_resources.resource_string( - 'tests', - 'data/help/package.txt') - assert_command(['dcos', 'package', '--help'], - stdout=stdout) + with open('tests/data/help/package.txt') as content: + assert_command(['dcos', 'package', '--help'], + stdout=content.read().encode('utf-8')) def test_info(): @@ -316,8 +314,9 @@ def test_bad_install_marathon_msg(): b'[0.1.0] with app id [/foo]\n' b'Installing CLI subcommand for package [helloworld] ' b'version [0.1.0]\n' - b'New command available: dcos helloworld\n' - b'A sample post-installation message\n') + b'New command available: dcos ' + + _executable_name(b'helloworld') + + b'\nA sample post-installation message\n') _install_helloworld(['--yes', '--app-id=/foo'], stdout=stdout) @@ -552,8 +551,9 @@ def test_uninstall_multiple_apps(): b'[0.1.0] with app id [/helloworld-1]\n' b'Installing CLI subcommand for package [helloworld] ' b'version [0.1.0]\n' - b'New command available: dcos helloworld\n' - b'A sample post-installation message\n') + b'New command available: dcos ' + + _executable_name(b'helloworld') + + b'\nA sample post-installation message\n') _install_helloworld(['--yes', '--app-id=/helloworld-1'], stdout=stdout) @@ -563,8 +563,9 @@ def test_uninstall_multiple_apps(): b'[0.1.0] with app id [/helloworld-2]\n' b'Installing CLI subcommand for package [helloworld] ' b'version [0.1.0]\n' - b'New command available: dcos helloworld\n' - b'A sample post-installation message\n') + b'New command available: dcos ' + + _executable_name(b'helloworld') + + b'\nA sample post-installation message\n') _install_helloworld(['--yes', '--app-id=/helloworld-2'], stdout=stdout) @@ -620,8 +621,9 @@ def test_install_yes(): b'[0.1.0]\n' b'Installing CLI subcommand for package [helloworld] ' b'version [0.1.0]\n' - b'New command available: dcos helloworld\n' - b'A sample post-installation message\n') + b'New command available: dcos ' + + _executable_name(b'helloworld') + + b'\nA sample post-installation message\n') _uninstall_helloworld() @@ -664,7 +666,9 @@ def test_list_cli(): stdout = (b"Installing CLI subcommand for package [helloworld] " + b"version [0.1.0]\n" - b"New command available: dcos helloworld\n") + b"New command available: dcos " + + _executable_name(b'helloworld') + + b"\n") _install_helloworld(args=['--cli', '--yes'], stdout=stdout) stdout = b"""\ @@ -828,6 +832,13 @@ def _get_app_labels(app_id): return app_json.get('labels') +def _executable_name(name): + if sys.platform == 'win32': + return name + b'.exe' + else: + return name + + def _install_helloworld( args=['--yes'], stdout=b'A sample pre-installation message\n' @@ -835,8 +846,9 @@ def _install_helloworld( b'version [0.1.0]\n' b'Installing CLI subcommand for package [helloworld] ' b'version [0.1.0]\n' - b'New command available: dcos helloworld\n' - b'A sample post-installation message\n', + b'New command available: dcos ' + + _executable_name(b'helloworld') + + b'\nA sample post-installation message\n', stderr=b'', returncode=0, stdin=None): @@ -937,12 +949,14 @@ def _list_remove_nulls(args=['--json'], stdout=b'[]\n'): def _helloworld(): - stdout = b'''A sample pre-installation message -Installing Marathon app for package [helloworld] version [0.1.0] -Installing CLI subcommand for package [helloworld] version [0.1.0] -New command available: dcos helloworld -A sample post-installation message -''' + stdout = (b'A sample pre-installation message\n' + b'Installing Marathon app for package [helloworld] version ' + b'[0.1.0]\n' + b'Installing CLI subcommand for package [helloworld] ' + b'version [0.1.0]\n' + b'New command available: dcos ' + + _executable_name(b'helloworld') + + b'\nA sample post-installation message\n') stderr = b'Uninstalled package [helloworld] version [0.1.0]\n' return _package('helloworld', diff --git a/cli/tests/integrations/test_service.py b/cli/tests/integrations/test_service.py index 8260234..205789f 100644 --- a/cli/tests/integrations/test_service.py +++ b/cli/tests/integrations/test_service.py @@ -1,5 +1,7 @@ import os +import signal import subprocess +import sys import time import dcos.util as util @@ -161,6 +163,8 @@ def test_log_marathon_file(): returncode=1) +@pytest.mark.skipif(sys.platform == 'win32', + reason='No pseduo terminal on windows') def test_log_marathon_config(): stdout, stderr, _ = ssh_output( 'dcos service log marathon ' + @@ -197,16 +201,33 @@ def test_log_config(): def test_log_follow(): wait_for_service('chronos') - proc = subprocess.Popen(['dcos', 'service', 'log', 'chronos', '--follow'], - preexec_fn=os.setsid, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + args = ['dcos', 'service', 'log', 'chronos', '--follow'] + if sys.platform == 'win32': + proc = subprocess.Popen( + args, + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + else: + # os.setsid is only available for Unix: + # https://docs.python.org/2/library/os.html#os.setsid + proc = subprocess.Popen( + args, + preexec_fn=os.setsid, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) time.sleep(10) proc.poll() assert proc.returncode is None - os.killpg(os.getpgid(proc.pid), 15) + if sys.platform == 'win32': + os.kill(proc.pid, signal.CTRL_BREAK_EVENT) + else: + # using Unix-only commands os.killpg + os.getgid + # https://docs.python.org/2/library/os.html#os.killpg + # https://docs.python.org/2/library/os.html#os.getpgid + os.killpg(os.getpgid(proc.pid), 15) stdout = proc.stdout.read() stderr = proc.stderr.read() diff --git a/cli/tests/integrations/test_ssl.py b/cli/tests/integrations/test_ssl.py index f4c34ea..4b1921e 100644 --- a/cli/tests/integrations/test_ssl.py +++ b/cli/tests/integrations/test_ssl.py @@ -13,6 +13,8 @@ def env(): r.update({ constants.PATH_ENV: os.environ[constants.PATH_ENV], constants.DCOS_CONFIG_ENV: os.path.join("tests", "data", "dcos.toml"), + 'DCOS_SNAKEOIL_CRT_PATH': os.environ.get( + "DCOS_SNAKEOIL_CRT_PATH", "/dcos-cli/adminrouter/snakeoil.crt") }) return r @@ -86,7 +88,7 @@ def test_verify_ssl_with_bad_cert_config(env): def test_verify_ssl_with_good_cert_env_var(env): - env['DCOS_SSL_VERIFY'] = '/dcos-cli/adminrouter/snakeoil.crt' + env['DCOS_SSL_VERIFY'] = env['DCOS_SNAKEOIL_CRT_PATH'] with update_config('core.ssl_verify', None, env): returncode, stdout, stderr = exec_command( @@ -99,7 +101,7 @@ def test_verify_ssl_with_good_cert_env_var(env): def test_verify_ssl_with_good_cert_config(env): with update_config( - 'core.ssl_verify', '/dcos-cli/adminrouter/snakeoil.crt', env): + 'core.ssl_verify', env['DCOS_SNAKEOIL_CRT_PATH'], env): returncode, stdout, stderr = exec_command( ['dcos', 'marathon', 'app', 'list'], env) assert returncode == 0 diff --git a/cli/tests/integrations/test_task.py b/cli/tests/integrations/test_task.py index baef6b7..6e9b49c 100644 --- a/cli/tests/integrations/test_task.py +++ b/cli/tests/integrations/test_task.py @@ -1,17 +1,19 @@ import collections -import fcntl import json import os import re import subprocess +import sys import time import dcos.util as util from dcos.util import create_schema +import pytest + from ..fixtures.task import task_fixture -from .common import (add_app, app, assert_command, assert_lines, - exec_command, remove_app, watch_all_deployments) +from .common import (add_app, app, assert_command, assert_lines, exec_command, + remove_app, watch_all_deployments) SLEEP_COMPLETED = 'tests/data/marathon/apps/sleep-completed.json' SLEEP_COMPLETED1 = 'tests/data/marathon/apps/sleep-completed1.json' @@ -144,6 +146,8 @@ def test_log_lines_invalid(): returncode=1) +@pytest.mark.skipif(sys.platform == 'win32', + reason="Using Windows unsupported import (fcntl)") def test_log_follow(): """ Test --follow """ # verify output @@ -180,6 +184,8 @@ def test_log_two_tasks(): assert re.match('===>.*<===', lines[5]) +@pytest.mark.skipif(sys.platform == 'win32', + reason='Using Windows unsupported import (fcntl)') def test_log_two_tasks_follow(): """ Test tailing a single file on two separate tasks with --follow """ with app(TWO_TASKS_FOLLOW, 'two-tasks-follow'): @@ -288,6 +294,7 @@ def test_ls_completed(): def _mark_non_blocking(file_): + import fcntl fcntl.fcntl(file_.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) diff --git a/dcos/config.py b/dcos/config.py index 86aa2a5..4353aa4 100644 --- a/dcos/config.py +++ b/dcos/config.py @@ -3,6 +3,7 @@ import copy import json import os import stat +import sys import pkg_resources import toml @@ -160,12 +161,19 @@ def _enforce_config_permissions(path): :type path: str :rtype: None """ - permissions = oct(stat.S_IMODE(os.lstat(path).st_mode)) - if permissions not in ['0o600', '0600']: - msg = ("Permissions '{}' for configuration file '{}' are too open. " - "File must only be accessible by owner. " - "Aborting...".format(permissions, path)) - raise DCOSException(msg) + + # Unix permissions are incompatible with windows + # TODO: https://github.com/dcos/dcos-cli/issues/662 + if sys.platform == 'win32': + return + else: + permissions = oct(stat.S_IMODE(os.lstat(path).st_mode)) + if permissions not in ['0o600', '0600']: + msg = ( + "Permissions '{}' for configuration file '{}' are too open. " + "File must only be accessible by owner. " + "Aborting...".format(permissions, path)) + raise DCOSException(msg) def load_from_path(path, mutable=False):