Merge lp:~jtv/launchpad/bug-819674 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 13618
Proposed branch: lp:~jtv/launchpad/bug-819674
Merge into: lp:launchpad
Diff against target: 109 lines (+75/-6)
2 files modified
lib/lp/archivepublisher/scripts/publish_ftpmaster.py (+14/-6)
lib/lp/archivepublisher/tests/test_publish_ftpmaster.py (+61/-0)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-819674
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+70135@code.launchpad.net

Commit message

[r=wgrant][bug=819674] Make sure publish-ftpmaster handles ctrl-C.

Description of the change

= Summary =

If you hit ctrl-C while running publish-ftpmaster, the sensible thing happens: the only parts of the filesystem that are affected are the archive pool (which may get some extra files but won't reference them yet), and the working dists directory (which won't become the current dists directory until success).

This wasn't fully tested, so here goes.

I tried to do this the proper TDD way, and then found that the only real code change I needed was to make this behaviour testable. Then I discovered that the "except:" catches not just Exceptions but KeyboardInterrupts as well (the latter is no longer derived from the former in recent python versions). But I believe the tests are still useful.

I used FakeMethods in two of their four capacities here:

1. To force a method into raising a specific failure.

2. To keep track of calls to a method.

(The other two, which I did not use here, are to turn a modifying method into a no-op and to force a querying method into returning a specific value).

The actual work done by recoverWorkingDists (including its integration with the newly extracted recoverArchiveWorkingDirectory) is already covered by existing tests. The new tests cover new aspects of how publish and recoverWorkingDists fit together.

== Tests ==

{{{
./bin/test -vvc lp.archivepublisher.tests.test_publish_ftpmaster
}}}

== Demo and Q/A ==

Ongoing: make sure the new python-based publish-ftpmaster script works well enough to replace the old shell script in production.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/archivepublisher/scripts/publish_ftpmaster.py
  lib/lp/archivepublisher/tests/test_publish_ftpmaster.py

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

As discussed on IRC, I'd like to see assertRaisesWithContent to simplify two of those tests.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/scripts/publish_ftpmaster.py'
2--- lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2011-07-27 10:54:53 +0000
3+++ lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2011-08-05 04:03:15 +0000
4@@ -321,6 +321,19 @@
5 failure=LaunchpadScriptFailure(
6 "Failed to rsync new dists for %s." % purpose.title))
7
8+ def recoverArchiveWorkingDir(self, archive_config):
9+ """Recover working dists dir for `archive_config`.
10+
11+ If there is a dists directory for `archive_config` in the working
12+ location, kick it back to the backup location.
13+ """
14+ working_location = get_working_dists(archive_config)
15+ if file_exists(working_location):
16+ self.logger.info(
17+ "Recovering working directory %s from failed run.",
18+ working_location)
19+ os.rename(working_location, get_backup_dists(archive_config))
20+
21 def recoverWorkingDists(self):
22 """Look for and recover any dists left in transient working state.
23
24@@ -330,12 +343,7 @@
25 permanent location.
26 """
27 for archive_config in self.configs.itervalues():
28- working_location = get_working_dists(archive_config)
29- if file_exists(working_location):
30- self.logger.info(
31- "Recovering working directory %s from failed run.",
32- working_location)
33- os.rename(working_location, get_backup_dists(archive_config))
34+ self.recoverArchiveWorkingDir(archive_config)
35
36 def setUpDirs(self):
37 """Create archive roots and such if they did not yet exist."""
38
39=== modified file 'lib/lp/archivepublisher/tests/test_publish_ftpmaster.py'
40--- lib/lp/archivepublisher/tests/test_publish_ftpmaster.py 2011-07-22 15:35:08 +0000
41+++ lib/lp/archivepublisher/tests/test_publish_ftpmaster.py 2011-08-05 04:03:15 +0000
42@@ -736,6 +736,67 @@
43 "Did not find expected marker for %s."
44 % archive.purpose.title)
45
46+ def test_publish_reraises_exception(self):
47+ # If an Exception comes up while publishing, it bubbles up out
48+ # of the publish method even though the method must intercept
49+ # it for its own purposes.
50+ class MoonPhaseError(Exception):
51+ """Simulated failure."""
52+
53+ message = self.factory.getUniqueString()
54+ script = self.makeScript()
55+ script.publishAllUploads = FakeMethod(failure=MoonPhaseError(message))
56+ script.setUp()
57+ self.assertRaisesWithContent(MoonPhaseError, message, script.publish)
58+
59+ def test_publish_obeys_keyboard_interrupt(self):
60+ # Similar to an Exception, a keyboard interrupt does not get
61+ # swallowed.
62+ message = self.factory.getUniqueString()
63+ script = self.makeScript()
64+ script.publishAllUploads = FakeMethod(
65+ failure=KeyboardInterrupt(message))
66+ script.setUp()
67+ self.assertRaisesWithContent(
68+ KeyboardInterrupt, message, script.publish)
69+
70+ def test_publish_recovers_working_dists_on_exception(self):
71+ # If an Exception comes up while publishing, the publish method
72+ # recovers its working directory.
73+ class MoonPhaseError(Exception):
74+ """Simulated failure."""
75+
76+ failure = MoonPhaseError(self.factory.getUniqueString())
77+
78+ script = self.makeScript()
79+ script.publishAllUploads = FakeMethod(failure=failure)
80+ script.recoverArchiveWorkingDir = FakeMethod()
81+ script.setUp()
82+
83+ try:
84+ script.publish()
85+ except MoonPhaseError:
86+ pass
87+
88+ self.assertEqual(1, script.recoverArchiveWorkingDir.call_count)
89+
90+ def test_publish_recovers_working_dists_on_ctrl_C(self):
91+ # If the user hits ctrl-C while publishing, the publish method
92+ # recovers its working directory.
93+ failure = KeyboardInterrupt("Ctrl-C!")
94+
95+ script = self.makeScript()
96+ script.publishAllUploads = FakeMethod(failure=failure)
97+ script.recoverArchiveWorkingDir = FakeMethod()
98+ script.setUp()
99+
100+ try:
101+ script.publish()
102+ except KeyboardInterrupt:
103+ pass
104+
105+ self.assertEqual(1, script.recoverArchiveWorkingDir.call_count)
106+
107
108 class TestCreateDistroSeriesIndexes(TestCaseWithFactory, HelpersMixin):
109 """Test initial creation of archive indexes for a `DistroSeries`."""