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
=== modified file 'lib/lp/archiveuploader/dscfile.py'
--- lib/lp/archiveuploader/dscfile.py 2010-03-23 14:00:34 +0000
+++ lib/lp/archiveuploader/dscfile.py 2010-06-28 20:53:25 +0000
@@ -483,7 +483,7 @@
483 "Verifying uploaded source package by unpacking it.")483 "Verifying uploaded source package by unpacking it.")
484484
485 # Get a temporary dir together.485 # Get a temporary dir together.
486 tmpdir = tempfile.mkdtemp(dir=self.dirname)486 tmpdir = tempfile.mkdtemp()
487487
488 # chdir into it488 # chdir into it
489 cwd = os.getcwd()489 cwd = os.getcwd()
490490
=== modified file 'lib/lp/archiveuploader/tests/test_dscfile.py'
--- lib/lp/archiveuploader/tests/test_dscfile.py 2010-04-12 20:17:59 +0000
+++ lib/lp/archiveuploader/tests/test_dscfile.py 2010-06-28 20:53:25 +0000
@@ -8,10 +8,12 @@
8import os8import os
9import unittest9import unittest
1010
11from lp.archiveuploader.dscfile import findAndMoveChangelog, findCopyright11from canonical.testing.layers import LaunchpadZopelessLayer
12from lp.archiveuploader.dscfile import (
13 findAndMoveChangelog, findCopyright)
12from lp.archiveuploader.nascentuploadfile import UploadError14from lp.archiveuploader.nascentuploadfile import UploadError
13from lp.archiveuploader.tests import mock_logger_quiet15from lp.archiveuploader.tests import mock_logger_quiet
14from lp.testing import TestCase16from lp.testing import TestCase, TestCaseWithFactory
1517
1618
17class TestDscFile(TestCase):19class TestDscFile(TestCase):
@@ -114,5 +116,22 @@
114 errors[0].args[0],116 errors[0].args[0],
115 "debian/changelog file too large, 10MiB max")117 "debian/changelog file too large, 10MiB max")
116118
119
120class TestDscFileLibrarian(TestCaseWithFactory):
121 """Tests for DscFile that may use the Librarian."""
122
123 layer = LaunchpadZopelessLayer
124
125 def test_ReadOnlyCWD(self):
126 """Processing a file should work when cwd is read-only."""
127 tempdir = self.useTempDir()
128 dsc_file = self.factory.makeDscFile(tempdir)
129 os.chmod(tempdir, 0555)
130 try:
131 list(dsc_file.unpackAndCheckSource())
132 finally:
133 os.chmod(tempdir, 0755)
134
135
117def test_suite():136def test_suite():
118 return unittest.TestLoader().loadTestsFromName(__name__)137 return unittest.TestLoader().loadTestsFromName(__name__)
119138
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2010-06-15 18:24:04 +0000
+++ lib/lp/testing/__init__.py 2010-06-28 20:53:25 +0000
@@ -253,11 +253,18 @@
253 """253 """
254 return self.id()254 return self.id()
255255
256 def useContext(self, context):
257 """Use the supplied context in this test.
258
259 The context will be cleaned via addCleanup.
260 """
261 retval = context.__enter__()
262 self.addCleanup(context.__exit__, None, None, None)
263 return retval
264
256 def makeTemporaryDirectory(self):265 def makeTemporaryDirectory(self):
257 """Create a temporary directory, and return its path."""266 """Create a temporary directory, and return its path."""
258 tempdir = tempfile.mkdtemp()267 return self.useContext(temp_dir())
259 self.addCleanup(shutil.rmtree, tempdir)
260 return tempdir
261268
262 def assertProvides(self, obj, interface):269 def assertProvides(self, obj, interface):
263 """Assert 'obj' correctly provides 'interface'."""270 """Assert 'obj' correctly provides 'interface'."""
@@ -433,6 +440,7 @@
433 cwd = os.getcwd()440 cwd = os.getcwd()
434 os.chdir(tempdir)441 os.chdir(tempdir)
435 self.addCleanup(os.chdir, cwd)442 self.addCleanup(os.chdir, cwd)
443 return tempdir
436444
437 def _unfoldEmailHeader(self, header):445 def _unfoldEmailHeader(self, header):
438 """Unfold a multiline e-mail header."""446 """Unfold a multiline e-mail header."""
@@ -997,6 +1005,15 @@
997 obj_url.replace('http://api.launchpad.dev/',1005 obj_url.replace('http://api.launchpad.dev/',
998 str(launchpad._root_uri)))1006 str(launchpad._root_uri)))
9991007
1008
1009@contextmanager
1010def temp_dir():
1011 """Provide a temporary directory as a ContextManager."""
1012 tempdir = tempfile.mkdtemp()
1013 yield tempdir
1014 shutil.rmtree(tempdir)
1015
1016
1000def unlink_source_packages(product):1017def unlink_source_packages(product):
1001 """Remove all links between the product and source packages.1018 """Remove all links between the product and source packages.
10021019
10031020
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-06-21 07:30:23 +0000
+++ lib/lp/testing/factory.py 2010-06-28 20:53:25 +0000
@@ -15,6 +15,7 @@
15 'ObjectFactory',15 'ObjectFactory',
16 ]16 ]
1717
18from contextlib import nested
18from datetime import datetime, timedelta19from datetime import datetime, timedelta
19from email.encoders import encode_base6420from email.encoders import encode_base64
20from email.utils import make_msgid, formatdate21from email.utils import make_msgid, formatdate
@@ -25,6 +26,7 @@
25import os.path26import os.path
26from random import randint27from random import randint
27from StringIO import StringIO28from StringIO import StringIO
29from textwrap import dedent
28from threading import local30from threading import local
2931
30import pytz32import pytz
@@ -55,10 +57,13 @@
55from canonical.launchpad.interfaces.temporaryblobstorage import (57from canonical.launchpad.interfaces.temporaryblobstorage import (
56 ITemporaryStorageManager)58 ITemporaryStorageManager)
57from canonical.launchpad.ftests._sqlobject import syncUpdate59from canonical.launchpad.ftests._sqlobject import syncUpdate
60from canonical.launchpad.scripts.logger import QuietFakeLogger
58from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy61from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy
59from canonical.launchpad.webapp.interfaces import (62from canonical.launchpad.webapp.interfaces import (
60 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)63 IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR)
6164
65from lp.archiveuploader.dscfile import DSCFile
66from lp.archiveuploader.uploadpolicy import BuildDaemonUploadPolicy
62from lp.blueprints.interfaces.specification import (67from lp.blueprints.interfaces.specification import (
63 ISpecificationSet, SpecificationDefinitionStatus)68 ISpecificationSet, SpecificationDefinitionStatus)
64from lp.blueprints.interfaces.sprint import ISprintSet69from lp.blueprints.interfaces.sprint import ISprintSet
@@ -137,7 +142,7 @@
137from lp.soyuz.model.processor import ProcessorFamily, ProcessorFamilySet142from lp.soyuz.model.processor import ProcessorFamily, ProcessorFamilySet
138from lp.soyuz.model.publishing import SourcePackagePublishingHistory143from lp.soyuz.model.publishing import SourcePackagePublishingHistory
139144
140from lp.testing import run_with_login, time_counter, login, logout145from lp.testing import run_with_login, time_counter, login, logout, temp_dir
141146
142from lp.translations.interfaces.potemplate import IPOTemplateSet147from lp.translations.interfaces.potemplate import IPOTemplateSet
143from lp.translations.interfaces.translationgroup import (148from lp.translations.interfaces.translationgroup import (
@@ -1846,6 +1851,47 @@
1846 store.add(bq)1851 store.add(bq)
1847 return bq1852 return bq
18481853
1854 def makeDscFile(self, tempdir_path=None):
1855 """Make a DscFile.
1856
1857 :param tempdir_path: Path to a temporary directory to use. If not
1858 supplied, a temp directory will be created.
1859 """
1860 filename = 'ed_0.2-20.dsc'
1861 contexts = []
1862 if tempdir_path is None:
1863 contexts.append(temp_dir())
1864 # Use nested so temp_dir is an optional context.
1865 with nested(*contexts) as result:
1866 if tempdir_path is None:
1867 tempdir_path = result[0]
1868 fullpath = os.path.join(tempdir_path, filename)
1869 with open(fullpath, 'w') as dsc_file:
1870 dsc_file.write(dedent("""\
1871 Format: 1.0
1872 Source: ed
1873 Version: 0.2-20
1874 Binary: ed
1875 Maintainer: James Troup <james@nocrew.org>
1876 Architecture: any
1877 Standards-Version: 3.5.8.0
1878 Build-Depends: dpatch
1879 Files:
1880 ddd57463774cae9b50e70cd51221281b 185913 ed_0.2.orig.tar.gz
1881 f9e1e5f13725f581919e9bfd62272a05 8506 ed_0.2-20.diff.gz
1882 """))
1883 class Changes:
1884 architectures = ['source']
1885 logger = QuietFakeLogger()
1886 policy = BuildDaemonUploadPolicy()
1887 policy.distroseries = self.makeDistroSeries()
1888 policy.archive = self.makeArchive()
1889 policy.distro = policy.distroseries.distribution
1890 dsc_file = DSCFile(fullpath, 'digest', 0, 'main/editors',
1891 'priority', 'package', 'version', Changes, policy, logger)
1892 list(dsc_file.verify())
1893 return dsc_file
1894
1849 def makeTranslationTemplatesBuildJob(self, branch=None):1895 def makeTranslationTemplatesBuildJob(self, branch=None):
1850 """Make a new `TranslationTemplatesBuildJob`.1896 """Make a new `TranslationTemplatesBuildJob`.
18511897