# -*- coding: utf-8 -*-
#
# Copyright 2012 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 3, as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the
# OpenSSL library under certain conditions as described in each
# individual source file, and distribute linked combinations
# including the two.
# You must obey the GNU General Public License in all respects
# for all of the code used other than OpenSSL.  If you modify
# file(s) with this exception, you may extend this exception to your
# version of the file(s), but you are not obligated to do so.  If you
# do not wish to do so, delete this exception statement from your
# version.  If you delete this exception statement from all source
# files in the program, then also delete it here.
"""Tests for the qt runner helper module."""

import subprocess

from PyQt4 import QtCore
from twisted.internet import defer

from ubuntu_kylin_sso.utils import compat, runner
from ubuntu_kylin_sso.utils.runner import qt
from ubuntu_kylin_sso.utils.runner.tests.test_runner import SpawnProgramTestCase


class FakedSignal(object):
    """Fake a Qt signal."""

    def __init__(self, name):
        self.name = name
        self._handlers = []

    def connect(self, handler):
        """Connect 'handler' with this signal."""
        self._handlers.append(handler)

    def emit(self, *args, **kwargs):
        """Emit this signal."""
        for handler in self._handlers:
            handler(*args, **kwargs)


class FakedProcess(object):
    """Fake a Qt Process."""

    _pid = 123456
    _error = None
    _status_code = 0

    FailedToStart = 0

    def __init__(self):
        self.pid = lambda: self._pid
        self.started = FakedSignal('started')
        self.finished = FakedSignal('finished')
        self.error = FakedSignal('error')

    def start(self, program, arguments):
        """Start this process."""
        if self._error is None:
            self.started.emit()

            args = (program,) + tuple(arguments)

            # subprocess expects bytes
            bytes_args = []
            for arg in args:
                if isinstance(arg, compat.text_type):
                    arg = arg.encode('utf-8')
                bytes_args.append(arg)

            try:
                subprocess.call(bytes_args)
            except OSError as e:
                if e.errno == 2:
                    self.error.emit(self.FailedToStart)
                else:
                    self.error.emit(e)
            except Exception as e:
                self.error.emit(e)
            else:
                self.finished.emit(self._status_code)
        else:
            self.error.emit(self._error)


class QtSpawnProgramTestCase(SpawnProgramTestCase):
    """The test suite for the spawn_program method (using Qt)."""

    use_reactor = False

    @defer.inlineCallbacks
    def setUp(self):
        yield super(QtSpawnProgramTestCase, self).setUp()
        # Since we can't mix plan qt runner and the qt4reactor, we patch
        # QProcess and fake the conditions so the qt runner is chosen
        self.process = FakedProcess()
        self.patch(QtCore, 'QProcess', lambda: self.process)
        self.patch(runner, 'is_qt4_main_loop_installed', lambda: True)

    @defer.inlineCallbacks
    def test_dont_let_it_go_success(self):
        """The QProcess instance is stored (and removed) to avoid GC."""
        # pylint: disable=W0212

        d = defer.Deferred()

        def check(status):
            """Do the check."""
            self.assertIn(self.process, qt._processes)
            d.callback(status)

        runner.qt.spawn_program(self.args,
            reply_handler=check, error_handler=lambda **kw: d.errback(kw))

        yield d

        self.assertNotIn(self.process, qt._processes)

    @defer.inlineCallbacks
    def test_dont_let_it_go_error(self):
        """The QProcess instance is stored (and removed) to avoid GC."""
        # pylint: disable=W0212

        d = defer.Deferred()

        def check(msg, failed_to_start):
            """Do the check."""
            self.assertIn(self.process, qt._processes)
            d.callback(msg)

        self.process._error = 42  # this will make the process emit 'error'

        runner.qt.spawn_program(self.args,
            reply_handler=lambda _: d.errback(AssertionError(_)),
            error_handler=check)

        yield d

        self.assertNotIn(self.process, qt._processes)
