Merge lp:~abentley/launchpad/process-upload-no-cwd into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 11085
Proposed branch: lp:~abentley/launchpad/process-upload-no-cwd
Merge into: lp:launchpad
Diff against target: 194 lines (+89/-7)
4 files modified
lib/lp/archiveuploader/dscfile.py (+1/-1)
lib/lp/archiveuploader/tests/test_dscfile.py (+21/-2)
lib/lp/testing/__init__.py (+20/-3)
lib/lp/testing/factory.py (+47/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/process-upload-no-cwd
Reviewer Review Type Date Requested Status
Paul Hummer (community) code Approve
Review via email: mp+28682@code.launchpad.net

Description of the change

= Summary =
Fix bug #595957: archive uploader tries to move the changelog to the current
working directory

== Proposed fix ==
Stop specifying where to create the temp directory.

== Pre-implementation notes ==
None

== Implementation details ==
In the process, I had to create a way to create DSCFiles. This meant using a
temp directory in the LaunchpadObjectFactory, which does not have addCleanup.
Therefore, I extracted a temp_dir context manager and useContext from
useTempDir.

== Tests ==
bin/test -t test_ReadOnlyCWD

== Demo and Q/A ==
None

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/archiveuploader/dscfile.py
  lib/lp/testing/factory.py
  lib/lp/testing/__init__.py
  lib/lp/archiveuploader/tests/test_dscfile.py

To post a comment you must log in.
Revision history for this message
Paul Hummer (rockstar) wrote :

Looks good. Just so you know, I went through this and followed the code in my head. I'm not letting you off easy, you just write good code.

Also, I now have it on my TODO list to read up on contextlib.

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/archiveuploader/dscfile.py'
2--- lib/lp/archiveuploader/dscfile.py 2010-03-23 14:00:34 +0000
3+++ lib/lp/archiveuploader/dscfile.py 2010-06-28 20:53:25 +0000
4@@ -483,7 +483,7 @@
5 "Verifying uploaded source package by unpacking it.")
6
7 # Get a temporary dir together.
8- tmpdir = tempfile.mkdtemp(dir=self.dirname)
9+ tmpdir = tempfile.mkdtemp()
10
11 # chdir into it
12 cwd = os.getcwd()
13
14=== modified file 'lib/lp/archiveuploader/tests/test_dscfile.py'
15--- lib/lp/archiveuploader/tests/test_dscfile.py 2010-04-12 20:17:59 +0000
16+++ lib/lp/archiveuploader/tests/test_dscfile.py 2010-06-28 20:53:25 +0000
17@@ -8,10 +8,12 @@
18 import os
19 import unittest
20
21-from lp.archiveuploader.dscfile import findAndMoveChangelog, findCopyright
22+from canonical.testing.layers import LaunchpadZopelessLayer
23+from lp.archiveuploader.dscfile import (
24+ findAndMoveChangelog, findCopyright)
25 from lp.archiveuploader.nascentuploadfile import UploadError
26 from lp.archiveuploader.tests import mock_logger_quiet
27-from lp.testing import TestCase
28+from lp.testing import TestCase, TestCaseWithFactory
29
30
31 class TestDscFile(TestCase):
32@@ -114,5 +116,22 @@
33 errors[0].args[0],
34 "debian/changelog file too large, 10MiB max")
35
36+
37+class TestDscFileLibrarian(TestCaseWithFactory):
38+ """Tests for DscFile that may use the Librarian."""
39+
40+ layer = LaunchpadZopelessLayer
41+
42+ def test_ReadOnlyCWD(self):
43+ """Processing a file should work when cwd is read-only."""
44+ tempdir = self.useTempDir()
45+ dsc_file = self.factory.makeDscFile(tempdir)
46+ os.chmod(tempdir, 0555)
47+ try:
48+ list(dsc_file.unpackAndCheckSource())
49+ finally:
50+ os.chmod(tempdir, 0755)
51+
52+
53 def test_suite():
54 return unittest.TestLoader().loadTestsFromName(__name__)
55
56=== modified file 'lib/lp/testing/__init__.py'
57--- lib/lp/testing/__init__.py 2010-06-15 18:24:04 +0000
58+++ lib/lp/testing/__init__.py 2010-06-28 20:53:25 +0000
59@@ -253,11 +253,18 @@
60 """
61 return self.id()
62
63+ def useContext(self, context):
64+ """Use the supplied context in this test.
65+
66+ The context will be cleaned via addCleanup.
67+ """
68+ retval = context.__enter__()
69+ self.addCleanup(context.__exit__, None, None, None)
70+ return retval
71+
72 def makeTemporaryDirectory(self):
73 """Create a temporary directory, and return its path."""
74- tempdir = tempfile.mkdtemp()
75- self.addCleanup(shutil.rmtree, tempdir)
76- return tempdir
77+ return self.useContext(temp_dir())
78
79 def assertProvides(self, obj, interface):
80 """Assert 'obj' correctly provides 'interface'."""
81@@ -433,6 +440,7 @@
82 cwd = os.getcwd()
83 os.chdir(tempdir)
84 self.addCleanup(os.chdir, cwd)
85+ return tempdir
86
87 def _unfoldEmailHeader(self, header):
88 """Unfold a multiline e-mail header."""
89@@ -997,6 +1005,15 @@
90 obj_url.replace('http://api.launchpad.dev/',
91 str(launchpad._root_uri)))
92
93+
94+@contextmanager
95+def temp_dir():
96+ """Provide a temporary directory as a ContextManager."""
97+ tempdir = tempfile.mkdtemp()
98+ yield tempdir
99+ shutil.rmtree(tempdir)
100+
101+
102 def unlink_source_packages(product):
103 """Remove all links between the product and source packages.
104
105
106=== modified file 'lib/lp/testing/factory.py'
107--- lib/lp/testing/factory.py 2010-06-21 07:30:23 +0000
108+++ lib/lp/testing/factory.py 2010-06-28 20:53:25 +0000
109@@ -15,6 +15,7 @@
110 'ObjectFactory',
111 ]
112
113+from contextlib import nested
114 from datetime import datetime, timedelta
115 from email.encoders import encode_base64
116 from email.utils import make_msgid, formatdate
117@@ -25,6 +26,7 @@
118 import os.path
119 from random import randint
120 from StringIO import StringIO
121+from textwrap import dedent
122 from threading import local
123
124 import pytz
125@@ -55,10 +57,13 @@
126 from canonical.launchpad.interfaces.temporaryblobstorage import (
127 ITemporaryStorageManager)
128 from canonical.launchpad.ftests._sqlobject import syncUpdate
129+from canonical.launchpad.scripts.logger import QuietFakeLogger
130 from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy
131 from canonical.launchpad.webapp.interfaces import (
132 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
133
134+from lp.archiveuploader.dscfile import DSCFile
135+from lp.archiveuploader.uploadpolicy import BuildDaemonUploadPolicy
136 from lp.blueprints.interfaces.specification import (
137 ISpecificationSet, SpecificationDefinitionStatus)
138 from lp.blueprints.interfaces.sprint import ISprintSet
139@@ -137,7 +142,7 @@
140 from lp.soyuz.model.processor import ProcessorFamily, ProcessorFamilySet
141 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
142
143-from lp.testing import run_with_login, time_counter, login, logout
144+from lp.testing import run_with_login, time_counter, login, logout, temp_dir
145
146 from lp.translations.interfaces.potemplate import IPOTemplateSet
147 from lp.translations.interfaces.translationgroup import (
148@@ -1846,6 +1851,47 @@
149 store.add(bq)
150 return bq
151
152+ def makeDscFile(self, tempdir_path=None):
153+ """Make a DscFile.
154+
155+ :param tempdir_path: Path to a temporary directory to use. If not
156+ supplied, a temp directory will be created.
157+ """
158+ filename = 'ed_0.2-20.dsc'
159+ contexts = []
160+ if tempdir_path is None:
161+ contexts.append(temp_dir())
162+ # Use nested so temp_dir is an optional context.
163+ with nested(*contexts) as result:
164+ if tempdir_path is None:
165+ tempdir_path = result[0]
166+ fullpath = os.path.join(tempdir_path, filename)
167+ with open(fullpath, 'w') as dsc_file:
168+ dsc_file.write(dedent("""\
169+ Format: 1.0
170+ Source: ed
171+ Version: 0.2-20
172+ Binary: ed
173+ Maintainer: James Troup <james@nocrew.org>
174+ Architecture: any
175+ Standards-Version: 3.5.8.0
176+ Build-Depends: dpatch
177+ Files:
178+ ddd57463774cae9b50e70cd51221281b 185913 ed_0.2.orig.tar.gz
179+ f9e1e5f13725f581919e9bfd62272a05 8506 ed_0.2-20.diff.gz
180+ """))
181+ class Changes:
182+ architectures = ['source']
183+ logger = QuietFakeLogger()
184+ policy = BuildDaemonUploadPolicy()
185+ policy.distroseries = self.makeDistroSeries()
186+ policy.archive = self.makeArchive()
187+ policy.distro = policy.distroseries.distribution
188+ dsc_file = DSCFile(fullpath, 'digest', 0, 'main/editors',
189+ 'priority', 'package', 'version', Changes, policy, logger)
190+ list(dsc_file.verify())
191+ return dsc_file
192+
193 def makeTranslationTemplatesBuildJob(self, branch=None):
194 """Make a new `TranslationTemplatesBuildJob`.
195