#!/usr/bin/python3
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-

import os
import shutil

import apt
import unittest

from UpdateManager.Core import UpdateList
from UpdateManager.Core.MyCache import MyCache

from gi.repository import Gio
from mock import patch, PropertyMock, MagicMock

CURDIR = os.path.dirname(os.path.abspath(__file__))


class PhasedTestCase(unittest.TestCase):

    def setUp(self):
        # mangle the arch
        real_arch = apt.apt_pkg.config.find("APT::Architecture")
        apt.apt_pkg.config.set("APT::Architecture", "amd64")
        self.addCleanup(
            lambda: apt.apt_pkg.config.set("APT::Architecture", real_arch))

        self.aptroot = os.path.join(CURDIR,
                                    "aptroot-update-list-test")
        self.cache = MyCache(apt.progress.base.OpProgress(),
                             rootdir=self.aptroot)
        self.cache.open()
        self.updates_list = UpdateList.UpdateList(parent=None)

    def tearDown(self):
        try:
            shutil.rmtree(os.path.join(self.aptroot,
                          "var/cache/apt"))
        except OSError:
            pass

    def assertUpdatesListLen(self, nr):
        self.assertEqual(self.updates_list.num_updates, nr)

    def test_phased_percentage_not_included(self):
        """ Test that updates above the threshold are not included"""
        with patch.object(self.updates_list.random, "randint") as mock_randint:
            # threshold is 10
            mock_randint.return_value = 11
            self.updates_list.update(self.cache)
            self.assertUpdatesListLen(1)

    def test_phased_percentage_included(self):
        """ Test that updates below the threshold are included"""
        with patch.object(self.updates_list.random, "randint") as mock_randint:
            # threshold is 10
            mock_randint.return_value = 9
            self.updates_list.update(self.cache)
            self.assertUpdatesListLen(3)

    def test_second_phased_binary_not_included(self):
        """ Test that there is no overlap between the source packages of the
            packages being ignored and installed """
        with patch.object(self.updates_list.random, "randint") as mock_randint:
            mock_randint.return_value = 11
            self.updates_list.update(self.cache)
        ignored_srcs = set([pkg.candidate.source_name for pkg in
                            self.updates_list.ignored_phased_updates])
        group = self.updates_list.update_groups[0]
        install_srcs = set([x.pkg.candidate.source_name for x in group.items])
        self.assertEqual(ignored_srcs, set({'zsh'}))
        self.assertEqual(install_srcs, set({'apt'}))
        self.assertTrue(len(ignored_srcs & install_srcs) == 0)

    def test_phased_percentage_included_via_force(self):
        """ Test that the "always" override config works """
        # set config to force override
        apt.apt_pkg.config.set(
            self.updates_list.ALWAYS_INCLUDE_PHASED_UPDATES, "1")
        self.addCleanup(lambda: apt.apt_pkg.config.set(
            self.updates_list.ALWAYS_INCLUDE_PHASED_UPDATES, "0"))
        # ensure it's included even if it's above the threshold
        with patch.object(self.updates_list.random, "randint") as mock_randint:
            mock_randint.return_value = 100
            self.updates_list.update(self.cache)
            self.assertUpdatesListLen(3)

    def test_phased_percentage_excluded_via_force(self):
        """ Test that the "never" override config works """
        # set config to force override
        apt.apt_pkg.config.set(
            self.updates_list.NEVER_INCLUDE_PHASED_UPDATES, "1")
        self.addCleanup(lambda: apt.apt_pkg.config.set(
            self.updates_list.NEVER_INCLUDE_PHASED_UPDATES, "0"))
        # ensure it's excluded even if it's below the threshold
        with patch.object(self.updates_list.random, "randint") as mock_randint:
            mock_randint.return_value = 0
            self.updates_list.update(self.cache)
            self.assertUpdatesListLen(1)

    @patch('UpdateManager.Core.UpdateList.UpdateList._is_security_update')
    def test_phased_percentage_from_security(self, mock_security):
        """ Test that updates from the security node go in"""
        # pretend all updates come from security for the sake of this test
        mock_security.return_value = True
        with patch.object(self.updates_list.random, "randint") as mock_randint:
            mock_randint.return_value = 100
            self.updates_list.update(self.cache)
            self.assertUpdatesListLen(3)


class GroupingTestCase(unittest.TestCase):
    # installed_files does not respect aptroot, so we have to patch it
    @patch('apt.package.Package.installed_files', new_callable=PropertyMock)
    @patch('gi.repository.Gio.DesktopAppInfo.new_from_filename')
    def setUp(self, mock_desktop, mock_installed):
        # mangle the arch
        real_arch = apt.apt_pkg.config.find("APT::Architecture")
        apt.apt_pkg.config.set("APT::Architecture", "amd64")
        self.addCleanup(
            lambda: apt.apt_pkg.config.set("APT::Architecture", real_arch))

        def set_versioned_kernel_pkgs(real_versioned_kernel_pkgs):
            apt.apt_pkg.config.clear("APT::VersionedKernelPackages")
            for k in real_versioned_kernel_pkgs:
                apt.apt_pkg.config.set("APT::VersionedKernelPackages::", k)

        # override the kernel autoremove regex
        real_versioned_kernel_pkgs = apt.apt_pkg.config.value_list()
        set_versioned_kernel_pkgs(["linux-.*"])
        self.addCleanup(
            lambda: set_versioned_kernel_pkgs(real_versioned_kernel_pkgs))

        self.aptroot = os.path.join(CURDIR,
                                    "aptroot-grouping-test")
        self.cache = MyCache(apt.progress.base.OpProgress(),
                             rootdir=self.aptroot)
        self.cache.open()
        self.cache["linux-generic-to-remove"].mark_auto(True)
        self.cache["linux-headers-to-remove"].mark_auto(True)
        mock_installed.__get__ = self.fake_installed_files
        mock_desktop.side_effect = self.fake_desktop
        self.updates_list = UpdateList.UpdateList(parent=None, dist='lucid')
        self.updates_list.update(self.cache,
                                 duplicate_packages=["linux-generic-dup1",
                                                     "linux-generic-dup2"])

    def tearDown(self):
        try:
            shutil.rmtree(os.path.join(self.aptroot,
                          "var/cache/apt"))
        except OSError:
            pass

    def fake_installed_files(self, mock_prop, pkg, pkg_class):
        if pkg.name == 'installed-app':
            return ['/usr/share/applications/installed-app.desktop']
        elif pkg.name == 'installed-app-with-subitems':
            return ['/usr/share/applications/installed-app2.desktop']
        else:
            return []

    def fake_desktop(self, path):
        # These can all be the same for our purposes
        app = MagicMock()
        app.get_filename.return_value = path
        app.get_display_name.return_value = 'App ' + os.path.basename(path)
        app.get_icon.return_value = Gio.ThemedIcon.new("package")
        return app

    def test_app(self):
        self.assertGreater(len(self.updates_list.update_groups), 0)
        group = self.updates_list.update_groups[0]
        self.assertIsInstance(group, UpdateList.UpdateApplicationGroup)
        self.assertIsNotNone(group.core_item)
        self.assertEqual(group.core_item.pkg.name, 'installed-app')
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['installed-app'])

    def test_app_with_subitems(self):
        self.assertGreater(len(self.updates_list.update_groups), 1)
        group = self.updates_list.update_groups[1]
        self.assertIsInstance(group, UpdateList.UpdateApplicationGroup)
        self.assertIsNotNone(group.core_item)
        self.assertEqual(group.core_item.pkg.name,
                         'installed-app-with-subitems')
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['installed-app-with-subitems',
                              'installed-pkg-single-dep'])

    def test_pkg(self):
        self.assertGreater(len(self.updates_list.update_groups), 2)
        group = self.updates_list.update_groups[2]
        self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
        self.assertIsNotNone(group.core_item)
        self.assertEqual(group.core_item.pkg.name, 'installed-pkg')
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['installed-pkg'])

    def test_negative_regex_match(self):
        self.assertGreater(len(self.updates_list.update_groups), 3)
        group = self.updates_list.update_groups[3]
        self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
        self.assertIsNotNone(group.core_item)
        self.assertEqual(group.core_item.pkg.name, 'linux-show-player')
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['linux-show-player'])

    def test_pkg_multiple_deps(self):
        self.assertGreater(len(self.updates_list.update_groups), 4)
        group = self.updates_list.update_groups[4]
        self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
        self.assertIsNotNone(group.core_item)
        self.assertEqual(group.core_item.pkg.name,
                         'installed-pkg-multiple-deps')
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['installed-pkg-multiple-deps'])

    def test_security(self):
        self.assertEqual(len(self.updates_list.security_groups), 1)
        group = self.updates_list.security_groups[0]
        self.assertIsInstance(group, UpdateList.UpdateSystemGroup)
        self.assertIsNone(group.core_item)
        self.assertListEqual([x.pkg.name for x in group.items], ['base-pkg'])

    def test_base_grouping(self):
        self.assertEqual(len(self.updates_list.update_groups), 6)
        group = self.updates_list.update_groups[5]
        self.assertIsInstance(group, UpdateList.UpdateSystemGroup)
        self.assertIsNone(group.core_item)
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['ubuntu-minimal',
                              'linux-test-hwe'])

    def test_autoremove_grouping(self):
        # test that packages staged for removal aren't grouped if they are
        # kernel autoremove packages (LP: #1912718)
        self.assertGreater(len(self.updates_list.kernel_autoremove_groups), 0)
        group = self.updates_list.kernel_autoremove_groups[0]
        self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
        self.assertIsNotNone(group.core_item)
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['linux-generic-to-remove'])

        self.assertEqual(len(self.updates_list.kernel_autoremove_groups), 2)
        group = self.updates_list.kernel_autoremove_groups[1]
        self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
        self.assertIsNotNone(group.core_item)
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['linux-headers-to-remove'])

    def test_duplicate_grouping(self):
        # test that packages staged for removal aren't grouped if they are
        # duplicate packages (LP: #1912718)
        self.assertGreater(len(self.updates_list.duplicate_groups), 0)
        group = self.updates_list.duplicate_groups[0]
        self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
        self.assertIsNotNone(group.core_item)
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['linux-generic-dup1'])

        self.assertEqual(len(self.updates_list.duplicate_groups), 2)
        group = self.updates_list.duplicate_groups[1]
        self.assertIsInstance(group, UpdateList.UpdatePackageGroup)
        self.assertIsNotNone(group.core_item)
        self.assertListEqual([x.pkg.name for x in group.items],
                             ['linux-generic-dup2'])


if __name__ == "__main__":
    unittest.main()
