import contextlib
import json
import os

import pkg_resources
import six
from dcos import package, subcommand
from dcos.errors import DCOSException

import pytest
from mock import patch

from .common import (assert_command, assert_lines, delete_zk_nodes,
                     exec_command, file_bytes, file_json, get_services,
                     service_shutdown, wait_for_service, watch_all_deployments)


@pytest.fixture(scope="module")
def zk_znode(request):
    request.addfinalizer(delete_zk_nodes)
    return request


def _chronos_description(app_ids):
    """
    :param app_ids: a list of application id
    :type app_ids: [str]
    :returns: a binary string representing the chronos description
    :rtype: str
    """

    result = [
        {"apps": app_ids,
         "description": "A fault tolerant job scheduler for Mesos which "
                        "handles dependencies and ISO8601 based schedules.",
         "framework": True,
         "images": {
             "icon-large": "https://downloads.mesosphere.io/chronos/assets/"
                           "icon-service-chronos-large.png",
             "icon-medium": "https://downloads.mesosphere.io/chronos/assets/"
                            "icon-service-chronos-medium.png",
             "icon-small": "https://downloads.mesosphere.io/chronos/assets/"
                           "icon-service-chronos-small.png"
         },
         "licenses": [
             {
                 "name": "Apache License Version 2.0",
                 "url": "https://github.com/mesos/chronos/blob/master/LICENSE"
             }
         ],
         "maintainer": "support@mesosphere.io",
         "name": "chronos",
         "packageSource": "https://github.com/mesosphere/universe/archive/\
cli-test-2.zip",
         "postInstallNotes": "Chronos DCOS Service has been successfully "
                             "installed!\n\n\tDocumentation: http://mesos."
                             "github.io/chronos\n\tIssues: https://github.com/"
                             "mesos/chronos/issues",
         "postUninstallNotes": "The Chronos DCOS Service has been uninstalled "
                               "and will no longer run.\nPlease follow the "
                               "instructions at http://docs.mesosphere."
                               "com/services/chronos/#uninstall to clean up "
                               "any persisted state",
         "preInstallNotes": "We recommend a minimum of one node with at least "
                            "1 CPU and 2GB of RAM available for the Chronos "
                            "Service.",
         "releaseVersion": "1",
         "scm": "https://github.com/mesos/chronos.git",
         "tags": [
             "cron",
             "analytics",
             "batch"
         ],
         "version": "2.4.0"
         }]

    return (json.dumps(result, sort_keys=True, indent=2).replace(' \n', '\n') +
            '\n').encode('utf-8')


def test_package():
    stdout = pkg_resources.resource_string(
        'tests',
        'data/package/help.txt')
    assert_command(['dcos', 'package', '--help'],
                   stdout=stdout)


def test_info():
    assert_command(['dcos', 'package', '--info'],
                   stdout=b'Install and manage DCOS packages\n')


def test_version():
    assert_command(['dcos', 'package', '--version'],
                   stdout=b'dcos-package version SNAPSHOT\n')


def test_sources_list():
    stdout = b"1a9bef0c579dd0692af9c6ba22c3ec910fb03efc " + \
             b"https://github.com/mesosphere/universe/archive/cli-test-2.zip\n"
    assert_command(['dcos', 'package', 'sources'],
                   stdout=stdout)


def test_update_without_validation():
    returncode, stdout, stderr = exec_command(['dcos', 'package', 'update'])

    assert returncode == 0
    assert b'source' in stdout
    assert b'Validating package definitions...' not in stdout
    assert b'OK' not in stdout
    assert stderr == b''


def test_update_with_validation():
    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'update', '--validate'])

    assert returncode == 0
    assert b'source' in stdout
    assert b'Validating package definitions...' in stdout
    assert b'OK' in stdout
    assert stderr == b''


def test_describe_nonexistent():
    assert_command(['dcos', 'package', 'describe', 'xyzzy'],
                   stderr=b'Package [xyzzy] not found\n',
                   returncode=1)


def test_describe_nonexistent_version():
    stderr = b'Version a.b.c of package [marathon] is not available\n'
    assert_command(['dcos', 'package', 'describe', 'marathon',
                    '--package-version=a.b.c'],
                   stderr=stderr,
                   returncode=1)


def test_describe():
    stdout = file_json(
        'tests/data/package/json/test_describe_marathon.json')
    assert_command(['dcos', 'package', 'describe', 'marathon'],
                   stdout=stdout)


def test_describe_cli():
    stdout = file_json(
        'tests/data/package/json/test_describe_cli_cassandra.json')
    assert_command(['dcos', 'package', 'describe', 'cassandra', '--cli'],
                   stdout=stdout)


def test_describe_app():
    stdout = file_bytes(
        'tests/data/package/json/test_describe_app_marathon.json')
    assert_command(['dcos', 'package', 'describe', 'marathon', '--app'],
                   stdout=stdout)


def test_describe_config():
    stdout = file_json(
        'tests/data/package/json/test_describe_marathon_config.json')
    assert_command(['dcos', 'package', 'describe', 'marathon', '--config'],
                   stdout=stdout)


def test_describe_render():
    stdout = file_json(
        'tests/data/package/json/test_describe_marathon_app_render.json')
    assert_command(
        ['dcos', 'package', 'describe', 'marathon', '--app', '--render'],
        stdout=stdout)


def test_describe_package_version():
    stdout = file_json(
        'tests/data/package/json/test_describe_marathon_package_version.json')
    assert_command(
        ['dcos', 'package', 'describe', 'marathon', '--package-version=0.8.1'],
        stdout=stdout)


def test_describe_package_version_missing():
    stderr = b'Version bogus of package [marathon] is not available\n'
    assert_command(
        ['dcos', 'package', 'describe', 'marathon', '--package-version=bogus'],
        returncode=1,
        stderr=stderr)


def test_describe_package_versions():
    stdout = file_bytes(
        'tests/data/package/json/test_describe_marathon_package_versions.json')
    assert_command(
        ['dcos', 'package', 'describe', 'marathon', '--package-versions'],
        stdout=stdout)


def test_describe_package_versions_others():
    stderr = (b'If --package-versions is provided, no other option can be '
              b'provided\n')
    assert_command(
        ['dcos', 'package', 'describe', 'marathon', '--package-versions',
         '--app'],
        returncode=1,
        stderr=stderr)


def test_describe_options():
    stdout = file_json(
        'tests/data/package/json/test_describe_app_options.json')
    assert_command(['dcos', 'package', 'describe', '--app', '--options',
                    'tests/data/package/marathon.json', 'marathon'],
                   stdout=stdout)


def test_describe_app_cli():
    stdout = file_bytes(
        'tests/data/package/json/test_describe_app_cli.json')
    assert_command(
        ['dcos', 'package', 'describe', 'cassandra', '--app', '--cli'],
        stdout=stdout)


def test_describe_specific_version():
    stdout = file_bytes(
        'tests/data/package/json/test_describe_marathon_0.8.1.json')
    assert_command(['dcos', 'package', 'describe', '--package-version=0.8.1',
                    'marathon'],
                   stdout=stdout)


def test_bad_install():
    args = ['--options=tests/data/package/chronos-bad.json', '--yes']
    stderr = b"""Error: False is not of type 'string'
Path: chronos.zk-hosts
Value: false

Please create a JSON file with the appropriate options, and pass the \
/path/to/file as an --options argument.
"""

    _install_chronos(args=args,
                     returncode=1,
                     stdout=b'',
                     stderr=stderr,
                     postInstallNotes=b'')


def test_install(zk_znode):
    _install_chronos()
    watch_all_deployments()
    wait_for_service('chronos')
    _uninstall_chronos()
    watch_all_deployments()
    services = get_services(args=['--inactive'])
    assert len([service for service in services
                if service['name'] == 'chronos']) == 0


def test_install_missing_options_file():
    """Test that a missing options file results in the expected stderr
    message."""
    assert_command(
        ['dcos', 'package', 'install', 'chronos', '--yes',
         '--options=asdf.json'],
        returncode=1,
        stderr=b"Error opening file [asdf.json]: No such file or directory\n")


def test_install_specific_version():
    stdout = (b'We recommend a minimum of one node with at least 2 '
              b'CPU\'s and 1GB of RAM available for the Marathon Service.\n'
              b'Installing Marathon app for package [marathon] '
              b'version [0.8.1]\n'
              b'Marathon DCOS Service has been successfully installed!\n\n'
              b'\tDocumentation: https://mesosphere.github.io/marathon\n'
              b'\tIssues: https:/github.com/mesosphere/marathon/issues\n\n')

    uninstall_stderr = (
        b'Uninstalled package [marathon] version [0.8.1]\n'
        b'The Marathon DCOS Service has been uninstalled and will no longer '
        b'run.\nPlease follow the instructions at http://docs.mesosphere.com/'
        b'services/marathon/#uninstall to clean up any persisted state\n'
    )

    with _package('marathon',
                  stdout=stdout,
                  uninstall_stderr=uninstall_stderr,
                  args=['--yes', '--package-version=0.8.1']):

        returncode, stdout, stderr = exec_command(
            ['dcos', 'package', 'list', 'marathon', '--json'])
        assert returncode == 0
        assert stderr == b''
        assert json.loads(stdout.decode('utf-8'))[0]['version'] == "0.8.1"


def test_install_bad_package_version():
    stderr = b'Version a.b.c of package [cassandra] is not available\n'
    assert_command(
        ['dcos', 'package', 'install', 'cassandra',
         '--package-version=a.b.c'],
        returncode=1,
        stderr=stderr)


def test_package_metadata():
    _install_helloworld()

    # test marathon labels
    expected_metadata = b"""eyJkZXNjcmlwdGlvbiI6ICJFeGFtcGxlIERDT1MgYXBwbGljYX\
Rpb24gcGFja2FnZSIsICJtYWludGFpbmVyIjogInN1cHBvcnRAbWVzb3NwaGVyZS5pbyIsICJuYW1l\
IjogImhlbGxvd29ybGQiLCAicG9zdEluc3RhbGxOb3RlcyI6ICJBIHNhbXBsZSBwb3N0LWluc3RhbG\
xhdGlvbiBtZXNzYWdlIiwgInByZUluc3RhbGxOb3RlcyI6ICJBIHNhbXBsZSBwcmUtaW5zdGFsbGF0\
aW9uIG1lc3NhZ2UiLCAidGFncyI6IFsibWVzb3NwaGVyZSIsICJleGFtcGxlIiwgInN1YmNvbW1hbm\
QiXSwgInZlcnNpb24iOiAiMC4xLjAiLCAid2Vic2l0ZSI6ICJodHRwczovL2dpdGh1Yi5jb20vbWVz\
b3NwaGVyZS9kY29zLWhlbGxvd29ybGQifQ=="""

    expected_command = b"""eyJwaXAiOiBbImRjb3M8MS4wIiwgImdpdCtodHRwczovL2dpdGh\
1Yi5jb20vbWVzb3NwaGVyZS9kY29zLWhlbGxvd29ybGQuZ2l0I2Rjb3MtaGVsbG93b3JsZD0wLjEuM\
CJdfQ=="""

    expected_source = b"""https://github.com/mesosphere/universe/archive/\
cli-test-2.zip"""

    expected_labels = {
        'DCOS_PACKAGE_METADATA': expected_metadata,
        'DCOS_PACKAGE_COMMAND': expected_command,
        'DCOS_PACKAGE_REGISTRY_VERSION': b'1.0.0-rc1',
        'DCOS_PACKAGE_NAME': b'helloworld',
        'DCOS_PACKAGE_VERSION': b'0.1.0',
        'DCOS_PACKAGE_SOURCE': expected_source,
        'DCOS_PACKAGE_RELEASE': b'0',
    }

    app_labels = _get_app_labels('helloworld')

    for label, value in expected_labels.items():
        assert value == six.b(app_labels.get(label))

    # test local package.json
    package = {
        "description": "Example DCOS application package",
        "maintainer": "support@mesosphere.io",
        "name": "helloworld",
        "postInstallNotes": "A sample post-installation message",
        "preInstallNotes": "A sample pre-installation message",
        "tags": ["mesosphere", "example", "subcommand"],
        "version": "0.1.0",
        "website": "https://github.com/mesosphere/dcos-helloworld",
    }

    package_dir = subcommand.package_dir('helloworld')

    # test local package.json
    package_path = os.path.join(package_dir, 'package.json')
    with open(package_path) as f:
        assert json.load(f) == package

    # test local source
    source_path = os.path.join(package_dir, 'source')
    with open(source_path) as f:
        assert six.b(f.read()) == expected_source

    # test local version
    version_path = os.path.join(package_dir, 'version')
    with open(version_path) as f:
        assert six.b(f.read()) == b'0'

    # uninstall helloworld
    _uninstall_helloworld()


def test_install_with_id(zk_znode):
    args = ['--app-id=chronos-1', '--yes']
    stdout = (b'Installing Marathon app for package [chronos] version [2.4.0] '
              b'with app id [chronos-1]\n')
    _install_chronos(args=args, stdout=stdout)

    args = ['--app-id=chronos-2', '--yes']
    stdout = (b'Installing Marathon app for package [chronos] version [2.4.0] '
              b'with app id [chronos-2]\n')
    _install_chronos(args=args, stdout=stdout)


def test_install_missing_package():
    stderr = b"""Package [missing-package] not found
You may need to run 'dcos package update' to update your repositories
"""
    assert_command(['dcos', 'package', 'install', 'missing-package'],
                   returncode=1,
                   stderr=stderr)


def test_uninstall_with_id(zk_znode):
    _uninstall_chronos(args=['--app-id=chronos-1'])


def test_uninstall_all(zk_znode):
    _uninstall_chronos(args=['--all'])
    get_services(expected_count=1, args=['--inactive'])


def test_uninstall_missing():
    stderr = 'Package [chronos] is not installed.\n'
    _uninstall_chronos(returncode=1, stderr=stderr)

    stderr = 'Package [chronos] with id [chronos-1] is not installed.\n'
    _uninstall_chronos(
        args=['--app-id=chronos-1'],
        returncode=1,
        stderr=stderr)


def test_uninstall_subcommand():
    _install_helloworld()
    _uninstall_helloworld()
    _list()


def test_uninstall_cli():
    _install_helloworld()
    _uninstall_helloworld(args=['--cli'])

    stdout = b"""[
  {
    "apps": [
      "/helloworld"
    ],
    "description": "Example DCOS application package",
    "maintainer": "support@mesosphere.io",
    "name": "helloworld",
    "packageSource": "https://github.com/mesosphere/universe/archive/\
cli-test-2.zip",
    "postInstallNotes": "A sample post-installation message",
    "preInstallNotes": "A sample pre-installation message",
    "releaseVersion": "0",
    "tags": [
      "mesosphere",
      "example",
      "subcommand"
    ],
    "version": "0.1.0",
    "website": "https://github.com/mesosphere/dcos-helloworld"
  }
]
"""
    _list(stdout=stdout)
    _uninstall_helloworld()


def test_uninstall_multiple_apps():
    stdout = (b'A sample pre-installation message\n'
              b'Installing Marathon app for package [helloworld] version '
              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')

    _install_helloworld(['--yes', '--app-id=/helloworld-1'],
                        stdout=stdout)

    stdout = (b'A sample pre-installation message\n'
              b'Installing Marathon app for package [helloworld] version '
              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')

    _install_helloworld(['--yes', '--app-id=/helloworld-2'],
                        stdout=stdout)

    stderr = (b"Multiple apps named [helloworld] are installed: "
              b"[/helloworld-1, /helloworld-2].\n"
              b"Please use --app-id to specify the ID of the app "
              b"to uninstall, or use --all to uninstall all apps.\n")
    _uninstall_helloworld(stderr=stderr,
                          returncode=1)

    assert_command(['dcos', 'package', 'uninstall', 'helloworld', '--all'])

    watch_all_deployments()


def test_list(zk_znode):
    _list()
    _list(args=['xyzzy', '--json'])
    _list(args=['--app-id=/xyzzy', '--json'])

    _install_chronos()
    expected_output = _chronos_description(['/chronos'])

    _list(stdout=expected_output)
    _list(args=['--json', 'chronos'],
          stdout=expected_output)
    _list(args=['--json', '--app-id=/chronos'],
          stdout=expected_output)
    _list(args=['--json', 'ceci-nest-pas-une-package'])
    _list(args=['--json', '--app-id=/ceci-nest-pas-une-package'])

    _uninstall_chronos()


def test_list_table():
    with _helloworld():
        assert_lines(['dcos', 'package', 'list'], 2)


def test_install_yes():
    with open('tests/data/package/assume_yes.txt') as yes_file:
        _install_helloworld(
            args=[],
            stdin=yes_file,
            stdout=b'A sample pre-installation message\n'
                   b'Continue installing? [yes/no] '
                   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 helloworld\n'
                   b'A sample post-installation message\n')
        _uninstall_helloworld()


def test_install_no():
    with open('tests/data/package/assume_no.txt') as no_file:
        _install_helloworld(
            args=[],
            stdin=no_file,
            stdout=b'A sample pre-installation message\n'
                   b'Continue installing? [yes/no] Exiting installation.\n')


def test_list_cli():
    _install_helloworld()

    stdout = b"""\
[
  {
    "apps": [
      "/helloworld"
    ],
    "command": {
      "name": "helloworld"
    },
    "description": "Example DCOS application package",
    "maintainer": "support@mesosphere.io",
    "name": "helloworld",
    "packageSource": "https://github.com/mesosphere/universe/archive/\
cli-test-2.zip",
    "postInstallNotes": "A sample post-installation message",
    "preInstallNotes": "A sample pre-installation message",
    "releaseVersion": "0",
    "tags": [
      "mesosphere",
      "example",
      "subcommand"
    ],
    "version": "0.1.0",
    "website": "https://github.com/mesosphere/dcos-helloworld"
  }
]
"""
    _list(stdout=stdout)
    _uninstall_helloworld()

    stdout = (b"A sample pre-installation message\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")
    _install_helloworld(args=['--cli', '--yes'], stdout=stdout)

    stdout = b"""\
[
  {
    "command": {
      "name": "helloworld"
    },
    "description": "Example DCOS application package",
    "maintainer": "support@mesosphere.io",
    "name": "helloworld",
    "packageSource": "https://github.com/mesosphere/universe/archive/\
cli-test-2.zip",
    "postInstallNotes": "A sample post-installation message",
    "preInstallNotes": "A sample pre-installation message",
    "releaseVersion": "0",
    "tags": [
      "mesosphere",
      "example",
      "subcommand"
    ],
    "version": "0.1.0",
    "website": "https://github.com/mesosphere/dcos-helloworld"
  }
]
"""
    _list(stdout=stdout)
    _uninstall_helloworld()


def test_uninstall_multiple_frameworknames(zk_znode):
    _install_chronos(
        args=['--yes', '--options=tests/data/package/chronos-1.json'])
    _install_chronos(
        args=['--yes', '--options=tests/data/package/chronos-2.json'])

    watch_all_deployments()

    expected_output = _chronos_description(
        ['/chronos-user-1', '/chronos-user-2'])

    _list(stdout=expected_output)
    _list(args=['--json', 'chronos'], stdout=expected_output)
    _list(args=['--json', '--app-id=/chronos-user-1'],
          stdout=_chronos_description(['/chronos-user-1']))
    _list(args=['--json', '--app-id=/chronos-user-2'],
          stdout=_chronos_description(['/chronos-user-2']))
    _uninstall_chronos(
        args=['--app-id=chronos-user-1'],
        returncode=1,
        stderr='Uninstalled package [chronos] version [2.4.0]\n'
               'The Chronos DCOS Service has been uninstalled and will no '
               'longer run.\nPlease follow the instructions at http://docs.'
               'mesosphere.com/services/chronos/#uninstall to clean up any '
               'persisted state\n'
               'Unable to shutdown the framework for [chronos-user] because '
               'there are multiple frameworks with the same name: ')
    _uninstall_chronos(
        args=['--app-id=chronos-user-2'],
        returncode=1,
        stderr='Uninstalled package [chronos] version [2.4.0]\n'
               'The Chronos DCOS Service has been uninstalled and will no '
               'longer run.\nPlease follow the instructions at http://docs.'
               'mesosphere.com/services/chronos/#uninstall to clean up any '
               'persisted state\n'
               'Unable to shutdown the framework for [chronos-user] because '
               'there are multiple frameworks with the same name: ')

    for framework in get_services(args=['--inactive']):
        if framework['name'] == 'chronos-user':
            service_shutdown(framework['id'])


def test_search():
    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'search', 'cron', '--json'])

    assert returncode == 0
    assert b'chronos' in stdout
    assert stderr == b''

    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'search', 'xyzzy', '--json'])

    assert returncode == 0
    assert b'"packages": []' in stdout
    assert b'"source": "https://github.com/mesosphere/universe/archive/\
cli-test-2.zip"' in stdout
    assert stderr == b''

    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'search', 'xyzzy'])

    assert returncode == 1
    assert b'' == stdout
    assert stderr == b'No packages found.\n'

    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'search', '--json'])

    registries = json.loads(stdout.decode('utf-8'))
    for registry in registries:
        # assert the number of packages is gte the number at the time
        # this test was written
        assert len(registry['packages']) >= 5

    assert returncode == 0
    assert stderr == b''


def test_search_table():
    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'search'])

    assert returncode == 0
    assert b'chronos' in stdout
    assert len(stdout.decode('utf-8').split('\n')) > 5
    assert stderr == b''


def test_search_ends_with_wildcard():
    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'search', 'c*', '--json'])

    assert returncode == 0
    assert b'chronos' in stdout
    assert b'cassandra' in stdout
    assert stderr == b''

    registries = json.loads(stdout.decode('utf-8'))
    for registry in registries:
        assert len(registry['packages']) == 2


def test_search_start_with_wildcard():
    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'search', '*nos', '--json'])

    assert returncode == 0
    assert b'chronos' in stdout
    assert stderr == b''

    registries = json.loads(stdout.decode('utf-8'))
    for registry in registries:
        assert len(registry['packages']) == 1


def test_search_middle_with_wildcard():
    returncode, stdout, stderr = exec_command(
        ['dcos', 'package', 'search', 'c*s', '--json'])

    assert returncode == 0
    assert b'chronos' in stdout
    assert stderr == b''

    registries = json.loads(stdout.decode('utf-8'))
    for registry in registries:
        assert len(registry['packages']) == 1


@patch('dcos.package.Package.package_json')
@patch('dcos.package.Package.config_json')
def test_bad_config_schema_msg(config_mock, package_mock):
    pkg = package.Package("", "/")
    config_mock.return_value = {}
    package_mock.return_value = {'maintainer': 'support@test'}

    with pytest.raises(DCOSException) as e:
        pkg.options("1", {})

    msg = ("An object in the package's config.json is missing the "
           "required 'properties' feature:\n {}"
           "\nPlease contact the project maintainer: support@test")
    assert e.exconly().split(':', 1)[1].strip() == msg


def _get_app_labels(app_id):
    returncode, stdout, stderr = exec_command(
        ['dcos', 'marathon', 'app', 'show', app_id])

    assert returncode == 0
    assert stderr == b''

    app_json = json.loads(stdout.decode('utf-8'))
    return app_json.get('labels')


def _install_helloworld(
        args=['--yes'],
        stdout=b'A sample pre-installation message\n'
               b'Installing Marathon app for package [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',
        returncode=0,
        stdin=None):
    assert_command(
        ['dcos', 'package', 'install', 'helloworld'] + args,
        stdout=stdout,
        returncode=returncode,
        stdin=stdin)


def _uninstall_helloworld(
        args=[],
        stdout=b'',
        stderr=b'',
        returncode=0):
    assert_command(['dcos', 'package', 'uninstall', 'helloworld'] + args,
                   stdout=stdout,
                   stderr=stderr,
                   returncode=returncode)


def _uninstall_chronos(args=[], returncode=0, stdout=b'', stderr=''):
    result_returncode, result_stdout, result_stderr = exec_command(
        ['dcos', 'package', 'uninstall', 'chronos'] + args)

    assert result_returncode == returncode
    assert result_stdout == stdout
    assert result_stderr.decode('utf-8').startswith(stderr)


def _install_chronos(
        args=['--yes'],
        returncode=0,
        stdout=b'Installing Marathon app for package [chronos] '
               b'version [2.4.0]\n',
        stderr=b'',
        preInstallNotes=b'We recommend a minimum of one node with at least 1 '
                        b'CPU and 2GB of RAM available for the Chronos '
                        b'Service.\n',
        postInstallNotes=b'Chronos DCOS Service has been successfully '
                         b'''installed!

\tDocumentation: http://mesos.github.io/chronos
\tIssues: https://github.com/mesos/chronos/issues\n''',
        stdin=None):

    cmd = ['dcos', 'package', 'install', 'chronos'] + args
    assert_command(
        cmd,
        returncode,
        preInstallNotes + stdout + postInstallNotes,
        stderr,
        stdin=stdin)


def _list(args=['--json'],
          stdout=b'[]\n'):
    assert_command(['dcos', 'package', 'list'] + args,
                   stdout=stdout)


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
'''
    return _package('helloworld',
                    stdout=stdout)


@contextlib.contextmanager
def _package(name,
             stdout=b'',
             uninstall_stderr=b'',
             args=['--yes']):
    """Context manager that installs a package on entrace, and uninstalls it on
    exit.

    :param name: package name
    :type name: str
    :param stdout: Expected stdout
    :type stdout: str
    :param uninstall_stderr: Expected stderr
    :type uninstall_stderr: str
    :param args: extra CLI args
    :type args: [str]
    :rtype: None
    """

    assert_command(['dcos', 'package', 'install', name] + args,
                   stdout=stdout)
    try:
        yield
    finally:
        assert_command(
            ['dcos', 'package', 'uninstall', name],
            stderr=uninstall_stderr)
