1# -*- coding: utf-8 -*-
2
3# Copyright (C) 2012-2018 Red Hat, Inc.
4#
5# This copyrighted material is made available to anyone wishing to use,
6# modify, copy, or redistribute it subject to the terms and conditions of
7# the GNU General Public License v.2, or (at your option) any later version.
8# This program is distributed in the hope that it will be useful, but WITHOUT
9# ANY WARRANTY expressed or implied, including the implied warranties of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
11# Public License for more details.  You should have received a copy of the
12# GNU General Public License along with this program; if not, write to the
13# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
14# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
15# source code or documentation are not subject to the GNU General Public
16# License and may only be used or replicated with the express permission of
17# Red Hat, Inc.
18#
19
20from __future__ import absolute_import
21from __future__ import unicode_literals
22
23import operator
24
25import libdnf.transaction
26
27import dnf.comps
28import dnf.util
29
30import tests.support
31from tests.support import mock
32
33
34class EmptyPersistorTest(tests.support.ResultTestCase):
35    """Test group operations with empty persistor."""
36
37    REPOS = ['main']
38    COMPS = True
39
40    @mock.patch('locale.getlocale', return_value=('cs_CZ', 'UTF-8'))
41    def test_group_install_locale(self, _unused):
42        grp = self.comps.group_by_pattern('Kritick\xe1 cesta (Z\xe1klad)')
43        cnt = self.base.group_install(grp.id, ('mandatory',))
44        self.assertEqual(cnt, 2)
45
46    def test_finalize_comps_trans(self):
47        trans = dnf.comps.TransactionBunch()
48        trans.install = ('trampoline',)
49        self.assertGreater(self.base._add_comps_trans(trans), 0)
50        self.base._finalize_comps_trans()
51        self.assertIn('trampoline', self.base._goal.group_members)
52        (installed, removed) = self.installed_removed(self.base)
53        self.assertCountEqual(map(str, installed), ('trampoline-2.1-1.noarch',))
54        self.assertEmpty(removed)
55
56
57class PresetPersistorTest(tests.support.ResultTestCase):
58    """Test group operations with some data in the persistor."""
59
60    REPOS = ['main']
61    COMPS = True
62    COMPS_SEED_HISTORY = True
63
64    def _install_test_env(self):
65        """Env installation itself does not handle packages. We need to handle
66           them manually for proper functionality of env remove"""
67
68        env_id = 'sugar-desktop-environment'
69        comps_env = self.comps._environment_by_id(env_id)
70        self.base.environment_install(comps_env.id, ('mandatory',))
71        self._swdb_commit()
72
73        swdb_env = self.history.env.get(comps_env.id)
74        self.assertIsNotNone(swdb_env)
75
76        for comps_group in comps_env.mandatory_groups:
77            swdb_group = self.history.group.get(comps_group.id)
78            self.assertIsNotNone(swdb_group)
79
80        tsis = []
81        seen_pkgs = set()
82        for swdb_env_group in swdb_env.getGroups():
83            swdb_group = self.history.group.get(swdb_env_group.getGroupId())
84            if not swdb_group:
85                continue
86            for swdb_pkg in swdb_group.getPackages():
87                swdb_pkg.setInstalled(True)
88                pkgs = self.base.sack.query().filter(name=swdb_pkg.getName(), arch="x86_64").run()
89                if not pkgs:
90                    continue
91                pkg = pkgs[0]
92                if pkg in seen_pkgs:
93                    # prevent RPMs from being twice in a transaction and triggering unique constraint error
94                    continue
95                seen_pkgs.add(pkg)
96                pkg._force_swdb_repoid = "main"
97                self.history.rpm.add_install(pkg, reason=libdnf.transaction.TransactionItemReason_GROUP)
98#                tsi = dnf.transaction.TransactionItem(
99#                    dnf.transaction.INSTALL,
100#                    installed=pkg,
101#                    reason=libdnf.transaction.TransactionItemReason_GROUP
102#                )
103#                tsis.append(tsi)
104
105        self._swdb_commit(tsis)
106
107    def _install_test_group(self):
108        """Group installation itself does not handle packages. We need to
109           handle them manually for proper functionality of group remove"""
110        group_id = 'somerset'
111        self.base.group_install(group_id, ('mandatory',))
112        swdb_group = self.history.group._installed[group_id]
113        tsis = []
114        for swdb_pkg in swdb_group.getPackages():
115            swdb_pkg.setInstalled(True)
116            pkgs = self.base.sack.query().filter(name=swdb_pkg.getName(), arch="x86_64").run()
117            if not pkgs:
118                continue
119            pkg = pkgs[0]
120            pkg._force_swdb_repoid = "main"
121            self.history.rpm.add_install(pkg, reason=libdnf.transaction.TransactionItemReason_GROUP)
122#            tsi = dnf.transaction.TransactionItem(
123#                dnf.transaction.INSTALL,
124#                installed=pkg,
125#                reason=libdnf.transaction.TransactionItemReason_GROUP
126#            )
127#            tsis.append(tsi)
128
129        self._swdb_commit(tsis)
130        self.base.reset(goal=True)
131
132    def test_env_group_remove(self):
133        self._install_test_env()
134        env_id = 'sugar-desktop-environment'
135        pkg_count = self.base.env_group_remove([env_id])
136        self._swdb_commit()
137        self.assertEqual(3, pkg_count)
138        with tests.support.mock.patch('logging.Logger.error'):
139            self.assertRaises(dnf.exceptions.Error,
140                              self.base.env_group_remove,
141                              ['nonexistent'])
142
143    def test_environment_remove(self):
144        self._install_test_env()
145        env_id = 'sugar-desktop-environment'
146        swdb_env = self.history.env.get(env_id)
147        self.assertIsNotNone(swdb_env)
148        self.assertEqual(swdb_env.getEnvironmentId(), 'sugar-desktop-environment')
149
150        removed_pkg_count = self.base.environment_remove(env_id)
151        self.assertGreater(removed_pkg_count, 0)
152        self._swdb_commit()
153
154        swdb_env = self.history.env.get(env_id)
155        self.assertIsNone(swdb_env)
156
157        peppers = self.history.group.get('Peppers')
158        self.assertIsNone(peppers)
159
160        somerset = self.history.group.get('somerset')
161        self.assertIsNone(somerset)
162
163    def test_env_upgrade(self):
164        self._install_test_env()
165        cnt = self.base.environment_upgrade("sugar-desktop-environment")
166        self.assertEqual(5, cnt)
167
168        peppers = self.history.group.get('Peppers')
169        self.assertIsNotNone(peppers)
170
171        somerset = self.history.group.get('somerset')
172        self.assertIsNotNone(somerset)
173
174    def test_group_install(self):
175        comps_group = self.base.comps.group_by_pattern('Base')
176        pkg_count = self.base.group_install(comps_group.id, ('mandatory',))
177        self.assertEqual(pkg_count, 2)
178        self._swdb_commit()
179
180        installed, removed = self.installed_removed(self.base)
181        self.assertEmpty(installed)
182        self.assertEmpty(removed)
183        swdb_group = self.history.group.get(comps_group.id)
184        self.assertIsNotNone(swdb_group)
185
186    def test_group_remove(self):
187        self._install_test_group()
188        group_id = 'somerset'
189
190        pkgs_removed = self.base.group_remove(group_id)
191        self.assertGreater(pkgs_removed, 0)
192
193        self._swdb_begin()
194        installed, removed = self.installed_removed(self.base)
195        self.assertEmpty(installed)
196        self.assertCountEqual([pkg.name for pkg in removed], ('pepper',))
197        self._swdb_end()
198
199
200class ProblemGroupTest(tests.support.ResultTestCase):
201    """Test some cases involving problems in groups: packages that
202    don't exist, and packages that exist but cannot be installed. The
203    "broken" group lists three packages. "meaning-of-life", explicitly
204    'default', does not exist. "lotus", implicitly 'mandatory' (no
205    explicit type), exists and is installable. "brokendeps",
206    explicitly 'optional', exists but has broken dependencies. See
207    https://bugzilla.redhat.com/show_bug.cgi?id=1292892,
208    https://bugzilla.redhat.com/show_bug.cgi?id=1337731,
209    https://bugzilla.redhat.com/show_bug.cgi?id=1427365, and
210    https://bugzilla.redhat.com/show_bug.cgi?id=1461539 for some of
211    the background on this.
212    """
213
214    REPOS = ['main', 'broken_group']
215    COMPS = True
216    COMPS_SEED_PERSISTOR = True
217
218    def test_group_install_broken_mandatory(self):
219        """Here we will test installing the group with only mandatory
220        packages. We expect this to succeed, leaving out the
221        non-existent 'meaning-of-life': it should also log a warning,
222        but we don't test that.
223        """
224        comps_group = self.base.comps.group_by_pattern('Broken Group')
225        swdb_group = self.history.group.get(comps_group.id)
226        self.assertIsNone(swdb_group)
227
228        cnt = self.base.group_install(comps_group.id, ('mandatory',))
229        self._swdb_commit()
230        self.base.resolve()
231        # this counts packages *listed* in the group, so 2
232        self.assertEqual(cnt, 2)
233
234        inst, removed = self.installed_removed(self.base)
235        # the above should work, but only 'lotus' actually installed
236        self.assertLength(inst, 1)
237        self.assertEmpty(removed)
238
239    def test_group_install_broken_default(self):
240        """Here we will test installing the group with only mandatory
241        and default packages. Again we expect this to succeed: the new
242        factor is an entry pulling in librita if no-such-package is
243        also included or installed. We expect this not to actually
244        pull in librita (as no-such-package obviously *isn't* there),
245        but also not to cause a fatal error.
246        """
247        comps_group = self.base.comps.group_by_pattern('Broken Group')
248        swdb_group = self.history.group.get(comps_group.id)
249        self.assertIsNone(swdb_group)
250
251        cnt = self.base.group_install(comps_group.id, ('mandatory', 'default'))
252        self._swdb_commit()
253        self.base.resolve()
254        # this counts packages *listed* in the group, so 3
255        self.assertEqual(cnt, 3)
256
257        inst, removed = self.installed_removed(self.base)
258        # the above should work, but only 'lotus' actually installed
259        self.assertLength(inst, 1)
260        self.assertEmpty(removed)
261
262    def test_group_install_broken_optional(self):
263        """Here we test installing the group with optional packages
264        included. We expect this to fail, as a package that exists but
265        has broken dependencies is now included.
266        """
267        comps_group = self.base.comps.group_by_pattern('Broken Group')
268        swdb_group = self.history.group.get(comps_group.id)
269        self.assertIsNone(swdb_group)
270
271        cnt = self.base.group_install(comps_group.id, ('mandatory', 'default', 'optional'))
272        self.assertEqual(cnt, 4)
273
274        self._swdb_commit()
275        # this should fail, as optional 'brokendeps' is now pulled in
276        self.assertRaises(dnf.exceptions.DepsolveError, self.base.resolve)
277
278    def test_group_install_broken_optional_nonstrict(self):
279        """Here we test installing the group with optional packages
280        included, but with strict=False. We expect this to succeed,
281        skipping the package with broken dependencies.
282        """
283        comps_group = self.base.comps.group_by_pattern('Broken Group')
284        swdb_group = self.history.group.get(comps_group.id)
285        self.assertIsNone(swdb_group)
286
287        cnt = self.base.group_install(comps_group.id, ('mandatory', 'default', 'optional'),
288                                      strict=False)
289        self._swdb_commit()
290        self.base.resolve()
291        self.assertEqual(cnt, 4)
292
293        inst, removed = self.installed_removed(self.base)
294        # the above should work, but only 'lotus' actually installed
295        self.assertLength(inst, 1)
296        self.assertEmpty(removed)
297
298    def test_group_install_missing_name(self):
299        comps_group = self.base.comps.group_by_pattern('missing-name-group')
300
301        cnt = self.base.group_install(comps_group.id, ('mandatory', 'default', 'optional'),
302                                      strict=False)
303        self._swdb_commit()
304        self.base.resolve()
305        self.assertEqual(cnt, 1)
306
307
308class EnvironmentInstallTest(tests.support.ResultTestCase):
309    """Set up a test where sugar is considered not installed."""
310
311    REPOS = ['main']
312    COMPS = True
313    COMPS_SEED_HISTORY = True
314
315    def test_environment_install(self):
316        # actually commit the pre-mocked comps, as otherwise
317        # 'sugar-desktop-environment' is already present in the open
318        # transaction and it wins over the one installed here
319        self._swdb_commit()
320
321        env_id = 'sugar-desktop-environment'
322        comps_env = self.comps.environment_by_pattern(env_id)
323        self.base.environment_install(comps_env.id, ('mandatory',))
324        self._swdb_commit()
325
326        installed, _ = self.installed_removed(self.base)
327        self.assertCountEqual(map(operator.attrgetter('name'), installed),
328                              ('trampoline', 'lotus'))
329
330        swdb_env = self.history.env.get(env_id)
331        self.assertCountEqual([i.getGroupId() for i in swdb_env.getGroups()], ('somerset', 'Peppers', 'base'))
332
333        peppers = self.history.group.get('Peppers')
334        self.assertIsNotNone(peppers)
335
336        somerset = self.history.group.get('somerset')
337        self.assertIsNotNone(somerset)
338
339        base = self.history.group.get('base')
340        self.assertIsNone(base)
341