# -*- 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 glib runner helper module."""

import subprocess

# pylint: disable=E0611
from gi.repository import GLib
# pylint: enable=E0611
from twisted.internet import defer

from ubuntu_kylin_sso.utils import runner
from ubuntu_kylin_sso.utils.runner import glib
from ubuntu_kylin_sso.utils.runner.tests.test_runner import SpawnProgramTestCase


class FakedSignal(object):
    """Fake a glib 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 glib Process."""

    _argv = _flags = None
    _pid = 123456
    _pids = []
    _status_code = 0

    SpawnFlags = GLib.SpawnFlags
    GError = GLib.GError

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

    def spawn_async(self, argv, flags):
        """Spwan a process."""
        if 'None' in argv:
            exc = GLib.GError()
            exc.message = str('Can not handle None')
            exc.code = 24
            raise exc

        self._argv = argv
        self._flags = flags

        try:
            subprocess.call(argv)
        except Exception as e:
            exc = GLib.GError()
            exc.message = str(e)
            exc.code = 42
            raise exc

        self._pids.append(self._pid)
        return (self._pid, None, None, None)

    def child_watch_add(self, pid, child_watch):
        """Addd a child watch."""
        if pid in self._pids:
            child_watch(pid, self._status_code)

    def spawn_close_pid(self, pid):
        """Close the 'pid'."""
        self._pids.remove(pid)


class GLibSpawnProgramTestCase(SpawnProgramTestCase):
    """The test suite for the spawn_program method (using GLib)."""

    use_reactor = False

    @defer.inlineCallbacks
    def setUp(self):
        yield super(GLibSpawnProgramTestCase, self).setUp()
        # Since we can't mix plan glib runner and the gireactor, we patch
        # GLib.spawn_async and fake the conditions so the glib runner is chosen
        self.process = FakedProcess()
        self.patch(glib, 'GLib', self.process)
        self.patch(runner, 'is_qt4_main_loop_installed', lambda: False)

    # Access to a protected member _flags, _argv of a client class
    # pylint: disable=W0212

    @defer.inlineCallbacks
    def test_flags_are_correct(self):
        """The flags are the correct ones."""
        yield self.spawn_fn(self.args)

        flags = GLib.SpawnFlags.DO_NOT_REAP_CHILD | \
                GLib.SpawnFlags.SEARCH_PATH | \
                GLib.SpawnFlags.STDOUT_TO_DEV_NULL | \
                GLib.SpawnFlags.STDERR_TO_DEV_NULL
        self.assertEqual(self.process._flags, flags)

    @defer.inlineCallbacks
    def test_argv_is_bytes(self):
        """The argv parameter is converted to bytes."""
        yield self.spawn_fn(self.args)

        bytes_args = [a.encode('utf-8') for a in self.args]
        self.assertEqual(self.process._argv, bytes_args)
