#
# Copyright (c) 2009, 2010 Testrepository Contributors
# 
# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
# license at the users choice. A copy of both licenses are available in the
# project source as Apache-2.0 and BSD. You may not use this file except in
# compliance with one of these two licences.
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# license you chose for the specific language governing permissions and
# limitations under that license.

"""Tests for the commands module."""

import optparse
import os.path
import sys

from testresources import TestResource
from testtools.matchers import (
    IsInstance,
    MatchesException,
    raises,
    )

from testrepository import commands
from testrepository.repository import file
from testrepository.tests import ResourcedTestCase
from testrepository.tests.monkeypatch import monkeypatch
from testrepository.tests.stubpackage import (
    StubPackageResource,
    )
from testrepository.ui import cli, model


class TemporaryCommand(object):
    """A temporary command."""


class TemporaryCommandResource(TestResource):

    def __init__(self, cmd_name):
        TestResource.__init__(self)
        cmd_name = cmd_name.replace('-', '_')
        self.resources.append(('pkg',
            StubPackageResource('commands',
            [('%s.py' % cmd_name,
             """from testrepository.commands import Command
class %s(Command):
    def run(self):
        pass
""" % cmd_name)], init=False)))
        self.cmd_name = cmd_name

    def make(self, dependency_resources):
        pkg = dependency_resources['pkg']
        result = TemporaryCommand()
        result.path = os.path.join(pkg.base, 'commands')
        commands.__path__.append(result.path)
        return result

    def clean(self, resource):
        commands.__path__.remove(resource.path)
        name = 'testrepository.commands.%s' % self.cmd_name
        if name in sys.modules:
            del sys.modules[name]


class TestFindCommand(ResourcedTestCase):

    resources = [('cmd', TemporaryCommandResource('foo'))]

    def test_looksupcommand(self):
        cmd = commands._find_command('foo')
        self.assertIsInstance(cmd(None), commands.Command)

    def test_missing_command(self):
        self.assertThat(lambda: commands._find_command('bar'),
            raises(KeyError))

    def test_sets_name(self):
        cmd = commands._find_command('foo')
        self.assertEqual('foo', cmd.name)


class TestNameMangling(ResourcedTestCase):

    resources = [('cmd', TemporaryCommandResource('foo-bar'))]

    def test_looksupcommand(self):
        cmd = commands._find_command('foo-bar')
        self.assertIsInstance(cmd(None), commands.Command)

    def test_sets_name(self):
        cmd = commands._find_command('foo-bar')
        # The name is preserved, so that 'testr commands' shows something
        # sensible.
        self.assertEqual('foo-bar', cmd.name)


class TestIterCommands(ResourcedTestCase):

    resources = [
        ('cmd1', TemporaryCommandResource('one')),
        ('cmd2', TemporaryCommandResource('two')),
        ]

    def test_iter_commands(self):
        cmds = list(commands.iter_commands())
        cmds = [cmd(None).name for cmd in cmds]
        # We don't care about all the built in commands
        cmds = [cmd for cmd in cmds if cmd in ('one', 'two')]
        self.assertEqual(['one', 'two'], cmds)


class TestRunArgv(ResourcedTestCase):

    def stub__find_command(self, cmd_run):
        self.calls = []
        self.addCleanup(monkeypatch('testrepository.commands._find_command',
            self._find_command))
        self.cmd_run = cmd_run

    def _find_command(self, cmd_name):
        self.calls.append(cmd_name)
        real_run = self.cmd_run
        class SampleCommand(commands.Command):
            """A command that is used for testing."""
            def execute(self):
                return real_run(self)
        return SampleCommand

    def test_looks_up_cmd(self):
        self.stub__find_command(lambda x:0)
        commands.run_argv(['testr', 'foo'], 'in', 'out', 'err')
        self.assertEqual(['foo'], self.calls)

    def test_looks_up_cmd_skips_options(self):
        self.stub__find_command(lambda x:0)
        commands.run_argv(['testr', '--version', 'foo'], 'in', 'out', 'err')
        self.assertEqual(['foo'], self.calls)

    def test_no_cmd_issues_help(self):
        self.stub__find_command(lambda x:0)
        commands.run_argv(['testr', '--version'], 'in', 'out', 'err')
        self.assertEqual(['help'], self.calls)

    def capture_ui(self, cmd):
        self.ui = cmd.ui
        return 0

    def test_runs_cmd_with_CLI_UI(self):
        self.stub__find_command(self.capture_ui)
        commands.run_argv(['testr', '--version', 'foo'], 'in', 'out', 'err')
        self.assertEqual(['foo'], self.calls)
        self.assertIsInstance(self.ui, cli.UI)

    def test_returns_0_when_None_returned_from_execute(self):
        self.stub__find_command(lambda x:None)
        self.assertEqual(0, commands.run_argv(['testr', 'foo'], 'in', 'out',
            'err'))

    def test_returns_execute_result(self):
        self.stub__find_command(lambda x:1)
        self.assertEqual(1, commands.run_argv(['testr', 'foo'], 'in', 'out',
            'err'))


class TestGetCommandParser(ResourcedTestCase):

    def test_trivial(self):
        cmd = InstrumentedCommand(model.UI())
        parser = commands.get_command_parser(cmd)
        self.assertThat(parser, IsInstance(optparse.OptionParser))


class InstrumentedCommand(commands.Command):
    """A command which records methods called on it.
    
    The first line is the summary.
    """

    def _init(self):
        self.calls = []

    def execute(self):
        self.calls.append('execute')
        return commands.Command.execute(self)

    def run(self):
        self.calls.append('run')


class TestAbstractCommand(ResourcedTestCase):

    def test_execute_calls_run(self):
        cmd = InstrumentedCommand(model.UI())
        self.assertEqual(0, cmd.execute())
        self.assertEqual(['execute', 'run'], cmd.calls)

    def test_execute_calls_set_command(self):
        ui = model.UI()
        cmd = InstrumentedCommand(ui)
        cmd.execute()
        self.assertEqual(cmd, ui.cmd)

    def test_execute_does_not_run_if_set_command_errors(self):
        class FailUI(object):
            def set_command(self, ui):
                return False
        cmd = InstrumentedCommand(FailUI())
        self.assertEqual(1, cmd.execute())

    def test_shows_errors_from_execute_returns_3(self):
        class FailCommand(commands.Command):
            def run(self):
                raise Exception("foo")
        ui = model.UI()
        cmd = FailCommand(ui)
        self.assertEqual(3, cmd.execute())
        self.assertEqual(1, len(ui.outputs))
        self.assertEqual('error', ui.outputs[0][0])
        self.assertThat(ui.outputs[0][1], MatchesException(Exception('foo')))

    def test_default_repository_factory(self):
        cmd = commands.Command(model.UI())
        self.assertIsInstance(cmd.repository_factory, file.RepositoryFactory)

    def test_get_summary(self):
        cmd = InstrumentedCommand
        self.assertEqual('A command which records methods called on it.',
            cmd.get_summary())
