31
30
class PackageReporterConfigurationTest(unittest.TestCase):
33
def test_force_smart_update_option(self):
32
def test_force_apt_update_option(self):
35
The L{PackageReporterConfiguration} supports a '--force-smart-update'
34
The L{PackageReporterConfiguration} supports a '--force-apt-update'
36
35
command line option.
38
37
config = PackageReporterConfiguration()
39
self.assertFalse(config.force_smart_update)
40
config.load(["--force-smart-update"])
41
self.assertTrue(config.force_smart_update)
44
class PackageReporterTestMixin(object):
38
self.assertFalse(config.force_apt_update)
39
config.load(["--force-apt-update"])
40
self.assertTrue(config.force_apt_update)
43
class PackageReporterAptTest(LandscapeTest):
45
helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
52
self.store = PackageStore(self.makeFile())
53
self.config = PackageReporterConfiguration()
54
self.reporter = PackageReporter(
55
self.store, self.facade, self.remote, self.config)
56
self.config.data_path = self.makeDir()
57
os.mkdir(self.config.package_directory)
59
result = super(PackageReporterAptTest, self).setUp()
60
return result.addCallback(set_up)
62
def _clear_repository(self):
63
"""Remove all packages from self.repository."""
64
create_file(self.repository_dir + "/Packages", "")
66
def set_pkg1_upgradable(self):
67
"""Make it so that package "name1" is considered to be upgradable.
69
Return the hash of the package that upgrades "name1".
71
self._add_package_to_deb_dir(
72
self.repository_dir, "name1", version="version2")
73
self.facade.reload_channels()
74
name1_upgrade = sorted(self.facade.get_packages_by_name("name1"))[1]
75
return self.facade.get_package_hash(name1_upgrade)
77
def set_pkg1_installed(self):
78
"""Make it so that package "name1" is considered installed."""
79
self._install_deb_file(os.path.join(self.repository_dir, PKGNAME1))
81
def _make_fake_apt_update(self, out="output", err="error", code=0):
82
"""Create a fake apt-update executable"""
83
self.reporter.apt_update_filename = self.makeFile(
87
"exit %d" % (out, err, code))
88
os.chmod(self.reporter.apt_update_filename, 0755)
46
90
def test_set_package_ids_with_all_known(self):
47
91
self.store.add_hash_id_request(["hash1", "hash2"])
581
567
content="deb http://foo ./")
582
568
self.assertTrue(self.reporter._apt_sources_have_changed())
584
def test_run_smart_update_with_force_smart_update_if_sources_changed(self):
586
L{PackageReporter.run_smart_update} forces a smart-update run if
587
the APT sources.list file has changed.
590
self.assertEqual(self.reporter.sources_list_filename,
591
"/etc/apt/sources.list")
592
self.reporter.sources_list_filename = self.makeFile("deb ftp://url ./")
593
self.reporter.smart_update_filename = self.makeFile(
594
"#!/bin/sh\necho -n $@")
595
os.chmod(self.reporter.smart_update_filename, 0755)
597
deferred = Deferred()
600
result = self.reporter.run_smart_update()
602
def callback((out, err, code)):
603
# Smart update was called without the --after parameter
604
self.assertEqual(out, "")
605
result.addCallback(callback)
606
result.chainDeferred(deferred)
608
reactor.callWhenRunning(do_test)
611
def test_run_smart_update_warns_about_failures(self):
613
The L{PackageReporter.run_smart_update} method should log a warning
614
in case smart-update terminates with a non-zero exit code other than 1.
616
self.reporter.smart_update_filename = self.makeFile(
617
"#!/bin/sh\necho -n error >&2\necho -n output\nexit 2")
618
os.chmod(self.reporter.smart_update_filename, 0755)
619
logging_mock = self.mocker.replace("logging.warning")
620
logging_mock("'%s' exited with status 2"
621
" (error)" % self.reporter.smart_update_filename)
623
deferred = Deferred()
626
result = self.reporter.run_smart_update()
628
def callback((out, err, code)):
629
self.assertEqual(out, "output")
630
self.assertEqual(err, "error")
631
self.assertEqual(code, 2)
632
result.addCallback(callback)
633
result.chainDeferred(deferred)
635
reactor.callWhenRunning(do_test)
638
def test_run_smart_update_report_smart_failure(self):
640
If L{PackageReporter.run_smart_update} fails, a message is sent to the
641
server reporting the error, to be able to fix the problem centrally.
643
message_store = self.broker_service.message_store
644
message_store.set_accepted_types(["package-reporter-result"])
645
self.reporter.smart_update_filename = self.makeFile(
646
"#!/bin/sh\necho -n error >&2\necho -n output\nexit 2")
647
os.chmod(self.reporter.smart_update_filename, 0755)
648
deferred = Deferred()
651
result = self.reporter.run_smart_update()
653
def callback(ignore):
654
self.assertMessages(message_store.get_pending_messages(),
655
[{"type": "package-reporter-result",
656
"code": 2, "err": u"error"}])
657
result.addCallback(callback)
658
result.chainDeferred(deferred)
660
reactor.callWhenRunning(do_test)
663
def test_run_smart_update_report_no_sources(self):
665
L{PackageReporter.run_smart_update} reports a failure if smart
666
succeeds but there are no APT sources defined. Smart doesn't
667
fail if there are no sources, but we fake a failure in order to
668
re-use the PackageReporterAlert on the server.
670
self.facade.reset_channels()
671
message_store = self.broker_service.message_store
672
message_store.set_accepted_types(["package-reporter-result"])
673
self.reporter.smart_update_filename = self.makeFile(
674
"#!/bin/sh\necho -n error >&2\necho -n output\nexit 0")
675
os.chmod(self.reporter.smart_update_filename, 0755)
676
deferred = Deferred()
679
result = self.reporter.run_smart_update()
681
def callback(ignore):
682
error = "There are no APT sources configured in %s or %s." % (
683
self.reporter.sources_list_filename,
684
self.reporter.sources_list_directory)
685
self.assertMessages(message_store.get_pending_messages(),
686
[{"type": "package-reporter-result",
687
"code": 1, "err": error}])
688
result.addCallback(callback)
689
result.chainDeferred(deferred)
691
reactor.callWhenRunning(do_test)
694
def test_run_smart_update_report_smart_failure_no_sources(self):
696
If L{PackageReporter.run_smart_update} fails and there are no
697
APT sources configured, the Smart error takes precedence.
699
self.facade.reset_channels()
700
message_store = self.broker_service.message_store
701
message_store.set_accepted_types(["package-reporter-result"])
702
self.reporter.smart_update_filename = self.makeFile(
703
"#!/bin/sh\necho -n error >&2\necho -n output\nexit 2")
704
os.chmod(self.reporter.smart_update_filename, 0755)
705
deferred = Deferred()
708
result = self.reporter.run_smart_update()
710
def callback(ignore):
711
self.assertMessages(message_store.get_pending_messages(),
712
[{"type": "package-reporter-result",
713
"code": 2, "err": u"error"}])
714
result.addCallback(callback)
715
result.chainDeferred(deferred)
717
reactor.callWhenRunning(do_test)
720
def test_run_smart_update_report_success(self):
722
L{PackageReporter.run_smart_update} also reports success to be able to
723
know the proper state of the client.
725
message_store = self.broker_service.message_store
726
message_store.set_accepted_types(["package-reporter-result"])
727
self.reporter.smart_update_filename = self.makeFile(
728
"#!/bin/sh\necho -n error >&2\necho -n output\nexit 0")
729
os.chmod(self.reporter.smart_update_filename, 0755)
730
deferred = Deferred()
733
result = self.reporter.run_smart_update()
735
def callback(ignore):
736
self.assertMessages(message_store.get_pending_messages(),
737
[{"type": "package-reporter-result",
738
"code": 0, "err": u"error"}])
739
result.addCallback(callback)
740
result.chainDeferred(deferred)
742
reactor.callWhenRunning(do_test)
745
def test_run_smart_update_warns_exit_code_1_and_non_empty_stderr(self):
747
The L{PackageReporter.run_smart_update} method should log a warning
748
in case smart-update terminates with exit code 1 and non empty stderr.
750
self.reporter.smart_update_filename = self.makeFile(
751
"#!/bin/sh\necho -n \"error \" >&2\nexit 1")
752
os.chmod(self.reporter.smart_update_filename, 0755)
753
logging_mock = self.mocker.replace("logging.warning")
754
logging_mock("'%s' exited with status 1"
755
" (error )" % self.reporter.smart_update_filename)
757
deferred = Deferred()
760
result = self.reporter.run_smart_update()
762
def callback((out, err, code)):
763
self.assertEqual(out, "")
764
self.assertEqual(err, "error ")
765
self.assertEqual(code, 1)
766
result.addCallback(callback)
767
result.chainDeferred(deferred)
769
reactor.callWhenRunning(do_test)
772
def test_run_smart_update_ignores_exit_code_1_and_empty_output(self):
774
The L{PackageReporter.run_smart_update} method should not log anything
775
in case smart-update terminates with exit code 1 and output containing
776
only a newline character.
778
self.reporter.smart_update_filename = self.makeFile(
779
"#!/bin/sh\necho\nexit 1")
780
os.chmod(self.reporter.smart_update_filename, 0755)
781
logging_mock = self.mocker.replace("logging.warning")
782
self.expect(logging_mock(ANY)).count(0)
784
deferred = Deferred()
788
result = self.reporter.run_smart_update()
790
def callback((out, err, code)):
791
self.assertEqual(out, "\n")
792
self.assertEqual(err, "")
793
self.assertEqual(code, 1)
794
result.addCallback(callback)
795
result.chainDeferred(deferred)
797
reactor.callWhenRunning(do_test)
800
def test_run_smart_update_touches_stamp_file(self):
802
The L{PackageReporter.run_smart_update} method touches a stamp file
803
after running the smart-update wrapper.
805
self.reporter.sources_list_filename = "/I/Dont/Exist"
806
self.reporter.smart_update_filename = "/bin/true"
807
deferred = Deferred()
811
result = self.reporter.run_smart_update()
813
def callback(ignored):
815
os.path.exists(self.config.update_stamp_filename))
816
result.addCallback(callback)
817
result.chainDeferred(deferred)
819
reactor.callWhenRunning(do_test)
822
570
def test_remove_expired_hash_id_request(self):
823
571
request = self.store.add_hash_id_request(["hash1"])
824
572
request.message_id = 9999
1371
1096
elif request.id == request2.id:
1372
1097
self.assertEqual(request.hashes, ["hash4"])
1373
1098
elif not new_request_found:
1374
self.assertEqual(request.hashes, [HASH3])
1099
self.assertEqual(request.hashes, [HASH3, HASH1])
1376
1101
self.fail("Unexpected hash-id request!")
1377
1102
self.assertEqual(requests_count, 3)
1379
# XXX: Don't check for package-locks messages until package
1380
# locks are implemented in AptFacade.
1381
if not isinstance(self.facade, AptFacade):
1382
self.assertMessages(message_store.get_pending_messages(),
1383
[{"type": "package-locks",
1384
"created": [("name1", "", "")]}])
1386
1104
deferred.addCallback(check_result)
1387
1105
return deferred
1390
class PackageReporterSmartTest(LandscapeTest, PackageReporterTestMixin):
1392
helpers = [SmartFacadeHelper, BrokerServiceHelper]
1396
def set_up(ignored):
1397
self.store = PackageStore(self.makeFile())
1398
self.config = PackageReporterConfiguration()
1399
self.reporter = PackageReporter(
1400
self.store, self.facade, self.remote, self.config)
1401
self.config.data_path = self.makeDir()
1402
os.mkdir(self.config.package_directory)
1404
result = super(PackageReporterSmartTest, self).setUp()
1405
return result.addCallback(set_up)
1407
def _clear_repository(self):
1408
"""Remove all packages from self.repository."""
1409
for filename in glob.glob(self.repository_dir + "/*"):
1412
def set_pkg1_upgradable(self):
1413
"""Make it so that package "name1" is considered to be upgradable.
1415
Return the hash of the package that upgrades "name1".
1417
previous = self.Facade.channels_reloaded
1420
from smart.backends.deb.base import DebUpgrades
1422
pkg2 = self.get_packages_by_name("name2")[0]
1423
pkg2.upgrades += (DebUpgrades("name1", "=", "version1-release1"),)
1424
self.reload_cache() # Relink relations.
1425
self.Facade.channels_reloaded = callback
1428
def set_pkg1_installed(self):
1429
"""Make it so that package "name1" is considered installed."""
1430
previous = self.Facade.channels_reloaded
1434
self.get_packages_by_name("name1")[0].installed = True
1435
self.Facade.channels_reloaded = callback
1437
def test_detect_packages_changes_with_locked(self):
1439
If Smart indicates locked packages we didn't know about, report
1442
message_store = self.broker_service.message_store
1443
message_store.set_accepted_types(["packages"])
1445
self.facade.set_package_lock("name1")
1446
self.facade.set_package_lock("name2", ">=", "version2")
1448
self.store.set_hash_ids({HASH1: 1, HASH2: 2})
1449
self.store.add_available([1, 2])
1451
def got_result(result):
1452
self.assertMessages(message_store.get_pending_messages(),
1453
[{"type": "packages", "locked": [1, 2]}])
1454
self.assertEqual(sorted(self.store.get_locked()), [1, 2])
1456
result = self.reporter.detect_packages_changes()
1457
return result.addCallback(got_result)
1459
def test_detect_packages_changes_with_locked_and_ranges(self):
1461
Ranges are used when reporting changes to 3 or more locked packages
1462
having consecutive ids.
1464
message_store = self.broker_service.message_store
1465
message_store.set_accepted_types(["packages"])
1467
self.facade.set_package_lock("name1")
1468
self.facade.set_package_lock("name2", ">=", "version2")
1469
self.facade.set_package_lock("name3", "<", "version4")
1471
self.store.set_hash_ids({HASH1: 1, HASH2: 2, HASH3: 3})
1472
self.store.add_available([1, 2, 3])
1474
def got_result(result):
1475
self.assertMessages(message_store.get_pending_messages(),
1476
[{"type": "packages", "locked": [(1, 3)]}])
1477
self.assertEqual(sorted(self.store.get_locked()), [1, 2, 3])
1479
result = self.reporter.detect_packages_changes()
1480
return result.addCallback(got_result)
1482
def test_detect_packages_changes_with_locked_with_unknown_hash(self):
1484
Locked packages whose hashes are unknown don't get reported.
1486
self.facade.set_package_lock("name1")
1488
def got_result(result):
1489
self.assertEqual(self.store.get_locked(), [])
1491
result = self.reporter.detect_packages_changes()
1492
return result.addCallback(got_result)
1494
def test_detect_packages_changes_with_locked_and_previously_known(self):
1496
We don't report locked packages we already know about.
1498
message_store = self.broker_service.message_store
1499
message_store.set_accepted_types(["packages"])
1501
self.facade.set_package_lock("name1")
1502
self.facade.set_package_lock("name2", ">=", "version2")
1504
self.store.set_hash_ids({HASH1: 1, HASH2: 2})
1505
self.store.add_available([1, 2])
1506
self.store.add_locked([1])
1508
def got_result(result):
1509
self.assertMessages(message_store.get_pending_messages(),
1510
[{"type": "packages", "locked": [2]}])
1512
self.assertEqual(sorted(self.store.get_locked()), [1, 2])
1514
result = self.reporter.detect_packages_changes()
1515
return result.addCallback(got_result)
1517
def test_detect_packages_changes_with_not_locked(self):
1519
We report when a package was previously locked and isn't anymore.
1521
message_store = self.broker_service.message_store
1522
message_store.set_accepted_types(["packages"])
1524
self.store.set_hash_ids({HASH1: 1})
1525
self.store.add_available([1])
1526
self.store.add_locked([1])
1528
def got_result(result):
1529
self.assertMessages(message_store.get_pending_messages(),
1530
[{"type": "packages", "not-locked": [1]}])
1531
self.assertEqual(self.store.get_locked(), [])
1533
result = self.reporter.detect_packages_changes()
1534
return result.addCallback(got_result)
1536
def test_detect_package_locks_changes_with_create_locks(self):
1538
If Smart indicates package locks we didn't know about, report
1541
message_store = self.broker_service.message_store
1542
message_store.set_accepted_types(["package-locks"])
1544
self.facade.set_package_lock("name")
1546
logging_mock = self.mocker.replace("logging.info")
1547
logging_mock("Queuing message with changes in known package locks:"
1548
" 1 created, 0 deleted.")
1549
self.mocker.replay()
1551
def got_result(result):
1552
self.assertMessages(message_store.get_pending_messages(),
1553
[{"type": "package-locks",
1554
"created": [("name", "", "")]}])
1555
self.assertEqual(self.store.get_package_locks(),
1558
result = self.reporter.detect_package_locks_changes()
1559
return result.addCallback(got_result)
1561
def test_detect_package_locks_changes_with_already_known_locks(self):
1563
We don't report changes about locks we already know about.
1565
message_store = self.broker_service.message_store
1566
message_store.set_accepted_types(["package-locks"])
1568
self.facade.set_package_lock("name1")
1569
self.facade.set_package_lock("name2", "<", "1.2")
1571
self.store.add_package_locks([("name1", "", "")])
1573
logging_mock = self.mocker.replace("logging.info")
1574
logging_mock("Queuing message with changes in known package locks:"
1575
" 1 created, 0 deleted.")
1576
self.mocker.replay()
1578
def got_result(result):
1579
self.assertMessages(message_store.get_pending_messages(),
1580
[{"type": "package-locks",
1581
"created": [("name2", "<", "1.2")]}])
1582
self.assertEqual(sorted(self.store.get_package_locks()),
1584
("name2", "<", "1.2")])
1586
result = self.reporter.detect_package_locks_changes()
1587
return result.addCallback(got_result)
1589
def test_detect_package_locks_changes_with_deleted_locks(self):
1591
If Smart indicates newly unset package locks, report them to the
1594
message_store = self.broker_service.message_store
1595
message_store.set_accepted_types(["package-locks"])
1597
self.store.add_package_locks([("name1", "", "")])
1599
logging_mock = self.mocker.replace("logging.info")
1600
logging_mock("Queuing message with changes in known package locks:"
1601
" 0 created, 1 deleted.")
1602
self.mocker.replay()
1604
def got_result(result):
1605
self.assertMessages(message_store.get_pending_messages(),
1606
[{"type": "package-locks",
1607
"deleted": [("name1", "", "")]}])
1608
self.assertEqual(self.store.get_package_locks(), [])
1610
result = self.reporter.detect_package_locks_changes()
1611
return result.addCallback(got_result)
1613
def test_detect_package_locks_changes_with_locked_already_known(self):
1615
If we didn't detect any change in the package locks, we don't send any
1616
message, and we return a deferred resulting in C{False}.
1618
message_store = self.broker_service.message_store
1619
message_store.set_accepted_types(["package-locks"])
1621
self.facade.set_package_lock("name1")
1622
self.store.add_package_locks([("name1", "", "")])
1624
def got_result(result):
1625
self.assertFalse(result)
1626
self.assertMessages(message_store.get_pending_messages(), [])
1628
result = self.reporter.detect_packages_changes()
1629
return result.addCallback(got_result)
1632
class PackageReporterAptTest(LandscapeTest, PackageReporterTestMixin):
1634
if not has_new_enough_apt:
1635
skip = "Can't use AptFacade on hardy"
1637
helpers = [AptFacadeHelper, SimpleRepositoryHelper, BrokerServiceHelper]
1643
def set_up(ignored):
1644
self.store = PackageStore(self.makeFile())
1645
self.config = PackageReporterConfiguration()
1646
self.reporter = PackageReporter(
1647
self.store, self.facade, self.remote, self.config)
1648
self.config.data_path = self.makeDir()
1649
os.mkdir(self.config.package_directory)
1651
result = super(PackageReporterAptTest, self).setUp()
1652
return result.addCallback(set_up)
1654
def _clear_repository(self):
1655
"""Remove all packages from self.repository."""
1656
create_file(self.repository_dir + "/Packages", "")
1658
def set_pkg1_upgradable(self):
1659
"""Make it so that package "name1" is considered to be upgradable.
1661
Return the hash of the package that upgrades "name1".
1663
self._add_package_to_deb_dir(
1664
self.repository_dir, "name1", version="version2")
1665
self.facade.reload_channels()
1666
name1_upgrade = sorted(self.facade.get_packages_by_name("name1"))[1]
1667
return self.facade.get_package_hash(name1_upgrade)
1669
def set_pkg1_installed(self):
1670
"""Make it so that package "name1" is considered installed."""
1671
self._install_deb_file(os.path.join(self.repository_dir, PKGNAME1))
1673
def _make_fake_apt_update(self, out="output", err="error", code=0):
1674
"""Create a fake apt-update executable"""
1675
self.reporter.apt_update_filename = self.makeFile(
1679
"exit %d" % (out, err, code))
1680
os.chmod(self.reporter.apt_update_filename, 0755)
1682
1107
def test_run_apt_update(self):
1684
1109
The L{PackageReporter.run_apt_update} method should run apt-update.