<commit_before># -*- coding: utf-8 -*-

import os
import json
import pytest

from click.testing import CliRunner

from cookiecutter.__main__ import main
from cookiecutter.main import cookiecutter
from cookiecutter import utils, config

runner = CliRunner()

@pytest.fixture
def remove_fake_project_dir(request):
    """
    Remove the fake project directory created during the tests.
    """
    def fin_remove_fake_project_dir():
        if os.path.isdir('fake-project'):
            utils.rmtree('fake-project')
    request.addfinalizer(fin_remove_fake_project_dir)

@pytest.fixture
def make_fake_project_dir(request):
    """Create a fake project to be overwritten in the according tests."""
    os.makedirs('fake-project')

@pytest.fixture(params=['-V', '--version'])
def version_cli_flag(request):
    return request.param

def test_cli_version(version_cli_flag):
    result = runner.invoke(main, [version_cli_flag])
    assert result.exit_code == 0
    assert result.output.startswith('Cookiecutter')

@pytest.mark.usefixtures('make_fake_project_dir', 'remove_fake_project_dir')
def test_cli_error_on_existing_output_directory():
    result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input'])
    assert result.exit_code != 0
    expected_error_msg = 'Error: "fake-project" directory already exists\n'
    assert result.output == expected_error_msg

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli():
    result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input'])
    assert result.exit_code == 0
    assert os.path.isdir('fake-project')
    with open(os.path.join('fake-project', 'README.rst')) as f:
        assert 'Project name: **Fake Project**' in f.read()

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_verbose():
    result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input', '-v'])
    assert result.exit_code == 0
    assert os.path.isdir('fake-project')
    with open(os.path.join('fake-project', 'README.rst')) as f:
        assert 'Project name: **Fake Project**' in f.read()

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_replay(mocker):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = runner.invoke(main, [
        template_path,
        '--replay',
        '-v'
    ])

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=True,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=config.USER_CONFIG_PATH,
        extra_context=None
    )

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_exit_on_noinput_and_replay(mocker):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter',
        side_effect=cookiecutter
    )

    template_path = 'tests/fake-repo-pre/'
    result = runner.invoke(main, [
        template_path,
        '--no-input',
        '--replay',
        '-v'
    ])

    assert result.exit_code == 1

    expected_error_msg = (
        "You can not use both replay and no_input or extra_context "
        "at the same time."
    )

    assert expected_error_msg in result.output

    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        True,
        replay=True,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=config.USER_CONFIG_PATH,
        extra_context=None
    )

@pytest.fixture(params=['-f', '--overwrite-if-exists'])
def overwrite_cli_flag(request):
    return request.param

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_run_cookiecutter_on_overwrite_if_exists_and_replay(
        mocker, overwrite_cli_flag):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter',
        side_effect=cookiecutter
    )

    template_path = 'tests/fake-repo-pre/'
    result = runner.invoke(main, [
        template_path,
        '--replay',
        '-v',
        overwrite_cli_flag,
    ])

    assert result.exit_code == 0

    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=True,
        overwrite_if_exists=True,
        output_dir='.',
        config_file=config.USER_CONFIG_PATH,
        extra_context=None
    )

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_overwrite_if_exists_when_output_dir_does_not_exist(
        overwrite_cli_flag):
    result = runner.invoke(main, [
        'tests/fake-repo-pre/', '--no-input', overwrite_cli_flag
    ])

    assert result.exit_code == 0
    assert os.path.isdir('fake-project')

@pytest.mark.usefixtures('make_fake_project_dir', 'remove_fake_project_dir')
def test_cli_overwrite_if_exists_when_output_dir_exists(overwrite_cli_flag):
    result = runner.invoke(main, [
        'tests/fake-repo-pre/', '--no-input', overwrite_cli_flag
    ])
    assert result.exit_code == 0
    assert os.path.isdir('fake-project')

@pytest.fixture(params=['-o', '--output-dir'])
def output_dir_flag(request):
    return request.param

@pytest.fixture
def output_dir(tmpdir):
    return str(tmpdir.mkdir('output'))

def test_cli_output_dir(mocker, output_dir_flag, output_dir):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = runner.invoke(main, [
        template_path,
        output_dir_flag,
        output_dir
    ])

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=False,
        overwrite_if_exists=False,
        output_dir=output_dir,
        config_file=config.USER_CONFIG_PATH,
        extra_context=None
    )

@pytest.fixture(params=['-h', '--help', 'help'])
def help_cli_flag(request):
    return request.param

def test_cli_help(help_cli_flag):
    result = runner.invoke(main, [help_cli_flag])
    assert result.exit_code == 0
    assert result.output.startswith('Usage')

@pytest.fixture
def user_config_path(tmpdir):
    return str(tmpdir.join('tests/config.yaml'))

def test_user_config(mocker, user_config_path):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = runner.invoke(main, [
        template_path,
        '--config-file',
        user_config_path
    ])

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=False,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=user_config_path,
        extra_context=None
    )

def test_default_user_config_overwrite(mocker, user_config_path):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = runner.invoke(main, [
        template_path,
        '--config-file',
        user_config_path,
        '--default-config'
    ])

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=False,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=None,
        extra_context=None
    )

def test_default_user_config(mocker):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = runner.invoke(main, [
        template_path,
        '--default-config'
    ])

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=False,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=None,
        extra_context=None
    )

def test_echo_undefined_variable_error(tmpdir):
    output_dir = str(tmpdir.mkdir('output'))
    template_path = 'tests/undefined-variable/file-name/'

    result = runner.invoke(main, [
        '--no-input',
        '--default-config',
        '--output-dir',
        output_dir,
        template_path,
    ])

    assert result.exit_code == 1

    error = "Unable to create file '{{cookiecutter.foobar}}'"
    assert error in result.output

    message = "Error message: 'dict object' has no attribute 'foobar'"
    assert message in result.output

    context = {
        'cookiecutter': {
            'github_username': 'hackebrot',
            'project_slug': 'testproject'
        }
    }
    context_str = json.dumps(context, indent=4, sort_keys=True)
    assert context_str in result.output

def test_echo_unknown_extension_error(tmpdir):
    output_dir = str(tmpdir.mkdir('output'))
    template_path = 'tests/test-extensions/unknown/'

    result = runner.invoke(main, [
        '--no-input',
        '--default-config',
        '--output-dir',
        output_dir,
        template_path,
    ])

    assert result.exit_code == 1

    assert 'Unable to load extension: ' in result.output

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_extra_context():
    result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input', '-v',
                                  'project_name=Awesomez'])
    assert result.exit_code == 0
    assert os.path.isdir('fake-project')
    with open(os.path.join('fake-project', 'README.rst')) as f:
        assert 'Project name: **Awesomez**' in f.read()

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_extra_context_invalid_format():
    result = runner.invoke(main, ['tests/fake-repo-pre/', '--no-input', '-v',
                                  'ExtraContextWithNoEqualsSoInvalid'])
    assert result.exit_code == 2
    assert 'Error: Invalid value for "extra_context"' in result.output
    assert 'should contain items of the form key=value' in result.output
<commit_msg>Implement cli_runner fixture for test_cli.py<commit_after># -*- coding: utf-8 -*-

import os
import json

from click.testing import CliRunner
import pytest

from cookiecutter.__main__ import main
from cookiecutter.main import cookiecutter
from cookiecutter import utils, config

@pytest.fixture(scope='session')
def cli_runner():
    """Fixture that returns a helper function to run the cookiecutter cli."""
    runner = CliRunner()

    def cli_main(*cli_args):
        """Run cookiecutter cli main with the given args."""
        return runner.invoke(main, cli_args)

    return cli_main

@pytest.fixture
def remove_fake_project_dir(request):
    """
    Remove the fake project directory created during the tests.
    """
    def fin_remove_fake_project_dir():
        if os.path.isdir('fake-project'):
            utils.rmtree('fake-project')
    request.addfinalizer(fin_remove_fake_project_dir)

@pytest.fixture
def make_fake_project_dir(request):
    """Create a fake project to be overwritten in the according tests."""
    os.makedirs('fake-project')

@pytest.fixture(params=['-V', '--version'])
def version_cli_flag(request):
    return request.param

def test_cli_version(cli_runner, version_cli_flag):
    result = cli_runner(version_cli_flag)
    assert result.exit_code == 0
    assert result.output.startswith('Cookiecutter')

@pytest.mark.usefixtures('make_fake_project_dir', 'remove_fake_project_dir')
def test_cli_error_on_existing_output_directory(cli_runner):
    result = cli_runner('tests/fake-repo-pre/', '--no-input')
    assert result.exit_code != 0
    expected_error_msg = 'Error: "fake-project" directory already exists\n'
    assert result.output == expected_error_msg

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli(cli_runner):
    result = cli_runner('tests/fake-repo-pre/', '--no-input')
    assert result.exit_code == 0
    assert os.path.isdir('fake-project')
    with open(os.path.join('fake-project', 'README.rst')) as f:
        assert 'Project name: **Fake Project**' in f.read()

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_verbose(cli_runner):
    result = cli_runner('tests/fake-repo-pre/', '--no-input', '-v')
    assert result.exit_code == 0
    assert os.path.isdir('fake-project')
    with open(os.path.join('fake-project', 'README.rst')) as f:
        assert 'Project name: **Fake Project**' in f.read()

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_replay(mocker, cli_runner):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = cli_runner(template_path, '--replay', '-v')

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=True,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=config.USER_CONFIG_PATH,
        extra_context=None
    )

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_exit_on_noinput_and_replay(mocker, cli_runner):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter',
        side_effect=cookiecutter
    )

    template_path = 'tests/fake-repo-pre/'
    result = cli_runner(template_path, '--no-input', '--replay', '-v')

    assert result.exit_code == 1

    expected_error_msg = (
        "You can not use both replay and no_input or extra_context "
        "at the same time."
    )

    assert expected_error_msg in result.output

    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        True,
        replay=True,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=config.USER_CONFIG_PATH,
        extra_context=None
    )

@pytest.fixture(params=['-f', '--overwrite-if-exists'])
def overwrite_cli_flag(request):
    return request.param

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_run_cookiecutter_on_overwrite_if_exists_and_replay(
        mocker, cli_runner, overwrite_cli_flag):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter',
        side_effect=cookiecutter
    )

    template_path = 'tests/fake-repo-pre/'
    result = cli_runner(template_path, '--replay', '-v', overwrite_cli_flag)

    assert result.exit_code == 0

    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=True,
        overwrite_if_exists=True,
        output_dir='.',
        config_file=config.USER_CONFIG_PATH,
        extra_context=None
    )

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_overwrite_if_exists_when_output_dir_does_not_exist(
        cli_runner, overwrite_cli_flag):

    result = cli_runner(
        'tests/fake-repo-pre/',
        '--no-input',
        overwrite_cli_flag,
    )

    assert result.exit_code == 0
    assert os.path.isdir('fake-project')

@pytest.mark.usefixtures('make_fake_project_dir', 'remove_fake_project_dir')
def test_cli_overwrite_if_exists_when_output_dir_exists(
        cli_runner, overwrite_cli_flag):

    result = cli_runner(
        'tests/fake-repo-pre/',
        '--no-input',
        overwrite_cli_flag,
    )
    assert result.exit_code == 0
    assert os.path.isdir('fake-project')

@pytest.fixture(params=['-o', '--output-dir'])
def output_dir_flag(request):
    return request.param

@pytest.fixture
def output_dir(tmpdir):
    return str(tmpdir.mkdir('output'))

def test_cli_output_dir(mocker, cli_runner, output_dir_flag, output_dir):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = cli_runner(template_path, output_dir_flag, output_dir)

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=False,
        overwrite_if_exists=False,
        output_dir=output_dir,
        config_file=config.USER_CONFIG_PATH,
        extra_context=None
    )

@pytest.fixture(params=['-h', '--help', 'help'])
def help_cli_flag(request):
    return request.param

def test_cli_help(cli_runner, help_cli_flag):
    result = cli_runner(help_cli_flag)
    assert result.exit_code == 0
    assert result.output.startswith('Usage')

@pytest.fixture
def user_config_path(tmpdir):
    return str(tmpdir.join('tests/config.yaml'))

def test_user_config(mocker, cli_runner, user_config_path):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = cli_runner(template_path, '--config-file', user_config_path)

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=False,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=user_config_path,
        extra_context=None
    )

def test_default_user_config_overwrite(mocker, cli_runner, user_config_path):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = cli_runner(
        template_path,
        '--config-file',
        user_config_path,
        '--default-config',
    )

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=False,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=None,
        extra_context=None
    )

def test_default_user_config(mocker, cli_runner):
    mock_cookiecutter = mocker.patch(
        'cookiecutter.cli.cookiecutter'
    )

    template_path = 'tests/fake-repo-pre/'
    result = cli_runner(template_path, '--default-config')

    assert result.exit_code == 0
    mock_cookiecutter.assert_called_once_with(
        template_path,
        None,
        False,
        replay=False,
        overwrite_if_exists=False,
        output_dir='.',
        config_file=None,
        extra_context=None
    )

def test_echo_undefined_variable_error(tmpdir, cli_runner):
    output_dir = str(tmpdir.mkdir('output'))
    template_path = 'tests/undefined-variable/file-name/'

    result = cli_runner(
        '--no-input',
        '--default-config',
        '--output-dir',
        output_dir,
        template_path,
    )

    assert result.exit_code == 1

    error = "Unable to create file '{{cookiecutter.foobar}}'"
    assert error in result.output

    message = "Error message: 'dict object' has no attribute 'foobar'"
    assert message in result.output

    context = {
        'cookiecutter': {
            'github_username': 'hackebrot',
            'project_slug': 'testproject'
        }
    }
    context_str = json.dumps(context, indent=4, sort_keys=True)
    assert context_str in result.output

def test_echo_unknown_extension_error(tmpdir, cli_runner):
    output_dir = str(tmpdir.mkdir('output'))
    template_path = 'tests/test-extensions/unknown/'

    result = cli_runner(
        '--no-input',
        '--default-config',
        '--output-dir',
        output_dir,
        template_path,
    )

    assert result.exit_code == 1

    assert 'Unable to load extension: ' in result.output

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_extra_context(cli_runner):
    result = cli_runner(
        'tests/fake-repo-pre/',
        '--no-input',
        '-v',
        'project_name=Awesomez',
    )
    assert result.exit_code == 0
    assert os.path.isdir('fake-project')
    with open(os.path.join('fake-project', 'README.rst')) as f:
        assert 'Project name: **Awesomez**' in f.read()

@pytest.mark.usefixtures('remove_fake_project_dir')
def test_cli_extra_context_invalid_format(cli_runner):
    result = cli_runner(
        'tests/fake-repo-pre/',
        '--no-input',
        '-v',
        'ExtraContextWithNoEqualsSoInvalid',
    )
    assert result.exit_code == 2
    assert 'Error: Invalid value for "extra_context"' in result.output
    assert 'should contain items of the form key=value' in result.output
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import random

import pytest
import taggo

def test_noargs(capsys):
    with pytest.raises(SystemExit) as ex:
        taggo.main([])
    assert ex.value.code == 2

    out, err = capsys.readouterr()
    assert "the following arguments are required" in err

def test_help(capsys):
    with pytest.raises(SystemExit) as ex:
        taggo.main(["-h"])
    assert ex.value.code == 0

    out, err = capsys.readouterr()
    assert "show this help message and exit" in out

def test_run_non_existing(capsys):
    taggo.main(["run", "non-existing", "temp/test_run_non_existing"])
    out, err = capsys.readouterr()
    assert "ERROR - Didnt find src-path" in err

def test_run_dry(capsys):
    taggo.main(["--debug", "run", "--dry", "tests/tagged_files/", "temp/test_run_dry"])
    out, err = capsys.readouterr()
    assert "making tag-directory" in err
    assert "making symlink" in err
    assert "Checking directory" in err
    assert "to: ../../../tests/tagged_files/folders/pictures/2012 #Oslo tour/fishes/file #tag.jpg" in err

def test_nonexisting_dst(capsys):
    tmp = "temp/test_nonexisting_dst/{}".format(str(random.random()))
    taggo.main(["run", "tests/tagged_files/", tmp])
    assert os.path.isfile(
        '{}/øl/folders_pictures_2012 #Oslo tour_fishes - a file     many #tag spaces #øl #nårsk tag.txt'.format(
            tmp
        )
    )

def test_existing_dst(capsys):
    if not os.path.isdir("temp/test_existing_dst"):
        os.mkdir("temp/test_existing_dst")
    taggo.main(["--debug", "run", "tests/tagged_files/", "temp/test_existing_dst"])
    out, err = capsys.readouterr()
    assert "dst path exists and is a folder" in err

def test_existing_file_dst(capsys):
    with open("temp/test_existing_file_dst", "w") as fp:
        fp.write("")
    with pytest.raises(taggo.FolderException, message="dst exist but is not a folder. Cant continue"):
        taggo.main(["--debug", "run", "tests/tagged_files/", "temp/test_existing_file_dst"], reraise=True)

    #out, err = capsys.readouterr()
    #assert "dst path exists and is a folder" in err
<commit_msg>test matching its goal, going to test unicode elsewhere<commit_after>#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import random

import pytest
import taggo

def test_noargs(capsys):
    with pytest.raises(SystemExit) as ex:
        taggo.main([])
    assert ex.value.code == 2

    out, err = capsys.readouterr()
    assert "the following arguments are required" in err

def test_help(capsys):
    with pytest.raises(SystemExit) as ex:
        taggo.main(["-h"])
    assert ex.value.code == 0

    out, err = capsys.readouterr()
    assert "show this help message and exit" in out

def test_run_non_existing(capsys):
    taggo.main(["run", "non-existing", "temp/test_run_non_existing"])
    out, err = capsys.readouterr()
    assert "ERROR - Didnt find src-path" in err

def test_run_dry(capsys):
    taggo.main(["--debug", "run", "--dry", "tests/tagged_files/", "temp/test_run_dry"])
    out, err = capsys.readouterr()
    assert "making tag-directory" in err
    assert "making symlink" in err
    assert "Checking directory" in err
    assert "to: ../../../tests/tagged_files/folders/pictures/2012 #Oslo tour/fishes/file #tag.jpg" in err

def test_nonexisting_dst(capsys):
    tmp = "temp/test_nonexisting_dst/{}".format(str(random.random()))
    taggo.main(["run", "tests/tagged_files/", tmp])
    assert os.path.isdir('{}/root'.format(tmp))

def test_existing_dst(capsys):
    if not os.path.isdir("temp/test_existing_dst"):
        os.mkdir("temp/test_existing_dst")
    taggo.main(["--debug", "run", "tests/tagged_files/", "temp/test_existing_dst"])
    out, err = capsys.readouterr()
    assert "dst path exists and is a folder" in err

def test_existing_file_dst(capsys):
    with open("temp/test_existing_file_dst", "w") as fp:
        fp.write("")
    with pytest.raises(taggo.FolderException, message="dst exist but is not a folder. Cant continue"):
        taggo.main(["--debug", "run", "tests/tagged_files/", "temp/test_existing_file_dst"], reraise=True)

    #out, err = capsys.readouterr()
    #assert "dst path exists and is a folder" in err
<|endoftext|>


<commit_before># built-ins
import itertools as it

# libraries
import numpy as np
import networkx as nx

# local modules

def lowest_common_ancestor(t, u, v):
    """Find the lowest common ancestor of two nodes

    Parameters
    ----------
    t : an `Ultrametric` tree or `nx.DiGraph`
        The tree or directed acyclic graph on which to find ancestors.
    u, v : int
        The node IDs in `t` for which to find the common ancestor

    Returns
    -------
    c : int or None
        The lowest common ancestor of `u` and `v` in `t`. ``None`` if
        they do not have any common ancestors.

    Examples
    --------
    >>> t = Ultrametric([0, 1, 2])
    >>> t.merge(0, 1)
    3
    >>> t.merge(2, 3)
    4
    >>> t.ancestors(0)
    [3, 4]
    >>> t.ancestors(1)
    [3, 4]
    >>> t.ancestors(2)
    [4]
    >>> lowest_common_ancestor(t, 0, 1)
    3
    """
    au = t.ancestors(u)
    av = t.ancestors(v)
    common = set(au) & set(av)
    short = au if len(au) < len(av) else av
    for c in short:
        if c in common:
            return c
    return None

lca = lowest_common_ancestor

class Ultrametric(nx.DiGraph):
    """An ultrametric tree data structure"""
    def __init__(self, init_nodes=[]):
        super(Ultrametric, self).__init__()
        self.maxw = -np.inf
        self.add_nodes_from(init_nodes, w=self.maxw, num_leaves=1)
        self.id_counter = it.count(max([0] + init_nodes) + 1)

    def merge(self, u, v, w=0.0):
        """Merge two nodes u, v and return the ID of the new node.

        Parameters
        ----------
        u, v : int
            The nodes being merged.
        w : float
            The weight of the merge.

        Returns
        -------
        node_id : int
            The generated ID of the node resulting from the (u, v)
            merge.

        Notes
        -----
        To preserve the ultrametric condition, the true weight of the
        merge is the maximum of w, weight(u), and weight(v).
        """
        node_id = self.id_counter.next()
        self.maxw = max(w, self.maxw)
        subtree_maxw = max(w, self.node[u]['w'], self.node[v]['w'])
        num_leaves = self.num_leaves(u) + self.num_leaves(v)
        self.add_node(node_id, w=subtree_maxw, num_leaves=num_leaves)
        self.add_edges_from([(node_id, u), (node_id, v)])
        return node_id

    def split(self, u, v):
        split_node = lowest_common_ancestor(self, u, v)
        if split_node is None:
            return
        a = self.ancestors(split_node)
        num_leaves = self.node[split_node]['num_leaves']
        self.remove_node(split_node)
        for n in a:
            self.node[n]['num_leaves'] -= num_leaves

    def get_map(self, t=np.inf, source=None):
        """Compute a map from leaf nodes to roots at a certain height.

        Parameters
        ----------
        t : float, optional
            The threshold at which to cut the tree.
        source : int, optional
            Compute the map only for the tree rooted at `source`.

        Examples
        --------
        >>> t = Ultrametric(np.arange(6)) # tree with 6 leaves
        >>> t.merge(0, 1, 0.1) # merge nodes 1 and 2
        6
        >>> t.merge(2, 6, 0.2)
        7
        >>> t.merge(3, 7, 0.3)
        8
        >>> t.merge(4, 5, 0.1)
        9
        >>> t.merge(7, 8, 0.2)
        10
        >>> t.get_map(0.25)
        array([7, 7, 7, 3, 9, 9])
        >>> t.get_map(source=7)
        array([7, 7, 7])
        >>> t.get_map(source=9)
        array([0, 0, 0, 0, 9, 9])
        >>> t.get_map(0.15, 7)
        array([6, 6, 2])
        """
        if source is not None:
            des = nx.algorithms.dag.descendants(self, source)
            des.add(source)
            g = self.subgraph(des)
        else:
            g = self
        nodes = filter(lambda n: self.node[n]['w'] < t, g.nodes())
        g = self.subgraph(nodes)
        ccs = nx.algorithms.connected_components(g.to_undirected())
        ccs = [self.subgraph(ns) for ns in ccs]
        leavess = [filter(h.is_leaf, h.nodes()) for h in ccs]
        roots = [filter(h.is_root, h.nodes()) for h in ccs]
        max_leaf = max(map(max, leavess))
        forward_map = np.zeros(max_leaf + 1, int)
        for root, leaves in zip(roots, leavess):
            forward_map[leaves] = root
        return forward_map

    def is_leaf(self, node):
        return self.out_degree(node) == 0

    def is_root(self, node):
        return self.in_degree(node) == 0

    def num_leaves(self, n):
        return self.node[n]['num_leaves']

    def parent(self, n):
        p = self.predecessors(n)
        if len(p) == 1:
            return p[0]
        return None

    def ancestors(self, n):
        a = []
        node = n
        while self.parent(node) is not None:
            node = self.parent(node)
            a.append(node)
        return a

    def children(self, n):
        return self.successors(n)

def num_leaves(g, n):
    return g.node[n]['num_leaves']

if __name__ == '__main__':
    import doctest
    doctest.testmod()

<commit_msg>Change map criterion to include most recent merges<commit_after># built-ins
import itertools as it

# libraries
import numpy as np
import networkx as nx

# local modules

def lowest_common_ancestor(t, u, v):
    """Find the lowest common ancestor of two nodes

    Parameters
    ----------
    t : an `Ultrametric` tree or `nx.DiGraph`
        The tree or directed acyclic graph on which to find ancestors.
    u, v : int
        The node IDs in `t` for which to find the common ancestor

    Returns
    -------
    c : int or None
        The lowest common ancestor of `u` and `v` in `t`. ``None`` if
        they do not have any common ancestors.

    Examples
    --------
    >>> t = Ultrametric([0, 1, 2])
    >>> t.merge(0, 1)
    3
    >>> t.merge(2, 3)
    4
    >>> t.ancestors(0)
    [3, 4]
    >>> t.ancestors(1)
    [3, 4]
    >>> t.ancestors(2)
    [4]
    >>> lowest_common_ancestor(t, 0, 1)
    3
    """
    au = t.ancestors(u)
    av = t.ancestors(v)
    common = set(au) & set(av)
    short = au if len(au) < len(av) else av
    for c in short:
        if c in common:
            return c
    return None

lca = lowest_common_ancestor

class Ultrametric(nx.DiGraph):
    """An ultrametric tree data structure"""
    def __init__(self, init_nodes=[]):
        super(Ultrametric, self).__init__()
        self.maxw = -np.inf
        self.add_nodes_from(init_nodes, w=self.maxw, num_leaves=1)
        self.id_counter = it.count(max([0] + init_nodes) + 1)

    def merge(self, u, v, w=0.0):
        """Merge two nodes u, v and return the ID of the new node.

        Parameters
        ----------
        u, v : int
            The nodes being merged.
        w : float
            The weight of the merge.

        Returns
        -------
        node_id : int
            The generated ID of the node resulting from the (u, v)
            merge.

        Notes
        -----
        To preserve the ultrametric condition, the true weight of the
        merge is the maximum of w, weight(u), and weight(v).
        """
        node_id = self.id_counter.next()
        self.maxw = max(w, self.maxw)
        subtree_maxw = max(w, self.node[u]['w'], self.node[v]['w'])
        num_leaves = self.num_leaves(u) + self.num_leaves(v)
        self.add_node(node_id, w=subtree_maxw, num_leaves=num_leaves)
        self.add_edges_from([(node_id, u), (node_id, v)])
        return node_id

    def split(self, u, v):
        split_node = lowest_common_ancestor(self, u, v)
        if split_node is None:
            return
        a = self.ancestors(split_node)
        num_leaves = self.node[split_node]['num_leaves']
        self.remove_node(split_node)
        for n in a:
            self.node[n]['num_leaves'] -= num_leaves

    def get_map(self, t=np.inf, source=None):
        """Compute a map from leaf nodes to roots at a certain height.

        Parameters
        ----------
        t : float, optional
            The threshold at which to cut the tree.
        source : int, optional
            Compute the map only for the tree rooted at `source`.

        Examples
        --------
        >>> t = Ultrametric(np.arange(6)) # tree with 6 leaves
        >>> t.merge(0, 1, 0.1) # merge nodes 1 and 2
        6
        >>> t.merge(2, 6, 0.2)
        7
        >>> t.merge(3, 7, 0.3)
        8
        >>> t.merge(4, 5, 0.1)
        9
        >>> t.merge(7, 8, 0.2)
        10
        >>> t.get_map(0.25)
        array([7, 7, 7, 3, 9, 9])
        >>> t.get_map(source=7)
        array([7, 7, 7])
        >>> t.get_map(source=9)
        array([0, 0, 0, 0, 9, 9])
        >>> t.get_map(0.15, 7)
        array([6, 6, 2])
        """
        if source is not None:
            des = nx.algorithms.dag.descendants(self, source)
            des.add(source)
            g = self.subgraph(des)
        else:
            g = self
        nodes = filter(lambda n: self.node[n]['w'] <= t, g.nodes())
        g = self.subgraph(nodes)
        ccs = nx.algorithms.connected_components(g.to_undirected())
        ccs = [self.subgraph(ns) for ns in ccs]
        leavess = [filter(h.is_leaf, h.nodes()) for h in ccs]
        roots = [filter(h.is_root, h.nodes()) for h in ccs]
        max_leaf = max(map(max, leavess))
        forward_map = np.zeros(max_leaf + 1, int)
        for root, leaves in zip(roots, leavess):
            forward_map[leaves] = root
        return forward_map

    def is_leaf(self, node):
        return self.out_degree(node) == 0

    def is_root(self, node):
        return self.in_degree(node) == 0

    def num_leaves(self, n):
        return self.node[n]['num_leaves']

    def parent(self, n):
        p = self.predecessors(n)
        if len(p) == 1:
            return p[0]
        return None

    def ancestors(self, n):
        a = []
        node = n
        while self.parent(node) is not None:
            node = self.parent(node)
            a.append(node)
        return a

    def children(self, n):
        return self.successors(n)

def num_leaves(g, n):
    return g.node[n]['num_leaves']

if __name__ == '__main__':
    import doctest
    doctest.testmod()
<|endoftext|>


<commit_before>import json
import posixpath
import re
import sys
import time

import pytest

timer = getattr(time, 'monotonic', time.time)

def test_simple(pyscript):
    script = pyscript("""
        import click
        from daemonocle.cli import DaemonCLI

        @click.command(cls=DaemonCLI,
                       daemon_params={'prog': 'foo', 'pidfile': 'foo.pid'})
        def main():
            \"\"\"My awesome daemon\"\"\"
            pass

        if __name__ == '__main__':
            main()
    """)
    result = script.run('--help')
    assert result.returncode == 0
    assert b'My awesome daemon' in result.stdout
    assert re.search((
        br'\s*start\s+Start the daemon\.\n'
        br'\s*stop\s+Stop the daemon\.\n'
        br'\s*restart\s+Stop then start the daemon\.\n'
        br'\s*status\s+Get the status of the daemon\.\n'),
        result.stdout)

    result = script.run('start', '--help')
    assert result.returncode == 0
    assert re.search(
        br'\s*--debug\s+Do NOT detach and run in the background\.\n',
        result.stdout)

    assert script.run('stop', '--help').returncode == 0
    assert script.run('restart', '--help').returncode == 0
    assert script.run('status', '--help').returncode == 0

def test_debug(pyscript):
    script = pyscript("""
        import click
        from daemonocle.cli import DaemonCLI

        @click.command(cls=DaemonCLI, daemon_params={'prog': 'foo'})
        def main():
            \"\"\"My awesome daemon\"\"\"
            print('hello world')

        if __name__ == '__main__':
            main()
    """)
    result = script.run('start', '--debug')
    assert result.returncode == 0
    assert result.stdout == (
        b'Starting foo ... OK\n'
        b'hello world\n'
        b'All children are gone. Parent is exiting...\n')
    assert result.stderr == b''

def test_shorthand_1(pyscript):
    script = pyscript("""
        import click
        from daemonocle.cli import cli

        @cli(prog='foo')
        def main():
            \"\"\"My awesome daemon\"\"\"
            print('hello world')

        if __name__ == '__main__':
            main()
    """)
    result = script.run('start', '--debug')
    assert result.returncode == 0
    assert result.stdout == (
        b'Starting foo ... OK\n'
        b'hello world\n'
        b'All children are gone. Parent is exiting...\n')
    assert result.stderr == b''

def test_shorthand_2(pyscript):
    script = pyscript("""
        from daemonocle import Daemon
        from daemonocle.cli import DaemonCLI

        def main():
            \"\"\"My awesome daemon\"\"\"
            print('hello world')

        if __name__ == '__main__':
            cli = DaemonCLI(daemon=Daemon(prog='foo', worker=main))
            cli()
    """)
    result = script.run('--help')
    assert result.returncode == 0
    assert b'My awesome daemon' in result.stdout

    result = script.run('start', '--debug')
    assert result.returncode == 0
    assert result.stdout == (
        b'Starting foo ... OK\n'
        b'hello world\n'
        b'All children are gone. Parent is exiting...\n')
    assert result.stderr == b''

def test_force_stop(pyscript):
    script = pyscript("""
        import signal
        import sys
        import time
        from daemonocle import Daemon, DaemonCLI

        def worker():
            def handle_sigterm(*args, **kwargs):
                time.sleep(10)

            signal.signal(signal.SIGTERM, handle_sigterm)
            time.sleep(10)

        if __name__ == '__main__':
            cli = DaemonCLI(daemon=Daemon(
                worker=worker, prog='foo', pidfile='foo.pid', stop_timeout=1))
            cli()
    """)
    pidfile = posixpath.realpath(posixpath.join(script.dirname, 'foo.pid'))

    script.run('start')
    with open(pidfile, 'rb') as f:
        pid = int(f.read())
    t1 = timer()
    result = script.run('stop', '--force')
    t2 = timer()
    assert result.returncode == 0
    assert result.stdout == b'Stopping foo ... FAILED\nKilling foo ... OK\n'
    assert result.stderr == ('ERROR: Timed out while waiting for process '
                             '(PID {pid}) to terminate\n').format(
                                pid=pid).encode('utf-8')
    assert 1.0 <= (t2 - t1) <= 1.5

def test_force_stop_custom_timeout(pyscript):
    script = pyscript("""
        import signal
        import sys
        import time
        from daemonocle import Daemon, DaemonCLI

        def worker():
            def handle_sigterm(*args, **kwargs):
                time.sleep(10)

            signal.signal(signal.SIGTERM, handle_sigterm)
            time.sleep(10)

        if __name__ == '__main__':
            cli = DaemonCLI(daemon=Daemon(
                worker=worker, prog='foo', pidfile='foo.pid', stop_timeout=5))
            cli()
    """)
    pidfile = posixpath.realpath(posixpath.join(script.dirname, 'foo.pid'))

    script.run('start')
    with open(pidfile, 'rb') as f:
        pid = int(f.read())
    t1 = timer()
    result = script.run('stop', '--force', '--timeout=1')
    t2 = timer()
    assert result.returncode == 0
    assert result.stdout == b'Stopping foo ... FAILED\nKilling foo ... OK\n'
    assert result.stderr == ('ERROR: Timed out while waiting for process '
                             '(PID {pid}) to terminate\n').format(
                                pid=pid).encode('utf-8')
    assert 1.0 <= (t2 - t1) <= 1.5

def test_status_json(pyscript):
    script = pyscript("""
        import time
        from daemonocle.cli import cli

        @cli(prog='foo', pidfile='foo.pid')
        def main():
            time.sleep(10)

        if __name__ == '__main__':
            main()
    """)
    pidfile = posixpath.realpath(posixpath.join(script.dirname, 'foo.pid'))

    script.run('start')
    with open(pidfile, 'rb') as f:
        pid = int(f.read())

    result = script.run('status', '--json')
    assert result.returncode == 0
    status = json.loads(result.stdout.decode('ascii').rstrip('\n'))
    assert status['prog'] == 'foo'
    assert status['pid'] == pid
    assert status['status'] in {'running', 'sleeping'}
    assert isinstance(status['uptime'], float)
    assert isinstance(status['cpu_percent'], float)
    assert isinstance(status['memory_percent'], float)

    script.run('stop')

    result = script.run('status', '--json')
    assert result.returncode == 1
    status = json.loads(result.stdout.decode('ascii').rstrip('\n'))
    assert status['prog'] == 'foo'
    assert status['status'] == 'dead'

def test_status_fields(pyscript):
    script = pyscript("""
        import subprocess
        from daemonocle.cli import cli

        @cli(prog='foo', pidfile='foo.pid')
        def main():
            subprocess.check_call(['sleep', '10'])

        if __name__ == '__main__':
            main()
    """)
    pidfile = posixpath.realpath(posixpath.join(script.dirname, 'foo.pid'))

    script.run('start')
    result = script.run(
        'status', '--json', '--fields=group_num_procs,open_files')
    assert result.returncode == 0
    status = json.loads(result.stdout.decode('ascii').rstrip('\n'))
    assert status['group_num_procs'] == 2
    open_file_paths = set()
    for item in status['open_files']:
        try:
            path = posixpath.realpath(item[0])
        except OSError:
            continue
        else:
            open_file_paths.add(path)
    assert pidfile in open_file_paths

    script.run('stop')

def test_custom_actions(pyscript):
    script = pyscript("""
        import click
        from daemonocle import Daemon, expose_action
        from daemonocle.cli import DaemonCLI

        class BananaDaemon(Daemon):
            @expose_action
            def banana(self):
                \"\"\"Go bananas.\"\"\"
                pass

            def plantain(self):
                pass

        @click.command(cls=DaemonCLI, daemon_class=BananaDaemon,
                       daemon_params={'prog': 'foo', 'pidfile': 'foo.pid'})
        def main():
            \"\"\"The banana daemon\"\"\"
            pass

        if __name__ == '__main__':
            main()
    """)
    result = script.run('--help')
    assert result.returncode == 0
    assert b'The banana daemon' in result.stdout
    assert re.search(br'\s*banana\s+Go bananas\.\n', result.stdout)

    assert script.run('banana', '--help').returncode == 0

    result = script.run('plantain', '--help')
    assert result.returncode != 0
    assert b'No such command' in result.stderr

@pytest.mark.skipif(sys.version_info.major < 3, reason='Requires Python 3.x')
def test_custom_actions_with_options(pyscript):
    script = pyscript("""
        import daemonocle

        class MyDaemon(daemonocle.Daemon):
            prog = 'my_daemon'

            @daemonocle.expose_action
            def foo(self, wibble: int,
                    wobble: str = '1ErrJ5QgasJKkcMdRBrEQHtyGqkWLa1sSJS'):
                \"\"\"2ScD2S4w44jivwVNAamYdCVUU8afdDqTsGU\"\"\"
                print(wibble * 24369)
                print(sum(map(ord, wobble)))

            @daemonocle.expose_action
            def bar(self, wubble=False, flub=True, **kwargs):
                \"\"\"1R3YQRaAMU2inZ7mhtZC96MTiaykPYGCqC9\"\"\"
                print(repr(wubble))
                print(repr(flub))
                print(repr(kwargs))

            def worker(self):
                \"\"\"2PfZ4gSZaghZXK3VuDqbD82ZGqpqDLAKPpj\"\"\"
                pass

        if __name__ == '__main__':
            MyDaemon(pidfile='foo.pid').cli()
    """)
    result = script.run('--help')
    assert result.returncode == 0
    assert b'2PfZ4gSZaghZXK3VuDqbD82ZGqpqDLAKPpj' in result.stdout
    assert re.search(
        br'\s*foo\s+2ScD2S4w44jivwVNAamYdCVUU8afdDqTsGU\n', result.stdout)
    assert re.search(
        br'\s*bar\s+1R3YQRaAMU2inZ7mhtZC96MTiaykPYGCqC9\n', result.stdout)

    result = script.run('foo', '--help')
    assert result.returncode == 0
    assert b'2ScD2S4w44jivwVNAamYdCVUU8afdDqTsGU' in result.stdout
    assert b'--wibble' in result.stdout
    assert b'--wobble' in result.stdout

    result = script.run('foo')
    assert result.returncode == 2
    assert b'Missing option \'--wibble\'' in result.stderr

    result = script.run('foo', '--wibble=9055')
    assert result.returncode == 0
    assert result.stdout == b'220661295\n3077\n'

    result = script.run('foo', '--wibble=1850',
                        '--wobble=26hevLhGzeeX7dNqAtdXjKBtmevsxgBvWNG')
    assert result.returncode == 0
    assert result.stdout == b'45082650\n3254\n'

    result = script.run('bar')
    assert result.returncode == 0
    assert result.stdout == b'False\nTrue\n{}\n'

    result = script.run('bar', '--flub')
    assert result.returncode == 0
    assert result.stdout == b'False\nTrue\n{}\n'

    result = script.run('bar', '--wubble', '--no-flub')
    assert result.returncode == 0
    assert result.stdout == b'True\nFalse\n{}\n'
<commit_msg>Be more lenient with timings<commit_after>import json
import posixpath
import re
import sys
import time

import pytest

timer = getattr(time, 'monotonic', time.time)

def test_simple(pyscript):
    script = pyscript("""
        import click
        from daemonocle.cli import DaemonCLI

        @click.command(cls=DaemonCLI,
                       daemon_params={'prog': 'foo', 'pidfile': 'foo.pid'})
        def main():
            \"\"\"My awesome daemon\"\"\"
            pass

        if __name__ == '__main__':
            main()
    """)
    result = script.run('--help')
    assert result.returncode == 0
    assert b'My awesome daemon' in result.stdout
    assert re.search((
        br'\s*start\s+Start the daemon\.\n'
        br'\s*stop\s+Stop the daemon\.\n'
        br'\s*restart\s+Stop then start the daemon\.\n'
        br'\s*status\s+Get the status of the daemon\.\n'),
        result.stdout)

    result = script.run('start', '--help')
    assert result.returncode == 0
    assert re.search(
        br'\s*--debug\s+Do NOT detach and run in the background\.\n',
        result.stdout)

    assert script.run('stop', '--help').returncode == 0
    assert script.run('restart', '--help').returncode == 0
    assert script.run('status', '--help').returncode == 0

def test_debug(pyscript):
    script = pyscript("""
        import click
        from daemonocle.cli import DaemonCLI

        @click.command(cls=DaemonCLI, daemon_params={'prog': 'foo'})
        def main():
            \"\"\"My awesome daemon\"\"\"
            print('hello world')

        if __name__ == '__main__':
            main()
    """)
    result = script.run('start', '--debug')
    assert result.returncode == 0
    assert result.stdout == (
        b'Starting foo ... OK\n'
        b'hello world\n'
        b'All children are gone. Parent is exiting...\n')
    assert result.stderr == b''

def test_shorthand_1(pyscript):
    script = pyscript("""
        import click
        from daemonocle.cli import cli

        @cli(prog='foo')
        def main():
            \"\"\"My awesome daemon\"\"\"
            print('hello world')

        if __name__ == '__main__':
            main()
    """)
    result = script.run('start', '--debug')
    assert result.returncode == 0
    assert result.stdout == (
        b'Starting foo ... OK\n'
        b'hello world\n'
        b'All children are gone. Parent is exiting...\n')
    assert result.stderr == b''

def test_shorthand_2(pyscript):
    script = pyscript("""
        from daemonocle import Daemon
        from daemonocle.cli import DaemonCLI

        def main():
            \"\"\"My awesome daemon\"\"\"
            print('hello world')

        if __name__ == '__main__':
            cli = DaemonCLI(daemon=Daemon(prog='foo', worker=main))
            cli()
    """)
    result = script.run('--help')
    assert result.returncode == 0
    assert b'My awesome daemon' in result.stdout

    result = script.run('start', '--debug')
    assert result.returncode == 0
    assert result.stdout == (
        b'Starting foo ... OK\n'
        b'hello world\n'
        b'All children are gone. Parent is exiting...\n')
    assert result.stderr == b''

def test_force_stop(pyscript):
    script = pyscript("""
        import signal
        import sys
        import time
        from daemonocle import Daemon, DaemonCLI

        def worker():
            def handle_sigterm(*args, **kwargs):
                time.sleep(10)

            signal.signal(signal.SIGTERM, handle_sigterm)
            time.sleep(10)

        if __name__ == '__main__':
            cli = DaemonCLI(daemon=Daemon(
                worker=worker, prog='foo', pidfile='foo.pid', stop_timeout=1))
            cli()
    """)
    pidfile = posixpath.realpath(posixpath.join(script.dirname, 'foo.pid'))

    script.run('start')
    with open(pidfile, 'rb') as f:
        pid = int(f.read())
    t1 = timer()
    result = script.run('stop', '--force')
    t2 = timer()
    assert result.returncode == 0
    assert result.stdout == b'Stopping foo ... FAILED\nKilling foo ... OK\n'
    assert result.stderr == ('ERROR: Timed out while waiting for process '
                             '(PID {pid}) to terminate\n').format(
                                pid=pid).encode('utf-8')
    assert 1.0 <= (t2 - t1) < 2.0

def test_force_stop_custom_timeout(pyscript):
    script = pyscript("""
        import signal
        import sys
        import time
        from daemonocle import Daemon, DaemonCLI

        def worker():
            def handle_sigterm(*args, **kwargs):
                time.sleep(10)

            signal.signal(signal.SIGTERM, handle_sigterm)
            time.sleep(10)

        if __name__ == '__main__':
            cli = DaemonCLI(daemon=Daemon(
                worker=worker, prog='foo', pidfile='foo.pid', stop_timeout=5))
            cli()
    """)
    pidfile = posixpath.realpath(posixpath.join(script.dirname, 'foo.pid'))

    script.run('start')
    with open(pidfile, 'rb') as f:
        pid = int(f.read())
    t1 = timer()
    result = script.run('stop', '--force', '--timeout=1')
    t2 = timer()
    assert result.returncode == 0
    assert result.stdout == b'Stopping foo ... FAILED\nKilling foo ... OK\n'
    assert result.stderr == ('ERROR: Timed out while waiting for process '
                             '(PID {pid}) to terminate\n').format(
                                pid=pid).encode('utf-8')
    assert 1.0 <= (t2 - t1) < 2.0

def test_status_json(pyscript):
    script = pyscript("""
        import time
        from daemonocle.cli import cli

        @cli(prog='foo', pidfile='foo.pid')
        def main():
            time.sleep(10)

        if __name__ == '__main__':
            main()
    """)
    pidfile = posixpath.realpath(posixpath.join(script.dirname, 'foo.pid'))

    script.run('start')
    with open(pidfile, 'rb') as f:
        pid = int(f.read())

    result = script.run('status', '--json')
    assert result.returncode == 0
    status = json.loads(result.stdout.decode('ascii').rstrip('\n'))
    assert status['prog'] == 'foo'
    assert status['pid'] == pid
    assert status['status'] in {'running', 'sleeping'}
    assert isinstance(status['uptime'], float)
    assert isinstance(status['cpu_percent'], float)
    assert isinstance(status['memory_percent'], float)

    script.run('stop')

    result = script.run('status', '--json')
    assert result.returncode == 1
    status = json.loads(result.stdout.decode('ascii').rstrip('\n'))
    assert status['prog'] == 'foo'
    assert status['status'] == 'dead'

def test_status_fields(pyscript):
    script = pyscript("""
        import subprocess
        from daemonocle.cli import cli

        @cli(prog='foo', pidfile='foo.pid')
        def main():
            subprocess.check_call(['sleep', '10'])

        if __name__ == '__main__':
            main()
    """)
    pidfile = posixpath.realpath(posixpath.join(script.dirname, 'foo.pid'))

    script.run('start')
    result = script.run(
        'status', '--json', '--fields=group_num_procs,open_files')
    assert result.returncode == 0
    status = json.loads(result.stdout.decode('ascii').rstrip('\n'))
    assert status['group_num_procs'] == 2
    open_file_paths = set()
    for item in status['open_files']:
        try:
            path = posixpath.realpath(item[0])
        except OSError:
            continue
        else:
            open_file_paths.add(path)
    assert pidfile in open_file_paths

    script.run('stop')

def test_custom_actions(pyscript):
    script = pyscript("""
        import click
        from daemonocle import Daemon, expose_action
        from daemonocle.cli import DaemonCLI

        class BananaDaemon(Daemon):
            @expose_action
            def banana(self):
                \"\"\"Go bananas.\"\"\"
                pass

            def plantain(self):
                pass

        @click.command(cls=DaemonCLI, daemon_class=BananaDaemon,
                       daemon_params={'prog': 'foo', 'pidfile': 'foo.pid'})
        def main():
            \"\"\"The banana daemon\"\"\"
            pass

        if __name__ == '__main__':
            main()
    """)
    result = script.run('--help')
    assert result.returncode == 0
    assert b'The banana daemon' in result.stdout
    assert re.search(br'\s*banana\s+Go bananas\.\n', result.stdout)

    assert script.run('banana', '--help').returncode == 0

    result = script.run('plantain', '--help')
    assert result.returncode != 0
    assert b'No such command' in result.stderr

@pytest.mark.skipif(sys.version_info.major < 3, reason='Requires Python 3.x')
def test_custom_actions_with_options(pyscript):
    script = pyscript("""
        import daemonocle

        class MyDaemon(daemonocle.Daemon):
            prog = 'my_daemon'

            @daemonocle.expose_action
            def foo(self, wibble: int,
                    wobble: str = '1ErrJ5QgasJKkcMdRBrEQHtyGqkWLa1sSJS'):
                \"\"\"2ScD2S4w44jivwVNAamYdCVUU8afdDqTsGU\"\"\"
                print(wibble * 24369)
                print(sum(map(ord, wobble)))

            @daemonocle.expose_action
            def bar(self, wubble=False, flub=True, **kwargs):
                \"\"\"1R3YQRaAMU2inZ7mhtZC96MTiaykPYGCqC9\"\"\"
                print(repr(wubble))
                print(repr(flub))
                print(repr(kwargs))

            def worker(self):
                \"\"\"2PfZ4gSZaghZXK3VuDqbD82ZGqpqDLAKPpj\"\"\"
                pass

        if __name__ == '__main__':
            MyDaemon(pidfile='foo.pid').cli()
    """)
    result = script.run('--help')
    assert result.returncode == 0
    assert b'2PfZ4gSZaghZXK3VuDqbD82ZGqpqDLAKPpj' in result.stdout
    assert re.search(
        br'\s*foo\s+2ScD2S4w44jivwVNAamYdCVUU8afdDqTsGU\n', result.stdout)
    assert re.search(
        br'\s*bar\s+1R3YQRaAMU2inZ7mhtZC96MTiaykPYGCqC9\n', result.stdout)

    result = script.run('foo', '--help')
    assert result.returncode == 0
    assert b'2ScD2S4w44jivwVNAamYdCVUU8afdDqTsGU' in result.stdout
    assert b'--wibble' in result.stdout
    assert b'--wobble' in result.stdout

    result = script.run('foo')
    assert result.returncode == 2
    assert b'Missing option \'--wibble\'' in result.stderr

    result = script.run('foo', '--wibble=9055')
    assert result.returncode == 0
    assert result.stdout == b'220661295\n3077\n'

    result = script.run('foo', '--wibble=1850',
                        '--wobble=26hevLhGzeeX7dNqAtdXjKBtmevsxgBvWNG')
    assert result.returncode == 0
    assert result.stdout == b'45082650\n3254\n'

    result = script.run('bar')
    assert result.returncode == 0
    assert result.stdout == b'False\nTrue\n{}\n'

    result = script.run('bar', '--flub')
    assert result.returncode == 0
    assert result.stdout == b'False\nTrue\n{}\n'

    result = script.run('bar', '--wubble', '--no-flub')
    assert result.returncode == 0
    assert result.stdout == b'True\nFalse\n{}\n'
<|endoftext|>


<commit_before>import os
import shlex
import subprocess
import sys

from pathlib import Path
from tempfile import TemporaryDirectory

from nose.tools import assert_raises, eq_, ok_

from rsmtool.test_utils import (check_file_output,
                                check_generated_output,
                                check_report,
                                collect_warning_messages_from_report)

# allow test directory to be set via an environment variable
# which is needed for package testing
TEST_DIR = os.environ.get('TESTDIR', None)
if TEST_DIR:
    rsmtool_test_dir = TEST_DIR
else:
    from rsmtool.test_utils import rsmtool_test_dir

class TestToolCLI:

    @classmethod
    def setUpClass(cls):
        cls.temporary_directories = []
        cls.expected_json_dir = Path(rsmtool_test_dir) / 'data' / 'output'

        common_dir = Path(rsmtool_test_dir) / 'data' / 'experiments'
        cls.rsmtool_config_file = common_dir / 'lr' / 'lr.json'
        cls.rsmeval_config_file = common_dir / 'lr-eval' / 'lr_evaluation.json'
        cls.rsmcompare_config_file = common_dir / 'lr-self-compare' / 'rsmcompare.json'
        cls.rsmpredict_config_file = common_dir / 'lr-predict' / 'rsmpredict.json'
        cls.rsmsummarize_config_file = common_dir / 'lr-self-summary' / 'rsmsummarize.json'
        cls.expected_rsmtool_output_dir = common_dir / 'lr' / 'output'
        cls.expected_rsmeval_output_dir = common_dir / 'lr-eval' / 'output'
        cls.expected_rsmcompare_output_dir = common_dir / 'lr-self-compare' / 'output'
        cls.expected_rsmpredict_output_dir = common_dir / 'lr-predict' / 'output'
        cls.expected_rsmsummarize_output_dir = common_dir / 'lr-self-summary' / 'output'

    @classmethod
    def tearDownClass(cls):
        for tempdir in cls.temporary_directories:
            tempdir.cleanup()

    def check_no_args(self, context):
        """
        A helper method that checks running the tool with no arguments
        """
        proc = subprocess.run(shlex.split(context, posix='win' not in sys.platform),
                              capture_output=True)
        eq_(proc.returncode, 0)
        ok_(b'usage: ' + bytes(context, encoding="utf-8") in proc.stdout)

    def test_no_args(self):
        # test that the tool without any arguments prints help messag

        # this applies to all tools
        for context in ['rsmtool', 'rsmeval', 'rsmsummarize',
                        'rsmpredict', 'rsmcompare']:
            yield self.check_no_args, context

    def validate_run_output(self, name, experiment_dir):
        """
        A helper method that validates that the output of the "run"
        subcommand for the ``name`` tool as stored in ``experiment_dir``
        is as expected.

        This is heavily inspired by ``rsmtool.test_utils.check_run_*()``
        functions.

        Parameters
        ----------
        name : str
            The name of the tool being tested.
        experiment_dir : str
            Path to rsmtool output directory.
        """
        expected_output_dir = getattr(self, f"expected_{name}_output_dir")

        # all tools except rsmcompare need to have their output files validated
        if name in ['rsmtool', 'rsmeval', 'rsmsummarize', 'rsmpredict']:

            # rsmpredict has its own set of files and it puts them right at the root
            # of the output directory rather than under the "output" subdirectory
            if name == 'rsmpredict':
                output_dir = Path(experiment_dir)
                output_files = [output_dir / 'predictions_with_metadata.csv']
            else:
                output_dir = Path(experiment_dir) / 'output'
                output_files = list(output_dir.glob('*.csv'))

            for output_file in output_files:
                output_filename = output_file.name
                expected_output_file = expected_output_dir / output_filename

                if expected_output_file.exists():
                    check_file_output(str(output_file), str(expected_output_file))

            # we need to do an extra check for rsmtool
            if name == 'rsmtool':
                check_generated_output(list(map(str, output_files)), 'lr', 'rsmtool')

            # there's no report for rsmpredict
            if name != 'rsmpredict':
                report_dir = Path(experiment_dir) / 'report'
                html_report = list(report_dir.glob('*_report.html'))[0]
                check_report(str(html_report))

        # rsmcompare only has a report and we want it to be warning-free
        else:
            report_dir = Path(experiment_dir)
            html_report = list(report_dir.glob('*_report.html'))[0]
            check_report(str(html_report), raise_warnings=False)

            warning_msgs = collect_warning_messages_from_report(html_report)
            warning_msgs = [msg for msg in warning_msgs if 'DeprecationWarning' not in msg]
            eq_(len(warning_msgs), 0)

    def validate_generate_output(self, name, output, subgroups=False):
        """
        A helper method that validates that the ``output`` of the ``name`` tool
        as output by the "generate" subcommand is as expected.

        Parameters
        ----------
        name : str
            The name of the tool being tested.
        output : str
            The output of the "generate" subcommand from ``name`` tool
        subgroups : bool, optional
            If ``True``, the ``--subgroups`` was added to the "generate" command
            for ``name``.
            Defaults to ``False``.
        """
        # load the appropriate expected json file and check that its contents
        # match what was printed to stdout with our generate command
        if subgroups:
            expected_json_file = (self.expected_json_dir /
                                  f"autogenerated_{name}_config_groups.json")
        else:
            expected_json_file = (self.expected_json_dir /
                                  f"autogenerated_{name}_config.json")
        with expected_json_file.open('r', encoding='utf-8') as expectedfh:
            expected_output = expectedfh.read().strip()
            eq_(output, expected_output)

    def check_tool_cmd(self, context, subcmd, output_dir=None, working_dir=None):
        """
        A helper method to test that the ``subcmd`` invocation for ``context`` works
        as expected.

        Parameters
        ----------
        context : str
            Name of the tool being tested.
        subcmd : str
            The tool command-line invocation that is being tested.
        output_dir : None, optional
            Directory containing the output for "run" subcommands.
            Will be ``None`` for "generate" subcommands.
        working_dir : None, optional
            If we want the "run" subcommand to be run in a specific
            working directory.
        """

        # if the BINPATH environment variable is defined
        # use that to construct the command instead of just
        # the name; this is needed for the CI builds where
        # we do not always activate the conda environment
        binpath = os.environ.get('BINPATH', None)
        if binpath is not None:
            cmd = f"{binpath}/{context} {subcmd}"
        else:
            cmd = f"{context} {subcmd}"

        # run different checks depending on the given command type
        cmd_type = 'generate' if ' generate' in cmd else 'run'
        if cmd_type == 'run':
            # for run subcommands, we can ignore the messages printed to stdout
            proc = subprocess.run(shlex.split(cmd, posix='win' not in sys.platform),
                                  check=True,
                                  cwd=working_dir,
                                  stderr=subprocess.PIPE,
                                  stdout=subprocess.DEVNULL,
                                  encoding='utf-8')
            # then check that the commmand ran successfully
            ok_(proc.returncode == 0)
            # and, finally, that the output was as expected
            self.validate_run_output(context, output_dir)
        else:
            # for generate subcommands, we ignore the warnings printed to stderr
            subgroups = "--subgroups" in cmd
            proc = subprocess.run(shlex.split(cmd, posix='win' not in sys.platform),
                                  check=True,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  encoding='utf-8')
            ok_(proc.returncode == 0)
            self.validate_generate_output(context,
                                          proc.stdout.strip(),
                                          subgroups=subgroups)

    def test_default_subcommand_is_run(self):
        # test that the default subcommand for all contexts is "run"

        # this applies to all tools
        for context in ['rsmtool', 'rsmeval', 'rsmcompare', 'rsmpredict', 'rsmsummarize']:

            # create a temporary dirextory
            tempdir = TemporaryDirectory()
            self.temporary_directories.append(tempdir)

            # and test the default subcommand
            config_file = getattr(self, f"{context}_config_file")
            subcmd = f"{config_file} {tempdir.name}"
            yield self.check_tool_cmd, context, subcmd, tempdir.name, None

    def test_run_without_output_directory(self):
        # test that "run" subcommand works without an output directory

        # this applies to all tools except rsmpredict
        for context in ['rsmtool', 'rsmeval', 'rsmcompare', 'rsmsummarize']:

            # create a temporary dirextory
            tempdir = TemporaryDirectory()
            self.temporary_directories.append(tempdir)

            # and test the run subcommand without an output directory
            config_file = getattr(self, f"{context}_config_file")
            subcmd = f"run {config_file}"
            # we call check_tool_cmd with a working directory here to simulate
            # the usage of the current working directory when the output directory
            # is not specified
            yield self.check_tool_cmd, context, subcmd, tempdir.name, tempdir.name

    def check_run_bad_overwrite(self, cmd):
        """
        A helper method that checks that the overwriting error is raised properly.
        """
        with assert_raises(subprocess.CalledProcessError) as e:
            _ = subprocess.run(shlex.split(cmd, posix='win' not in sys.platform),
                               check=True,
                               stderr=subprocess.DEVNULL,
                               stdout=subprocess.DEVNULL)
            ok_('already contains' in e.msg)
            ok_('OSError' in e.msg)

    def test_run_bad_overwrite(self):
        # test that the "run" command fails to overwrite when "-f" is not specified

        # this applies to all tools except rsmpredict and rsmcompare
        for context in ['rsmtool', 'rsmeval', 'rsmsummarize']:

            tempdir = TemporaryDirectory()
            self.temporary_directories.append(tempdir)

            # make it look like we ran the tool in this directory already
            os.makedirs(f"{tempdir.name}/output")
            fake_file = Path(tempdir.name) / "output" / "foo.csv"
            fake_file.touch()

            config_file = getattr(self, f"{context}_config_file")
            # if the BINPATH environment variable is defined
            # use that to construct the command instead of just
            # the name; this is needed for the CI builds where
            # we do not always activate the conda environment
            binpath = os.environ.get('BINPATH', None)
            if binpath is not None:
                cmd = f"{binpath}/{context} {config_file} {tempdir.name}"
            else:
                cmd = f"{context} {config_file} {tempdir.name}"
            yield self.check_run_bad_overwrite, cmd

    def test_run_good_overwrite(self):
        #  test that the "run" command does overwrite when "-f" is specified

        # this applies to all tools except rsmpredict and rsmcompare
        for context in ['rsmtool', 'rsmeval', 'rsmsummarize']:

            tempdir = TemporaryDirectory()
            self.temporary_directories.append(tempdir)

            # make it look like we ran rsmtool in this directory already
            os.makedirs(f"{tempdir.name}/output")
            fake_file = Path(tempdir.name) / "output" / "foo.csv"
            fake_file.touch()

            config_file = getattr(self, f"{context}_config_file")
            subcmd = f"{config_file} {tempdir.name} -f"
            yield self.check_tool_cmd, context, subcmd, tempdir.name, None

    def test_rsmpredict_run_features_file(self):
        """
        test that rsmpredict "run" command works with ``--features``.
        """

        tempdir = TemporaryDirectory()
        self.temporary_directories.append(tempdir)

        subcmd = (f"{self.rsmpredict_config_file} {tempdir.name} "
                  f"--features {tempdir.name}/preprocessed_features.csv")

        self.check_tool_cmd("rsmpredict", subcmd, tempdir.name)

        # check the features file separately
        file1 = Path(tempdir.name) / "preprocessed_features.csv"
        file2 = self.expected_rsmpredict_output_dir / "preprocessed_features.csv"
        check_file_output(str(file1), str(file2))

    def test_generate(self):
        # test that the "generate" subcommand for all tools works as expected
        # in batch mode

        for context in ['rsmtool', 'rsmeval', 'rsmcompare', 'rsmpredict', 'rsmsummarize']:
            yield self.check_tool_cmd, context, "generate", None, None

    def test_generate_with_groups(self):
        # test that the "generate --subgroups" subcommand for all tools works
        # as expected in batch mode

        # this applies to all tools except rsmpredict and rsmsummarize
        for context in ['rsmtool', 'rsmeval', 'rsmcompare']:
            yield self.check_tool_cmd, context, "generate --subgroups", None, None
<commit_msg>Replace Python 3.7 only parameters in test.<commit_after>import os
import shlex
import subprocess
import sys

from pathlib import Path
from tempfile import TemporaryDirectory

from nose.tools import assert_raises, eq_, ok_

from rsmtool.test_utils import (check_file_output,
                                check_generated_output,
                                check_report,
                                collect_warning_messages_from_report)

# allow test directory to be set via an environment variable
# which is needed for package testing
TEST_DIR = os.environ.get('TESTDIR', None)
if TEST_DIR:
    rsmtool_test_dir = TEST_DIR
else:
    from rsmtool.test_utils import rsmtool_test_dir

class TestToolCLI:

    @classmethod
    def setUpClass(cls):
        cls.temporary_directories = []
        cls.expected_json_dir = Path(rsmtool_test_dir) / 'data' / 'output'

        common_dir = Path(rsmtool_test_dir) / 'data' / 'experiments'
        cls.rsmtool_config_file = common_dir / 'lr' / 'lr.json'
        cls.rsmeval_config_file = common_dir / 'lr-eval' / 'lr_evaluation.json'
        cls.rsmcompare_config_file = common_dir / 'lr-self-compare' / 'rsmcompare.json'
        cls.rsmpredict_config_file = common_dir / 'lr-predict' / 'rsmpredict.json'
        cls.rsmsummarize_config_file = common_dir / 'lr-self-summary' / 'rsmsummarize.json'
        cls.expected_rsmtool_output_dir = common_dir / 'lr' / 'output'
        cls.expected_rsmeval_output_dir = common_dir / 'lr-eval' / 'output'
        cls.expected_rsmcompare_output_dir = common_dir / 'lr-self-compare' / 'output'
        cls.expected_rsmpredict_output_dir = common_dir / 'lr-predict' / 'output'
        cls.expected_rsmsummarize_output_dir = common_dir / 'lr-self-summary' / 'output'

    @classmethod
    def tearDownClass(cls):
        for tempdir in cls.temporary_directories:
            tempdir.cleanup()

    def check_no_args(self, context):
        """
        A helper method that checks running the tool with no arguments
        """
        proc = subprocess.run(shlex.split(context, posix='win' not in sys.platform),
                              check=False,
                              stderr=subprocess.DEVNULL,
                              stdout=subprocess.PIPE)
        eq_(proc.returncode, 0)
        ok_(b'usage: ' + bytes(context, encoding="utf-8") in proc.stdout)

    def test_no_args(self):
        # test that the tool without any arguments prints help messag

        # this applies to all tools
        for context in ['rsmtool', 'rsmeval', 'rsmsummarize',
                        'rsmpredict', 'rsmcompare']:
            yield self.check_no_args, context

    def validate_run_output(self, name, experiment_dir):
        """
        A helper method that validates that the output of the "run"
        subcommand for the ``name`` tool as stored in ``experiment_dir``
        is as expected.

        This is heavily inspired by ``rsmtool.test_utils.check_run_*()``
        functions.

        Parameters
        ----------
        name : str
            The name of the tool being tested.
        experiment_dir : str
            Path to rsmtool output directory.
        """
        expected_output_dir = getattr(self, f"expected_{name}_output_dir")

        # all tools except rsmcompare need to have their output files validated
        if name in ['rsmtool', 'rsmeval', 'rsmsummarize', 'rsmpredict']:

            # rsmpredict has its own set of files and it puts them right at the root
            # of the output directory rather than under the "output" subdirectory
            if name == 'rsmpredict':
                output_dir = Path(experiment_dir)
                output_files = [output_dir / 'predictions_with_metadata.csv']
            else:
                output_dir = Path(experiment_dir) / 'output'
                output_files = list(output_dir.glob('*.csv'))

            for output_file in output_files:
                output_filename = output_file.name
                expected_output_file = expected_output_dir / output_filename

                if expected_output_file.exists():
                    check_file_output(str(output_file), str(expected_output_file))

            # we need to do an extra check for rsmtool
            if name == 'rsmtool':
                check_generated_output(list(map(str, output_files)), 'lr', 'rsmtool')

            # there's no report for rsmpredict
            if name != 'rsmpredict':
                report_dir = Path(experiment_dir) / 'report'
                html_report = list(report_dir.glob('*_report.html'))[0]
                check_report(str(html_report))

        # rsmcompare only has a report and we want it to be warning-free
        else:
            report_dir = Path(experiment_dir)
            html_report = list(report_dir.glob('*_report.html'))[0]
            check_report(str(html_report), raise_warnings=False)

            warning_msgs = collect_warning_messages_from_report(html_report)
            warning_msgs = [msg for msg in warning_msgs if 'DeprecationWarning' not in msg]
            eq_(len(warning_msgs), 0)

    def validate_generate_output(self, name, output, subgroups=False):
        """
        A helper method that validates that the ``output`` of the ``name`` tool
        as output by the "generate" subcommand is as expected.

        Parameters
        ----------
        name : str
            The name of the tool being tested.
        output : str
            The output of the "generate" subcommand from ``name`` tool
        subgroups : bool, optional
            If ``True``, the ``--subgroups`` was added to the "generate" command
            for ``name``.
            Defaults to ``False``.
        """
        # load the appropriate expected json file and check that its contents
        # match what was printed to stdout with our generate command
        if subgroups:
            expected_json_file = (self.expected_json_dir /
                                  f"autogenerated_{name}_config_groups.json")
        else:
            expected_json_file = (self.expected_json_dir /
                                  f"autogenerated_{name}_config.json")
        with expected_json_file.open('r', encoding='utf-8') as expectedfh:
            expected_output = expectedfh.read().strip()
            eq_(output, expected_output)

    def check_tool_cmd(self, context, subcmd, output_dir=None, working_dir=None):
        """
        A helper method to test that the ``subcmd`` invocation for ``context`` works
        as expected.

        Parameters
        ----------
        context : str
            Name of the tool being tested.
        subcmd : str
            The tool command-line invocation that is being tested.
        output_dir : None, optional
            Directory containing the output for "run" subcommands.
            Will be ``None`` for "generate" subcommands.
        working_dir : None, optional
            If we want the "run" subcommand to be run in a specific
            working directory.
        """

        # if the BINPATH environment variable is defined
        # use that to construct the command instead of just
        # the name; this is needed for the CI builds where
        # we do not always activate the conda environment
        binpath = os.environ.get('BINPATH', None)
        if binpath is not None:
            cmd = f"{binpath}/{context} {subcmd}"
        else:
            cmd = f"{context} {subcmd}"

        # run different checks depending on the given command type
        cmd_type = 'generate' if ' generate' in cmd else 'run'
        if cmd_type == 'run':
            # for run subcommands, we can ignore the messages printed to stdout
            proc = subprocess.run(shlex.split(cmd, posix='win' not in sys.platform),
                                  check=True,
                                  cwd=working_dir,
                                  stderr=subprocess.PIPE,
                                  stdout=subprocess.DEVNULL,
                                  encoding='utf-8')
            # then check that the commmand ran successfully
            ok_(proc.returncode == 0)
            # and, finally, that the output was as expected
            self.validate_run_output(context, output_dir)
        else:
            # for generate subcommands, we ignore the warnings printed to stderr
            subgroups = "--subgroups" in cmd
            proc = subprocess.run(shlex.split(cmd, posix='win' not in sys.platform),
                                  check=True,
                                  stdout=subprocess.PIPE,
                                  stderr=subprocess.PIPE,
                                  encoding='utf-8')
            ok_(proc.returncode == 0)
            self.validate_generate_output(context,
                                          proc.stdout.strip(),
                                          subgroups=subgroups)

    def test_default_subcommand_is_run(self):
        # test that the default subcommand for all contexts is "run"

        # this applies to all tools
        for context in ['rsmtool', 'rsmeval', 'rsmcompare', 'rsmpredict', 'rsmsummarize']:

            # create a temporary dirextory
            tempdir = TemporaryDirectory()
            self.temporary_directories.append(tempdir)

            # and test the default subcommand
            config_file = getattr(self, f"{context}_config_file")
            subcmd = f"{config_file} {tempdir.name}"
            yield self.check_tool_cmd, context, subcmd, tempdir.name, None

    def test_run_without_output_directory(self):
        # test that "run" subcommand works without an output directory

        # this applies to all tools except rsmpredict
        for context in ['rsmtool', 'rsmeval', 'rsmcompare', 'rsmsummarize']:

            # create a temporary dirextory
            tempdir = TemporaryDirectory()
            self.temporary_directories.append(tempdir)

            # and test the run subcommand without an output directory
            config_file = getattr(self, f"{context}_config_file")
            subcmd = f"run {config_file}"
            # we call check_tool_cmd with a working directory here to simulate
            # the usage of the current working directory when the output directory
            # is not specified
            yield self.check_tool_cmd, context, subcmd, tempdir.name, tempdir.name

    def check_run_bad_overwrite(self, cmd):
        """
        A helper method that checks that the overwriting error is raised properly.
        """
        with assert_raises(subprocess.CalledProcessError) as e:
            _ = subprocess.run(shlex.split(cmd, posix='win' not in sys.platform),
                               check=True,
                               stderr=subprocess.DEVNULL,
                               stdout=subprocess.DEVNULL)
            ok_('already contains' in e.msg)
            ok_('OSError' in e.msg)

    def test_run_bad_overwrite(self):
        # test that the "run" command fails to overwrite when "-f" is not specified

        # this applies to all tools except rsmpredict and rsmcompare
        for context in ['rsmtool', 'rsmeval', 'rsmsummarize']:

            tempdir = TemporaryDirectory()
            self.temporary_directories.append(tempdir)

            # make it look like we ran the tool in this directory already
            os.makedirs(f"{tempdir.name}/output")
            fake_file = Path(tempdir.name) / "output" / "foo.csv"
            fake_file.touch()

            config_file = getattr(self, f"{context}_config_file")
            # if the BINPATH environment variable is defined
            # use that to construct the command instead of just
            # the name; this is needed for the CI builds where
            # we do not always activate the conda environment
            binpath = os.environ.get('BINPATH', None)
            if binpath is not None:
                cmd = f"{binpath}/{context} {config_file} {tempdir.name}"
            else:
                cmd = f"{context} {config_file} {tempdir.name}"
            yield self.check_run_bad_overwrite, cmd

    def test_run_good_overwrite(self):
        #  test that the "run" command does overwrite when "-f" is specified

        # this applies to all tools except rsmpredict and rsmcompare
        for context in ['rsmtool', 'rsmeval', 'rsmsummarize']:

            tempdir = TemporaryDirectory()
            self.temporary_directories.append(tempdir)

            # make it look like we ran rsmtool in this directory already
            os.makedirs(f"{tempdir.name}/output")
            fake_file = Path(tempdir.name) / "output" / "foo.csv"
            fake_file.touch()

            config_file = getattr(self, f"{context}_config_file")
            subcmd = f"{config_file} {tempdir.name} -f"
            yield self.check_tool_cmd, context, subcmd, tempdir.name, None

    def test_rsmpredict_run_features_file(self):
        """
        test that rsmpredict "run" command works with ``--features``.
        """

        tempdir = TemporaryDirectory()
        self.temporary_directories.append(tempdir)

        subcmd = (f"{self.rsmpredict_config_file} {tempdir.name} "
                  f"--features {tempdir.name}/preprocessed_features.csv")

        self.check_tool_cmd("rsmpredict", subcmd, tempdir.name)

        # check the features file separately
        file1 = Path(tempdir.name) / "preprocessed_features.csv"
        file2 = self.expected_rsmpredict_output_dir / "preprocessed_features.csv"
        check_file_output(str(file1), str(file2))

    def test_generate(self):
        # test that the "generate" subcommand for all tools works as expected
        # in batch mode

        for context in ['rsmtool', 'rsmeval', 'rsmcompare', 'rsmpredict', 'rsmsummarize']:
            yield self.check_tool_cmd, context, "generate", None, None

    def test_generate_with_groups(self):
        # test that the "generate --subgroups" subcommand for all tools works
        # as expected in batch mode

        # this applies to all tools except rsmpredict and rsmsummarize
        for context in ['rsmtool', 'rsmeval', 'rsmcompare']:
            yield self.check_tool_cmd, context, "generate --subgroups", None, None
<|endoftext|>


<commit_before>import sh
import pytest
from os.path import exists

def cleanup():
    if exists('tmp/cli/'):
        sh.rm('-R', 'tmp/cli/')

def test_init_build_run():
    """ Make sure init works"""
    try:
        en_cli = sh.Command('./enaml-native')
        en_cli('init', 'MyApp', 'com.mycompany.myapp', 'tmp/cli/MyApp')
        assert exists('tmp/cli/MyApp/android/app/src/main/java/com/mycompany/myapp/MainActivity.java')
        assert exists('tmp/cli/MyApp/android/app/src/main/java/com/mycompany/myapp/MainApplication.java')
    finally:
        cleanup()<commit_msg>Disable test<commit_after>import sh
import pytest
from os.path import exists

def cleanup():
    if exists('tmp/cli/'):
        sh.rm('-R', 'tmp/cli/')

def test_init_build_run():
    """ Make sure init works"""
    try:
        return  #: Doesn't work on travis
        en_cli = sh.Command('./enaml-native')
        en_cli('init', 'MyApp', 'com.mycompany.myapp', 'tmp/cli/MyApp')
        assert exists('tmp/cli/MyApp/android/app/src/main/java/com/mycompany/myapp/MainActivity.java')
        assert exists('tmp/cli/MyApp/android/app/src/main/java/com/mycompany/myapp/MainApplication.java')
    finally:
        cleanup()<|endoftext|>


<commit_before>from framework.celery_tasks import app as celery_app
from framework.transactions.context import TokuTransaction

@celery_app.task(ignore_results=True)
def on_node_updated(node_id, first_save, saved_fields, request_headers=None):
    from website.models import Node
    node = Node.load(node_id)

    if node.is_collection or node.archiving:
        return

    if request_headers:
        with TokuTransaction():
            node.check_spam(saved_fields, request_headers, save=True)

    need_update = bool(node.SEARCH_UPDATE_FIELDS.intersection(saved_fields))
    # due to async nature of call this can issue a search delete for a new record (acceptable trade-off)
    if bool({'spam_status', 'is_deleted'}.intersection(saved_fields)):
        need_update = True
    elif not node.is_public and 'is_public' not in saved_fields:
        need_update = False
    if need_update:
        node.update_search()
<commit_msg>Add/update spam tasks<commit_after>from framework.celery_tasks import app as celery_app
from framework.transactions.context import TokuTransaction

@celery_app.task(ignore_results=True)
def on_node_updated(node_id, user_id, first_save, saved_fields, request_headers=None):
    from website.models import Node
    node = Node.load(node_id)

    if node.is_collection or node.archiving:
        return

    if request_headers:
        with TokuTransaction():
            # Will ban spammer
            node.check_spam(saved_fields, request_headers, mode='async', user_id=user_id, save=True)

    need_update = bool(node.SEARCH_UPDATE_FIELDS.intersection(saved_fields))
    # due to async nature of call this can issue a search delete for a new record (acceptable trade-off)
    if bool({'spam_status', 'is_deleted'}.intersection(saved_fields)):
        need_update = True
    elif not node.is_public and 'is_public' not in saved_fields:
        need_update = False
    if need_update:
        node.update_search()

@celery_app.task(ignore_results=True)
def on_user_suspension(user_id, system_tag):
    from framework.auth import User

    with TokuTransaction():
        user = User.load(user_id)
        if system_tag not in user.system_tags:
            user.system_tags.append(system_tag)
        if not user.is_disabled:
            user.disable_account()
            user.is_registered = False
        user.save()
<|endoftext|>