Merge pull request #548 from mesosphere/test-cleanup

cleanup unit/integration test seperation
This commit is contained in:
tamarrow
2016-03-31 13:32:45 -07:00
15 changed files with 194 additions and 205 deletions

View File

@@ -1,14 +1,10 @@
#!/bin/bash
# This script expects the following env var:
# CLI_TEST_SSH_KEY_PATH
set -e
set -o pipefail
set -x
# run tests
DCOS_PRODUCTION=false \
CLI_TEST_MASTER_PROXY=true \
CLI_TEST_SSH_KEY_PATH=/dcos-cli/mesosphere-aws.key \
./bin/start_tests.sh

View File

@@ -1,8 +1,8 @@
import subprocess
from concurrent.futures import ThreadPoolExecutor
import dcoscli
import docopt
from concurrent.futures import ThreadPoolExecutor
from dcos import cmds, emitting, options, subcommand, util
from dcos.errors import DCOSException
from dcoscli.subcommand import (default_command_documentation,

View File

@@ -1,10 +1,10 @@
import os
import signal
import sys
from concurrent.futures import ThreadPoolExecutor
import dcoscli
import docopt
from concurrent.futures import ThreadPoolExecutor
from dcos import (auth, constants, emitting, errors, http, mesos, subcommand,
util)
from dcos.errors import DCOSAuthenticationException, DCOSException

View File

@@ -4,14 +4,12 @@ import json
import os
import pty
import subprocess
import sys
import time
import requests
import six
from dcos import util
import mock
from six.moves import urllib
@@ -86,85 +84,6 @@ def assert_command(
assert stderr_ == stderr
def exec_mock(main, args):
"""Call a main function with sys.args mocked, and capture
stdout/stderr
:param main: main function to call
:type main: function
:param args: sys.args to mock, excluding the initial 'dcos'
:type args: [str]
:returns: (returncode, stdout, stderr)
:rtype: (int, bytes, bytes)
"""
print('MOCK ARGS: {}'.format(' '.join(args)))
with mock_args(args) as (stdout, stderr):
returncode = main(args)
stdout_val = six.b(stdout.getvalue())
stderr_val = six.b(stderr.getvalue())
print('STDOUT: {}'.format(stdout_val))
print('STDERR: {}'.format(stderr_val))
return (returncode, stdout_val, stderr_val)
def assert_mock(main,
args,
returncode=0,
stdout=b'',
stderr=b''):
"""Mock and call a main function, and assert expected behavior.
:param main: main function to call
:type main: function
:param args: sys.args to mock, excluding the initial 'dcos'
:type args: [str]
:type returncode: int
:param stdout: Expected stdout
:type stdout: str
:param stderr: Expected stderr
:type stderr: str
:rtype: None
"""
returncode_, stdout_, stderr_ = exec_mock(main, args)
assert returncode_ == returncode
assert stdout_ == stdout
assert stderr_ == stderr
def mock_called_some_args(mock, *args, **kwargs):
"""Convience method for some mock assertions. Returns True if the
arguments to one of the calls of `mock` contains `args` and
`kwargs`.
:param mock: the mock to check
:type mock: mock.Mock
:returns: True if the arguments to one of the calls for `mock`
contains `args` and `kwargs`.
:rtype: bool
"""
for call in mock.call_args_list:
call_args, call_kwargs = call
if any(arg not in call_args for arg in args):
continue
if any(k not in call_kwargs or call_kwargs[k] != v
for k, v in kwargs.items()):
continue
return True
return False
def watch_deployment(deployment_id, count):
""" Wait for a deployment to complete.
@@ -498,23 +417,6 @@ def package(package_name, deploy=False, args=[]):
watch_all_deployments()
@contextlib.contextmanager
def mock_args(args):
""" Context manager that mocks sys.args and captures stdout/stderr
:param args: sys.args values to mock
:type args: [str]
:rtype: None
"""
with mock.patch('sys.argv', [util.which('dcos')] + args):
stdout, stderr = sys.stdout, sys.stderr
sys.stdout, sys.stderr = six.StringIO(), six.StringIO()
try:
yield sys.stdout, sys.stderr
finally:
sys.stdout, sys.stderr = stdout, stderr
def popen_tty(cmd):
"""Open a process with stdin connected to a pseudo-tty. Returns a

View File

@@ -1,7 +1,6 @@
import json
import os
import dcoscli.constants as cli_constants
import six
from dcos import constants
@@ -16,7 +15,6 @@ def env():
r.update({
constants.PATH_ENV: os.environ[constants.PATH_ENV],
constants.DCOS_CONFIG_ENV: os.path.join("tests", "data", "dcos.toml"),
cli_constants.DCOS_PRODUCTION_ENV: 'false'
})
return r

View File

@@ -1,6 +1,5 @@
import os
import dcoscli.constants as cli_constants
from dcos import constants
import pytest
@@ -15,7 +14,6 @@ def env():
constants.PATH_ENV: os.environ[constants.PATH_ENV],
constants.DCOS_CONFIG_ENV: os.path.join("tests",
"data", "ssl", "ssl.toml"),
cli_constants.DCOS_PRODUCTION_ENV: 'false'
})
return r

View File

@@ -7,15 +7,10 @@ import subprocess
import time
import dcos.util as util
from dcos import mesos
from dcos.errors import DCOSException
from dcos.util import create_schema
from dcoscli.task.main import main
from mock import MagicMock, patch
from ..fixtures.task import task_fixture
from .common import (add_app, app, assert_command, assert_lines, assert_mock,
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'
@@ -229,39 +224,6 @@ def test_log_completed():
assert len(stdout.decode('utf-8').split('\n')) > 4
def test_log_master_unavailable():
""" Test master's state.json being unavailable """
client = mesos.DCOSClient()
client.get_master_state = _mock_exception()
with patch('dcos.mesos.DCOSClient', return_value=client):
args = ['task', 'log', '_']
assert_mock(main, args, returncode=1, stderr=(b"exception\n"))
def test_log_slave_unavailable():
""" Test slave's state.json being unavailable """
client = mesos.DCOSClient()
client.get_slave_state = _mock_exception()
with patch('dcos.mesos.DCOSClient', return_value=client):
args = ['task', 'log', 'test-app1']
stderr = (b"""Error accessing slave: exception\n"""
b"""No matching tasks. Exiting.\n""")
assert_mock(main, args, returncode=1, stderr=stderr)
def test_log_file_unavailable():
""" Test a file's read.json being unavailable """
files = [mesos.MesosFile('bogus')]
files[0].read = _mock_exception('exception')
with patch('dcoscli.task.main._mesos_files', return_value=files):
args = ['task', 'log', 'test-app1']
stderr = b"No files exist. Exiting.\n"
assert_mock(main, args, returncode=1, stderr=stderr)
def test_ls():
stdout = b'stderr stderr.logrotate.conf stdout stdout.logrotate.conf\n'
assert_command(['dcos', 'task', 'ls', 'test-app1'],
@@ -294,10 +256,6 @@ def test_ls_bad_path():
returncode=1)
def _mock_exception(contents='exception'):
return MagicMock(side_effect=DCOSException(contents))
def _mark_non_blocking(file_):
fcntl.fcntl(file_.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)

102
cli/tests/unit/common.py Normal file
View File

@@ -0,0 +1,102 @@
import contextlib
import sys
import six
import mock
@contextlib.contextmanager
def mock_args(args):
""" Context manager that mocks sys.args and captures stdout/stderr
:param args: sys.args values to mock
:type args: [str]
:rtype: None
"""
with mock.patch('sys.argv', ['dcos'] + args):
stdout, stderr = sys.stdout, sys.stderr
sys.stdout, sys.stderr = six.StringIO(), six.StringIO()
try:
yield sys.stdout, sys.stderr
finally:
sys.stdout, sys.stderr = stdout, stderr
def mock_called_some_args(mock, *args, **kwargs):
"""Convience method for some mock assertions. Returns True if the
arguments to one of the calls of `mock` contains `args` and
`kwargs`.
:param mock: the mock to check
:type mock: mock.Mock
:returns: True if the arguments to one of the calls for `mock`
contains `args` and `kwargs`.
:rtype: bool
"""
for call in mock.call_args_list:
call_args, call_kwargs = call
if any(arg not in call_args for arg in args):
continue
if any(k not in call_kwargs or call_kwargs[k] != v
for k, v in kwargs.items()):
continue
return True
return False
def exec_mock(main, args):
"""Call a main function with sys.args mocked, and capture
stdout/stderr
:param main: main function to call
:type main: function
:param args: sys.args to mock, excluding the initial 'dcos'
:type args: [str]
:returns: (returncode, stdout, stderr)
:rtype: (int, bytes, bytes)
"""
print('MOCK ARGS: {}'.format(' '.join(args)))
with mock_args(args) as (stdout, stderr):
returncode = main(args)
stdout_val = six.b(stdout.getvalue())
stderr_val = six.b(stderr.getvalue())
print('STDOUT: {}'.format(stdout_val))
print('STDERR: {}'.format(stderr_val))
return (returncode, stdout_val, stderr_val)
def assert_mock(main,
args,
returncode=0,
stdout=b'',
stderr=b''):
"""Mock and call a main function, and assert expected behavior.
:param main: main function to call
:type main: function
:param args: sys.args to mock, excluding the initial 'dcos'
:type args: [str]
:type returncode: int
:param stdout: Expected stdout
:type stdout: str
:param stderr: Expected stderr
:type stderr: str
:rtype: None
"""
returncode_, stdout_, stderr_ = exec_mock(main, args)
assert returncode_ == returncode
assert stdout_ == stdout
assert stderr_ == stderr

View File

@@ -3,10 +3,10 @@ from functools import wraps
import dcoscli.analytics
import rollbar
from dcos import constants, http, util
from dcoscli.config.main import main as config_main
from dcos import constants, http
from dcoscli.constants import SEGMENT_URL
from dcoscli.main import main
from dcoscli.subcommand import SubcommandMain
from mock import patch
@@ -33,33 +33,29 @@ def _mock(fn):
@_mock
def test_config_set():
'''Tests that a `dcos config set core.email <email>` makes a
segment.io identify call'''
argv = ['set', 'core.email', 'test@mail.com']
argv = ['config', 'set', 'core.email', 'test@mail.com']
env = _env_reporting()
config_thread = SubcommandMain("config", argv)
exitcode, err = config_thread.run_and_capture()
assert exitcode == 0
assert err is None
with patch.dict(os.environ, env):
assert config_main(argv) == 0
# segment.io
assert mock_called_some_args(http.post,
'{}/identify'.format(SEGMENT_URL),
json={'userId': 'test@mail.com'},
timeout=(1, 1))
# segment.io
assert mock_called_some_args(http.post,
'{}/identify'.format(SEGMENT_URL),
json={'userId': 'test@mail.com'},
timeout=(1, 1))
@_mock
def test_cluster_id_not_sent():
'''Tests that cluster_id is sent to segment.io'''
def test_cluster_id_not_sent_on_config_call():
"""Tests that cluster_id is not sent to segment.io on call to config
subcommand
"""
args = [util.which('dcos'), 'config', 'show']
env = _env_reporting_with_url()
version = 'release'
args = ['dcos', 'config', 'show']
with patch('sys.argv', args), \
patch.dict(os.environ, env), \
patch('dcoscli.version', version), \
patch('dcos.mesos.DCOSClient.metadata') as get_cluster_id:
assert main() == 0
@@ -73,7 +69,7 @@ def test_no_exc():
'''
args = [util.which('dcos')]
args = ['dcos']
env = _env_reporting()
version = 'release'
@@ -92,7 +88,7 @@ def test_exc():
'''
args = [util.which('dcos')]
args = ['dcos']
env = _env_reporting()
version = 'release'
with patch('sys.argv', args), \
@@ -107,11 +103,16 @@ def test_exc():
assert rollbar.report_message.call_count == 1
def _env_reporting():
path = os.path.join('tests', 'data', 'analytics', 'dcos_reporting.toml')
return {constants.DCOS_CONFIG_ENV: path}
@_mock
def test_config_reporting_false():
'''Test that "core.reporting = false" blocks exception reporting.'''
args = [util.which('dcos')]
args = ['dcos']
env = _env_no_reporting()
version = 'release'
@@ -126,17 +127,6 @@ def test_config_reporting_false():
assert track.call_count == 0
def _env_reporting():
path = os.path.join('tests', 'data', 'analytics', 'dcos_reporting.toml')
return {constants.DCOS_CONFIG_ENV: path}
def _env_no_reporting():
path = os.path.join('tests', 'data', 'analytics', 'dcos_no_reporting.toml')
return {constants.DCOS_CONFIG_ENV: path}
def _env_reporting_with_url():
path = os.path.join('tests', 'data', 'analytics',
'dcos_reporting_with_url.toml')
return {constants.DCOS_CONFIG_ENV: path}

View File

@@ -14,23 +14,15 @@ def test_no_browser_auth():
assert op.call_count == 0
def test_when_authenticated():
with patch('dcos.auth.force_auth'):
_mock_dcos_run([util.which('dcos')], True)
assert auth.force_auth.call_count == 0
def test_anonymous_login():
with patch('sys.stdin.readline', return_value='\n'), \
patch('uuid.uuid1', return_value='anonymous@email'):
assert _mock_dcos_run([util.which('dcos'),
'help'], False) == 0
assert _mock_dcos_run([util.which('dcos'), 'config',
'show', 'core.email'], False) == 0
assert _mock_dcos_run([util.which('dcos'), 'config',
'unset', 'core.email'], False) == 0
assert _mock_dcos_run(['dcos', 'help'], False) == 0
assert _mock_dcos_run(['dcos',
'config', 'show', 'core.email'], False) == 0
assert _mock_dcos_run(['dcos',
'config', 'unset', 'core.email'], False) == 0
def _mock_dcos_run(args, authenticated=True):
@@ -43,6 +35,13 @@ def _mock_dcos_run(args, authenticated=True):
return main()
def test_when_authenticated():
with patch('dcos.auth.force_auth'):
_mock_dcos_run(['dcos'], True)
assert auth.force_auth.call_count == 0
def _config_with_credentials():
return {
constants.DCOS_CONFIG_ENV: os.path.join(

View File

@@ -0,0 +1,46 @@
from dcos import mesos
from dcos.errors import DCOSException
from dcoscli.log import log_files
from dcoscli.task.main import main
import pytest
from mock import MagicMock, patch
from .common import assert_mock
def test_log_master_unavailable():
""" Test master's state.json being unavailable """
client = mesos.DCOSClient()
client.get_master_state = _mock_exception()
with patch('dcos.mesos.DCOSClient', return_value=client):
args = ['task', 'log', '_']
assert_mock(main, args, returncode=1, stderr=(b"exception\n"))
def test_log_no_tasks():
""" Test slave's state.json being unavailable """
with patch('dcos.mesos.DCOSClient.get_master_state', return_value={}), \
patch('dcos.mesos.DCOSClient.get_master_state', return_value={}), \
patch('dcos.mesos.Master.tasks', return_value={}):
stderr = b"""No matching tasks. Exiting.\n"""
args = ['task', 'log', 'test-app-1']
assert_mock(main, args, returncode=1, stderr=stderr)
def test_log_file_unavailable():
""" Test a file's read.json being unavailable """
files = [mesos.MesosFile('bogus')]
files[0].read = _mock_exception('exception')
with pytest.raises(DCOSException) as e:
log_files(files, True, 10)
msg = "No files exist. Exiting."
assert e.exconly().split(':', 1)[1].strip() == msg
def _mock_exception(contents='exception'):
return MagicMock(side_effect=DCOSException(contents))

View File

@@ -1,17 +1,17 @@
[tox]
envlist = syntax, py{27,34}-unit, py{27,34}-integration
envlist = py34-syntax, py{27,34}-unit, py{27,34}-integration
[testenv]
deps =
teamcity-messages
mock
pytest
pytest-cov
mock
pytz
-e..
passenv = DCOS_* CI_FLAGS CLI_TEST_SSH_KEY_PATH CLI_TEST_MASTER_PROXY
[testenv:syntax]
[testenv:py34-syntax]
deps =
teamcity-messages
flake8

View File

@@ -1,4 +1,5 @@
import collections
import concurrent.futures
import contextlib
import functools
import json
@@ -11,7 +12,6 @@ import sys
import tempfile
import time
import concurrent.futures
import jsonschema
import png
import pystache

View File

@@ -1,5 +1,5 @@
[tox]
envlist = syntax, py{27,34}-unit
envlist = py34-syntax, py{27,34}-unit
[testenv]
deps =
@@ -8,7 +8,7 @@ deps =
pytest-cov
passenv = DCOS_* CI_FLAGS CLI_TEST_SSH_KEY_PATH
[testenv:syntax]
[testenv:py34-syntax]
deps =
teamcity-messages
flake8