launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #00195
[Merge] lp:~adeuring/launchpad/security-guarded-test-object-factory-1 into lp:launchpad/devel
Abel Deuring has proposed merging lp:~adeuring/launchpad/security-guarded-test-object-factory-1 into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
While working together with Brian on another branch during the LP epic last week, I was bitten by the fact that we have still a number of methods in LaunchpadObjectFactory which return unproxied objects. While we fixed this problem for the one or two methods we needed in that branch, a number of other methods still remained that return unproxied objects.
Some time ago, Gavin noticed the same problem, IIRC for makePerson(); he also filed a bug that other methods still returned unproxied objects, and Salgado finally fixed that bug.
However, new methods obviously returning unproxied objects creeped into LaunchpadObjectFactory in the meantime, thus reintroducing the old problem. So I thought that it might be reasonable to ensure that all objects returned by LPObjectFactory methods are either of a simple Python type or are security proxied.
The implementation is quite simple: I renamed LaunchpadObjectFactory to _LaunchpadObjectFactory and wrote a new class LaunchpadObjectFactory with basically one method, __getattr__(). This method returns a the "real" factory method, wrapped in a function guarded_method() which ensures that the object returned by the _LaunchpadObjectFactory.makeWhatever() matches the conditions described above.
We have at present ca 20 methods which return, at least sometimes, unproxied objects. Fixing all these methods in one branch would result in a huge diff, so I fixed only two methods in this branch, makeDistroRelease() and makeProductSeries().
My plan is to let guarded_method() raise an exception if it detects an unproxied object, once all existing factory methods return properly proxied objects. For now, it just print a warning to stderr.
The largest part of the diff is test fixes. I took the simple route: Just to remove the security proxy so that the affected tests pass again. I did not bother to check if this is reasonable in a given test -- doing so would simply have needed far too much time. Instead, I added a function remove_security_proxy_and_shout_at_engineer() which just calls removeSecurityProxy() and print a warning to stderr. This will make the tests quite noisy (as do the warnings issued guarded_method() about unproxied objects)...
Pre-/mid-imp discussions with Jeroen and Gary.
affected tests: Many, see the diff.
I also removed some lint in the files I touched, but some messages remain:
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/code/browser/tests/test_branchlisting.py
lib/lp/code/browser/tests/test_sourcepackagerecipe.py
lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py
lib/lp/code/model/tests/test_branch.py
lib/lp/code/model/tests/test_linkedbranch.py
lib/lp/code/model/tests/test_sourcepackagerecipe.py
lib/lp/code/model/tests/test_sourcepackagerecipebuild.py
lib/lp/registry/browser/tests/distroseries-views.txt
lib/lp/registry/browser/tests/milestone-views.txt
lib/lp/registry/browser/tests/productseries-views.txt
lib/lp/registry/stories/webservice/xx-project-registry.txt
lib/lp/registry/tests/test_distroseries.py
lib/lp/registry/tests/test_sourcepackage.py
lib/lp/soyuz/browser/tests/test_distrosourcepackagerelease.py
lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py
lib/lp/soyuz/tests/test_publishing.py
lib/lp/testing/factory.py
lib/lp/translations/browser/tests/test_breadcrumbs.py
lib/lp/translations/doc/translations-export-to-branch.txt
lib/lp/translations/stories/buildfarm/xx-build-summary.txt
./lib/lp/registry/browser/tests/productseries-views.txt
83: want exceeds 78 characters.
./lib/lp/registry/stories/webservice/xx-project-registry.txt
570: source exceeds 78 characters.
1254: 'IRepresentationCache' imported but unused
./lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py
119: E302 expected 2 blank lines, found 1
./lib/lp/translations/browser/tests/test_breadcrumbs.py
27: E501 line too long (81 characters)
37: E501 line too long (86 characters)
45: E501 line too long (81 characters)
56: E501 line too long (86 characters)
64: E501 line too long (81 characters)
72: E501 line too long (82 characters)
81: E501 line too long (82 characters)
88: E501 line too long (81 characters)
138: E301 expected 1 blank line, found 0
152: E501 line too long (85 characters)
27: Line exceeds 78 characters.
37: Line exceeds 78 characters.
45: Line exceeds 78 characters.
56: Line exceeds 78 characters.
64: Line exceeds 78 characters.
72: Line exceeds 78 characters.
81: Line exceeds 78 characters.
88: Line exceeds 78 characters.
116: Line exceeds 78 characters.
133: Line exceeds 78 characters.
152: Line exceeds 78 characters.
./lib/lp/translations/doc/translations-export-to-branch.txt
0: narrative uses a moin header.
15: want exceeds 78 characters.
273: narrative uses a moin header.
301: narrative uses a moin header.
242: Could not compile:
finally:
./lib/lp/translations/stories/buildfarm/xx-build-summary.txt
0: narrative uses a moin header.
5: narrative uses a moin header.
64: narrative uses a moin header.
--
https://code.launchpad.net/~adeuring/launchpad/security-guarded-test-object-factory-1/+merge/30497
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~adeuring/launchpad/security-guarded-test-object-factory-1 into lp:launchpad/devel.
=== modified file 'lib/lp/code/browser/tests/test_branchlisting.py'
--- lib/lp/code/browser/tests/test_branchlisting.py 2010-05-28 09:54:45 +0000
+++ lib/lp/code/browser/tests/test_branchlisting.py 2010-07-21 09:04:53 +0000
@@ -32,12 +32,14 @@
from lp.testing import (
BrowserTestCase, TestCase, TestCaseWithFactory, login_person,
person_logged_in, time_counter)
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
from lp.testing.views import create_initialized_view
from canonical.launchpad.testing.pages import extract_text, find_tag_by_id
from canonical.launchpad.webapp import canonical_url
from canonical.launchpad.webapp.servers import LaunchpadTestRequest
from canonical.testing.layers import DatabaseFunctionalLayer
+
class TestListingToSortOrder(TestCase):
"""Tests for the BranchSet._listingSortToOrderBy static method.
@@ -60,6 +62,7 @@
def assertSortsEqual(self, sort_one, sort_two):
"""Assert that one list of sort specs is equal to another."""
+
def sort_data(sort):
return sort.suffix, sort.expr
self.assertEqual(map(sort_data, sort_one), map(sort_data, sort_two))
@@ -352,7 +355,7 @@
# series on the main site, not the code site.
branch = self.factory.makeProductBranch()
series = self.factory.makeProductSeries(product=branch.product)
- series.branch = branch
+ remove_security_proxy_and_shout_at_engineer(series).branch = branch
browser = self.getUserBrowser(
canonical_url(branch.product, rootsite='code'))
link = browser.getLink(re.compile('^' + series.name + '$'))
@@ -402,4 +405,3 @@
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
-
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-19 10:51:39 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-07-21 09:04:53 +0000
@@ -28,6 +28,7 @@
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.soyuz.model.processor import ProcessorFamily
from lp.testing import ANONYMOUS, BrowserTestCase, login, logout
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
class TestCaseForRecipe(BrowserTestCase):
@@ -44,7 +45,9 @@
self.squirrel = self.factory.makeDistroSeries(
displayname='Secret Squirrel', name='secret', version='100.04',
distribution=self.ppa.distribution)
- self.squirrel.nominatedarchindep = self.squirrel.newArch(
+ naked_squirrel = remove_security_proxy_and_shout_at_engineer(
+ self.squirrel)
+ naked_squirrel.nominatedarchindep = self.squirrel.newArch(
'i386', ProcessorFamily.get(1), False, self.chef,
supports_virtualized=True)
@@ -492,7 +495,7 @@
def makeBuildJob(self, recipe):
"""Return a build associated with a buildjob."""
build = self.factory.makeSourcePackageRecipeBuild(
- recipe=recipe, distroseries=self.squirrel, archive=self.ppa )
+ recipe=recipe, distroseries=self.squirrel, archive=self.ppa)
self.factory.makeSourcePackageRecipeBuildJob(recipe_build=build)
return build
@@ -525,6 +528,7 @@
self.assertEqual(
set([build1, build2, build3, build4, build5, build6]),
set(view.builds))
+
def set_day(build, day):
removeSecurityProxy(build).datebuilt = datetime(
2010, 03, day, tzinfo=utc)
@@ -567,7 +571,8 @@
woody = self.factory.makeDistroSeries(
name='woody', displayname='Woody',
distribution=self.ppa.distribution)
- woody.nominatedarchindep = woody.newArch(
+ naked_woody = remove_security_proxy_and_shout_at_engineer(woody)
+ naked_woody.nominatedarchindep = woody.newArch(
'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
supports_virtualized=True)
@@ -599,7 +604,8 @@
woody = self.factory.makeDistroSeries(
name='woody', displayname='Woody',
distribution=self.ppa.distribution)
- woody.nominatedarchindep = woody.newArch(
+ naked_woody = remove_security_proxy_and_shout_at_engineer(woody)
+ naked_woody.nominatedarchindep = woody.newArch(
'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
supports_virtualized=True)
@@ -620,7 +626,8 @@
woody = self.factory.makeDistroSeries(
name='woody', displayname='Woody',
distribution=self.ppa.distribution)
- woody.nominatedarchindep = woody.newArch(
+ naked_woody = remove_security_proxy_and_shout_at_engineer(woody)
+ naked_woody.nominatedarchindep = woody.newArch(
'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
supports_virtualized=True)
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py 2010-07-19 09:22:06 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py 2010-07-21 09:04:53 +0000
@@ -18,6 +18,7 @@
from lp.buildmaster.interfaces.buildbase import BuildStatus
from lp.soyuz.model.processor import ProcessorFamily
from lp.testing import ANONYMOUS, BrowserTestCase, login, logout
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
class TestSourcePackageRecipeBuild(BrowserTestCase):
@@ -36,7 +37,9 @@
self.squirrel = self.factory.makeDistroSeries(
displayname='Secret Squirrel', name='secret', version='100.04',
distribution=self.ppa.distribution)
- self.squirrel.nominatedarchindep = self.squirrel.newArch(
+ naked_squirrel = remove_security_proxy_and_shout_at_engineer(
+ self.squirrel)
+ naked_squirrel.nominatedarchindep = self.squirrel.newArch(
'i386', ProcessorFamily.get(1), False, self.chef,
supports_virtualized=True)
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2010-07-17 23:15:30 +0000
+++ lib/lp/code/model/tests/test_branch.py 2010-07-21 09:04:53 +0000
@@ -537,7 +537,7 @@
jobs = list(getUtility(IBranchUpgradeJobSource).iterReady())
self.assertEqual(
jobs,
- [job,])
+ [job, ])
def test_requestUpgrade_no_upgrade_needed(self):
# If a branch doesn't need to be upgraded, requestUpgrade raises an
@@ -635,6 +635,7 @@
branch = self.factory.makeProductBranch(
product=fooix, owner=eric, name='trunk')
linked_branch = ICanHasLinkedBranch(future)
+ login_person(fooix.owner)
linked_branch.setBranch(branch)
self.assertEqual(
[linked_branch],
@@ -827,6 +828,7 @@
product = branch.product
series = self.factory.makeProductSeries(product=product)
linked_branch = ICanHasLinkedBranch(series)
+ login_person(series.owner)
linked_branch.setBranch(branch)
self.assertBzrIdentity(branch, linked_branch.bzr_path)
@@ -852,6 +854,7 @@
removeSecurityProxy(branch.product))
series_link = ICanHasLinkedBranch(series)
product_link.setBranch(branch)
+ login_person(series.owner)
series_link.setBranch(branch)
self.assertBzrIdentity(branch, product_link.bzr_path)
=== modified file 'lib/lp/code/model/tests/test_linkedbranch.py'
--- lib/lp/code/model/tests/test_linkedbranch.py 2010-04-16 15:06:55 +0000
+++ lib/lp/code/model/tests/test_linkedbranch.py 2010-07-21 09:04:53 +0000
@@ -18,6 +18,7 @@
from lp.registry.interfaces.distroseries import NoSuchDistroSeries
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.testing import run_with_login, TestCaseWithFactory
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
class TestProductSeriesLinkedBranch(TestCaseWithFactory):
@@ -27,7 +28,9 @@
def test_branch(self):
# The linked branch of a product series is its branch attribute.
product_series = self.factory.makeProductSeries()
- product_series.branch = self.factory.makeProductBranch(
+ naked_product_series = remove_security_proxy_and_shout_at_engineer(
+ product_series)
+ naked_product_series.branch = self.factory.makeProductBranch(
product=product_series.product)
self.assertEqual(
product_series.branch, ICanHasLinkedBranch(product_series).branch)
@@ -35,9 +38,11 @@
def test_setBranch(self):
# setBranch sets the linked branch of the product series.
product_series = self.factory.makeProductSeries()
+ naked_product_series = remove_security_proxy_and_shout_at_engineer(
+ product_series)
branch = self.factory.makeProductBranch(
product=product_series.product)
- ICanHasLinkedBranch(product_series).setBranch(branch)
+ ICanHasLinkedBranch(naked_product_series).setBranch(branch)
self.assertEqual(branch, product_series.branch)
def test_bzr_path(self):
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-06-18 19:36:55 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-07-21 09:04:53 +0000
@@ -49,6 +49,7 @@
from lp.testing import (
ANONYMOUS, launchpadlib_for, login, login_person, person_logged_in,
TestCaseWithFactory, ws_object)
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
class TestSourcePackageRecipe(TestCaseWithFactory):
@@ -167,7 +168,6 @@
branch2 = self.factory.makeAnyBranch()
builder_recipe2 = self.factory.makeRecipe(branch2)
login_person(sp_recipe.owner.teamowner)
- #import pdb; pdb.set_trace()
sp_recipe.builder_recipe = builder_recipe2
self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
@@ -210,7 +210,7 @@
def test_requestBuild(self):
recipe = self.factory.makeSourcePackageRecipe()
- (distroseries,) = list(recipe.distroseries)
+ (distroseries, ) = list(recipe.distroseries)
ppa = self.factory.makeArchive()
build = recipe.requestBuild(ppa, ppa.owner, distroseries,
PackagePublishingPocket.RELEASE)
@@ -234,7 +234,7 @@
def test_requestBuildRejectsNotPPA(self):
recipe = self.factory.makeSourcePackageRecipe()
not_ppa = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
- (distroseries,) = list(recipe.distroseries)
+ (distroseries, ) = list(recipe.distroseries)
self.assertRaises(NonPPABuildRequest, recipe.requestBuild, not_ppa,
not_ppa.owner, distroseries, PackagePublishingPocket.RELEASE)
@@ -242,14 +242,14 @@
recipe = self.factory.makeSourcePackageRecipe()
ppa = self.factory.makeArchive()
requester = self.factory.makePerson()
- (distroseries,) = list(recipe.distroseries)
+ (distroseries, ) = list(recipe.distroseries)
self.assertRaises(CannotUploadToArchive, recipe.requestBuild, ppa,
requester, distroseries, PackagePublishingPocket.RELEASE)
def test_requestBuildRejectsInvalidPocket(self):
recipe = self.factory.makeSourcePackageRecipe()
ppa = self.factory.makeArchive()
- (distroseries,) = list(recipe.distroseries)
+ (distroseries, ) = list(recipe.distroseries)
self.assertRaises(InvalidPocketForPPA, recipe.requestBuild, ppa,
ppa.owner, distroseries, PackagePublishingPocket.BACKPORTS)
@@ -257,7 +257,7 @@
recipe = self.factory.makeSourcePackageRecipe()
ppa = self.factory.makeArchive()
removeSecurityProxy(ppa).disable()
- (distroseries,) = list(recipe.distroseries)
+ (distroseries, ) = list(recipe.distroseries)
self.assertRaises(ArchiveDisabled, recipe.requestBuild, ppa,
ppa.owner, distroseries, PackagePublishingPocket.RELEASE)
@@ -283,7 +283,7 @@
def test_requestBuildHonoursConfig(self):
recipe = self.factory.makeSourcePackageRecipe()
- (distroseries,) = list(recipe.distroseries)
+ (distroseries, ) = list(recipe.distroseries)
ppa = self.factory.makeArchive()
self.pushConfig('build_from_branch', enabled=False)
self.assertRaises(
@@ -297,6 +297,7 @@
name=u'myrecipe', owner=requester)
series = list(recipe.distroseries)[0]
archive = self.factory.makeArchive(owner=requester)
+
def request_build():
build = recipe.requestBuild(archive, requester, series,
PackagePublishingPocket.RELEASE)
@@ -323,7 +324,9 @@
series, PackagePublishingPocket.RELEASE)
# Varying distroseries allows build.
new_distroseries = self.factory.makeDistroSeries()
- new_distroseries.nominatedarchindep = new_distroseries.newArch(
+ naked_new_distroseries = remove_security_proxy_and_shout_at_engineer(
+ new_distroseries)
+ naked_new_distroseries.nominatedarchindep = new_distroseries.newArch(
'i386', ProcessorFamily.get(1), False, recipe.owner,
supports_virtualized=True)
recipe.requestBuild(archive, recipe.owner,
@@ -344,7 +347,7 @@
"""Test that the distroseries behaves as a set."""
recipe = self.factory.makeSourcePackageRecipe()
distroseries = self.factory.makeDistroSeries()
- (old_distroseries,) = recipe.distroseries
+ (old_distroseries, ) = recipe.distroseries
recipe.distroseries.add(distroseries)
self.assertEqual(
set([distroseries, old_distroseries]), set(recipe.distroseries))
@@ -430,6 +433,7 @@
build.buildduration = timedelta(minutes=10)
self.assertEqual(
timedelta(minutes=10), recipe.getMedianBuildDuration())
+
def addBuild(minutes):
build = removeSecurityProxy(
self.factory.makeSourcePackageRecipeBuild(recipe=recipe))
@@ -695,7 +699,9 @@
distroseries_i386 = distroseries.newArch(
'i386', ProcessorFamily.get(1), False, person,
supports_virtualized=True)
- distroseries.nominatedarchindep = distroseries_i386
+ naked_distroseries = remove_security_proxy_and_shout_at_engineer(
+ distroseries)
+ naked_distroseries.nominatedarchindep = distroseries_i386
recipe, user, launchpad = self.makeRecipe(person)
distroseries = ws_object(launchpad, distroseries)
@@ -712,7 +718,9 @@
distroseries_i386 = distroseries.newArch(
'i386', ProcessorFamily.get(1), False, person,
supports_virtualized=True)
- distroseries.nominatedarchindep = distroseries_i386
+ naked_distroseries = remove_security_proxy_and_shout_at_engineer(
+ distroseries)
+ naked_distroseries.nominatedarchindep = distroseries_i386
recipe, user, launchpad = self.makeRecipe(person)
distroseries = ws_object(launchpad, distroseries)
@@ -733,7 +741,9 @@
distroseries_i386 = distroseries.newArch(
'i386', ProcessorFamily.get(1), False, person,
supports_virtualized=True)
- distroseries.nominatedarchindep = distroseries_i386
+ naked_distroseries = remove_security_proxy_and_shout_at_engineer(
+ distroseries)
+ naked_distroseries.nominatedarchindep = distroseries_i386
recipe, user, launchpad = self.makeRecipe(person)
distroseries = ws_object(launchpad, distroseries)
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-14 08:42:01 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-21 09:04:53 +0000
@@ -37,6 +37,7 @@
from lp.soyuz.model.processor import ProcessorFamily
from lp.soyuz.tests.soyuzbuilddhelpers import WaitingSlave
from lp.testing import ANONYMOUS, login, person_logged_in, TestCaseWithFactory
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
from lp.testing.fakemethod import FakeMethod
from lp.testing.mail_helpers import pop_notifications
@@ -53,7 +54,9 @@
distroseries_i386 = distroseries.newArch(
'i386', ProcessorFamily.get(1), False, person,
supports_virtualized=True)
- distroseries.nominatedarchindep = distroseries_i386
+ naked_distroseries = remove_security_proxy_and_shout_at_engineer(
+ distroseries)
+ naked_distroseries.nominatedarchindep = distroseries_i386
return getUtility(ISourcePackageRecipeBuildSource).new(
distroseries=distroseries,
@@ -250,9 +253,12 @@
recipe.daily_build_archive, recipe.owner, first_distroseries,
PackagePublishingPocket.RELEASE)
second_distroseries = self.factory.makeDistroSeries()
- second_distroseries.nominatedarchindep = second_distroseries.newArch(
- 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
- supports_virtualized=True)
+ naked_second_distroseries = (
+ remove_security_proxy_and_shout_at_engineer(second_distroseries))
+ naked_second_distroseries.nominatedarchindep = (
+ second_distroseries.newArch(
+ 'i386', ProcessorFamily.get(1), False,
+ self.factory.makePerson(), supports_virtualized=True))
recipe.distroseries.add(second_distroseries)
builds = SourcePackageRecipeBuild.makeDailyBuilds()
self.assertEqual(
@@ -274,6 +280,7 @@
recipe=recipe, distroseries=series)
self.factory.makeSourcePackageRecipeBuild(
requester=requester, distroseries=series)
+
def get_recent():
Store.of(build).flush()
return SourcePackageRecipeBuild.getRecentBuilds(
@@ -321,7 +328,7 @@
removeSecurityProxy(build).buildstate = BuildStatus.FULLYBUILT
IStore(build).flush()
build.notify()
- (message,) = pop_notifications()
+ (message, ) = pop_notifications()
requester = build.requester
requester_address = format_address(
requester.displayname, requester.preferredemail.email)
@@ -332,11 +339,11 @@
body, footer = message.get_payload(decode=True).split('\n-- \n')
self.assertEqual(
'Build person/recipe into ppa for distroseries: Successfully'
- ' built.\n', body
- )
+ ' built.\n', body)
def test_handleStatusNotifies(self):
- """"handleStatus causes notification, even if OK."""
+ """handleStatus causes notification, even if OK."""
+
def prepare_build():
queue_record = self.factory.makeSourcePackageRecipeBuildJob()
build = queue_record.specific_job.build
@@ -345,6 +352,7 @@
slave = WaitingSlave('BuildStatus.OK')
queue_record.builder.setSlaveForTesting(slave)
return build
+
def assertNotifyOnce(status, build):
build.handleStatus(status, None, {'filemap': {}})
self.assertEqual(1, len(pop_notifications()))
=== modified file 'lib/lp/registry/browser/tests/distroseries-views.txt'
--- lib/lp/registry/browser/tests/distroseries-views.txt 2010-05-24 22:04:19 +0000
+++ lib/lp/registry/browser/tests/distroseries-views.txt 2010-07-21 09:04:53 +0000
@@ -196,7 +196,7 @@
>>> [field.__name__ for field in view.form_fields]
['displayname', 'title', 'summary', 'description', 'status']
- >>> print view.widgets.get('status')._getFormValue()
+ >>> print view.widgets.get('status')._getFormValue().title
Active Development
=== modified file 'lib/lp/registry/browser/tests/milestone-views.txt'
--- lib/lp/registry/browser/tests/milestone-views.txt 2010-06-30 19:37:09 +0000
+++ lib/lp/registry/browser/tests/milestone-views.txt 2010-07-21 09:04:53 +0000
@@ -579,9 +579,13 @@
The driver of a series that belongs to an `IDerivativeDistribution` is a
release manager and can create milestones.
+ >>> from lp.testing.factory import (
+ ... remove_security_proxy_and_shout_at_engineer)
>>> distroseries = factory.makeDistroRelease(name='pumpkin')
>>> driver = factory.makePerson(name='a-driver')
- >>> distroseries.driver = driver
+ >>> naked_distroseries = remove_security_proxy_and_shout_at_engineer(
+ ... distroseries)
+ >>> naked_distroseries.driver = driver
>>> login_person(driver)
>>> form = {
=== modified file 'lib/lp/registry/browser/tests/productseries-views.txt'
--- lib/lp/registry/browser/tests/productseries-views.txt 2010-06-13 02:01:25 +0000
+++ lib/lp/registry/browser/tests/productseries-views.txt 2010-07-21 09:04:53 +0000
@@ -255,6 +255,8 @@
>>> from datetime import datetime
>>> from pytz import UTC
+ >>> from lp.testing.factory import (
+ ... remove_security_proxy_and_shout_at_engineer)
>>> product = factory.makeProduct(name="field", displayname='Field')
>>> productseries = factory.makeProductSeries(
@@ -263,7 +265,9 @@
# Hack the creation date for testing purposes.
>>> test_date = datetime(2009, 05, 01, 19, 34, 24, tzinfo=UTC)
- >>> productseries.datecreated = test_date
+ >>> naked_productseries = remove_security_proxy_and_shout_at_engineer(
+ ... productseries)
+ >>> naked_productseries.datecreated = test_date
Users without edit permission cannot access the view.
@@ -470,7 +474,7 @@
The series status is set to obsolete and the releasefileglob was set to None.
- >>> print productseries.status
+ >>> print productseries.status.title
Obsolete
>>> print productseries.releasefileglob
None
=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-06-14 18:32:58 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-07-21 09:04:53 +0000
@@ -847,15 +847,17 @@
The entry for a project series is available at its canonical URL on the
virtual host.
+ >>> from zope.security.proxy import removeSecurityProxy
>>> login('test@xxxxxxxxxxxxx')
>>> babadoo_owner = factory.makePerson(name='babadoo-owner')
>>> babadoo = factory.makeProduct(name='babadoo', owner=babadoo_owner)
>>> foobadoo = factory.makeProductSeries(
... product=babadoo, name='foobadoo', owner=babadoo_owner)
- >>> foobadoo.summary = u'Foobadoo support for Babadoo'
+ >>> removeSecurityProxy(foobadoo).summary = (
+ ... u'Foobadoo support for Babadoo')
>>> fooey = factory.makeAnyBranch(
... product=babadoo, name='fooey', owner=babadoo_owner)
- >>> foobadoo.branch = fooey
+ >>> removeSecurityProxy(foobadoo).branch = fooey
>>> logout()
>>> babadoo_foobadoo = webservice.get('/babadoo/foobadoo').jsonBody()
=== modified file 'lib/lp/registry/tests/test_distroseries.py'
--- lib/lp/registry/tests/test_distroseries.py 2010-06-23 23:23:28 +0000
+++ lib/lp/registry/tests/test_distroseries.py 2010-07-21 09:04:53 +0000
@@ -24,6 +24,7 @@
active_publishing_status, PackagePublishingStatus)
from lp.soyuz.model.processor import ProcessorFamilySet
from lp.testing import TestCase, TestCaseWithFactory
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
from lp.translations.interfaces.translations import (
TranslationsBranchImportMode)
@@ -285,7 +286,9 @@
self.linkPackage('hot')
self.makeSeriesPackage('cold')
product_series = self.linkPackage('cold')
- product_series.product.bugtraker = self.factory.makeBugTracker()
+ naked_product_series = remove_security_proxy_and_shout_at_engineer(
+ product_series)
+ naked_product_series.product.bugtraker = self.factory.makeBugTracker()
packagings = self.series.getPrioritizedlPackagings()
names = [packaging.sourcepackagename.name for packaging in packagings]
expected = [u'hot', u'cold', u'linked']
@@ -296,7 +299,9 @@
self.linkPackage('translatable')
self.makeSeriesPackage('withbranch')
product_series = self.linkPackage('withbranch')
- product_series.branch = self.factory.makeBranch()
+ naked_product_series = remove_security_proxy_and_shout_at_engineer(
+ product_series)
+ naked_product_series.branch = self.factory.makeBranch()
packagings = self.series.getPrioritizedlPackagings()
names = [packaging.sourcepackagename.name for packaging in packagings]
expected = [u'translatable', u'linked', u'withbranch']
@@ -308,8 +313,10 @@
self.linkPackage('translatable')
self.makeSeriesPackage('importabletranslatable')
product_series = self.linkPackage('importabletranslatable')
- product_series.branch = self.factory.makeBranch()
- product_series.translations_autoimport_mode = (
+ naked_product_series = remove_security_proxy_and_shout_at_engineer(
+ product_series)
+ naked_product_series.branch = self.factory.makeBranch()
+ naked_product_series.translations_autoimport_mode = (
TranslationsBranchImportMode.IMPORT_TEMPLATES)
packagings = self.series.getPrioritizedlPackagings()
names = [packaging.sourcepackagename.name for packaging in packagings]
@@ -343,7 +350,9 @@
new_distroseries = (
self.factory.makeDistroRelease(name=u"sampleseries"))
- new_distroseries.hide_all_translations = False
+ naked_new_distroseries = remove_security_proxy_and_shout_at_engineer(
+ new_distroseries)
+ naked_new_distroseries.hide_all_translations = False
transaction.commit()
translatables = self._get_translatables()
self.failUnlessEqual(
@@ -365,7 +374,7 @@
translatables,
self._ref_translatables(u"sampleseries")))
- new_distroseries.hide_all_translations = True
+ naked_new_distroseries.hide_all_translations = True
transaction.commit()
translatables = self._get_translatables()
self.failUnlessEqual(
=== modified file 'lib/lp/registry/tests/test_sourcepackage.py'
--- lib/lp/registry/tests/test_sourcepackage.py 2010-02-24 03:06:54 +0000
+++ lib/lp/registry/tests/test_sourcepackage.py 2010-07-21 09:04:53 +0000
@@ -22,6 +22,7 @@
from lp.code.interfaces.seriessourcepackagebranch import (
IMakeOfficialBranchLinks)
from lp.testing import TestCaseWithFactory
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
from lp.testing.views import create_initialized_view
from canonical.testing.layers import DatabaseFunctionalLayer
@@ -252,11 +253,17 @@
self.obsolete_productseries = self.factory.makeProductSeries(
name='obsolete', product=self.product)
- self.obsolete_productseries.status = SeriesStatus.OBSOLETE
+ naked_obsolete_productseries = (
+ remove_security_proxy_and_shout_at_engineer(
+ self.obsolete_productseries))
+ naked_obsolete_productseries.status = SeriesStatus.OBSOLETE
self.dev_productseries = self.factory.makeProductSeries(
name='current', product=self.product)
- self.dev_productseries.status = SeriesStatus.DEVELOPMENT
+ naked_dev_productseries = (
+ remove_security_proxy_and_shout_at_engineer(
+ self.dev_productseries))
+ naked_dev_productseries.status = SeriesStatus.DEVELOPMENT
self.distribution = self.factory.makeDistribution(
name='youbuntu', displayname='Youbuntu', owner=self.owner)
=== modified file 'lib/lp/soyuz/browser/tests/test_distrosourcepackagerelease.py'
--- lib/lp/soyuz/browser/tests/test_distrosourcepackagerelease.py 2010-07-06 11:52:17 +0000
+++ lib/lp/soyuz/browser/tests/test_distrosourcepackagerelease.py 2010-07-21 09:04:53 +0000
@@ -16,6 +16,7 @@
DistributionSourcePackageRelease)
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
from lp.testing import TestCaseWithFactory
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
from lp.testing.views import create_initialized_view
@@ -29,7 +30,11 @@
# The package must be published for the page to render.
stp = SoyuzTestPublisher()
distroseries = stp.setUpDefaultDistroSeries()
- distro = distroseries.distribution
+ naked_distroseries = remove_security_proxy_and_shout_at_engineer(
+ distroseries)
+ # XXX this is scary. But if we use distroseries.distribution
+ # instead, test_spr_files_deleted() and test_spr_files_one() fail.
+ distro = naked_distroseries.distribution
source_package_release = stp.getPubSource().sourcepackagerelease
self.dspr = DistributionSourcePackageRelease(
distro, source_package_release)
=== modified file 'lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py'
--- lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py 2010-07-02 17:51:33 +0000
+++ lib/lp/soyuz/browser/tests/test_sourcepackagerelease.py 2010-07-21 09:04:53 +0000
@@ -18,6 +18,7 @@
from canonical.testing import (
DatabaseFunctionalLayer, LaunchpadFunctionalLayer)
from lp.testing import TestCaseWithFactory
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
from lp.testing.views import create_initialized_view
@@ -66,20 +67,23 @@
def test_highlighted_copyright_is_None(self):
expected = ''
- self.source_package_release.copyright = None
+ remove_security_proxy_and_shout_at_engineer(
+ self.source_package_release).copyright = None
view = create_initialized_view(
self.source_package_release, '+copyright')
self.assertEqual(expected, view.highlighted_copyright)
def test_highlighted_copyright_no_matches(self):
expected = 'nothing to see and/or do.'
- self.source_package_release.copyright = expected
+ remove_security_proxy_and_shout_at_engineer(
+ self.source_package_release).copyright = expected
view = create_initialized_view(
self.source_package_release, '+copyright')
self.assertEqual(expected, view.highlighted_copyright)
def test_highlighted_copyright_match_url(self):
- self.source_package_release.copyright = (
+ remove_security_proxy_and_shout_at_engineer(
+ self.source_package_release).copyright = (
'Downloaded from https://upstream.dom/fnord/no/ and')
expected = (
'Downloaded from '
@@ -90,7 +94,8 @@
self.assertEqual(expected, view.highlighted_copyright)
def test_highlighted_copyright_match_path(self):
- self.source_package_release.copyright = (
+ remove_security_proxy_and_shout_at_engineer(
+ self.source_package_release).copyright = (
'See /usr/share/common-licenses/GPL')
expected = (
'See '
@@ -100,7 +105,8 @@
self.assertEqual(expected, view.highlighted_copyright)
def test_highlighted_copyright_match_multiple(self):
- self.source_package_release.copyright = (
+ remove_security_proxy_and_shout_at_engineer(
+ self.source_package_release).copyright = (
'See /usr/share/common-licenses/GPL or https://osi.org/mit')
expected = (
'See '
=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py 2010-07-20 17:32:03 +0000
+++ lib/lp/soyuz/tests/test_publishing.py 2010-07-21 09:04:53 +0000
@@ -43,7 +43,8 @@
from lp.soyuz.interfaces.queue import PackageUploadStatus
from canonical.launchpad.scripts import FakeLogger
from lp.testing import TestCaseWithFactory
-from lp.testing.factory import LaunchpadObjectFactory
+from lp.testing.factory import (
+ LaunchpadObjectFactory, remove_security_proxy_and_shout_at_engineer)
class SoyuzTestPublisher:
@@ -135,7 +136,7 @@
changes_file_name="foo_666_source.changes",
changes_file_content="fake changes file content",
upload_status=PackageUploadStatus.DONE):
- signing_key = self.person.gpg_keys[0]
+ signing_key = self.person.gpg_keys[0]
package_upload = distroseries.createQueueEntry(
pocket, changes_file_name, changes_file_content, archive,
signing_key)
@@ -144,7 +145,10 @@
PackageUploadStatus.DONE: 'setDone',
PackageUploadStatus.ACCEPTED: 'setAccepted',
}
- method = getattr(package_upload, status_to_method[upload_status])
+ naked_package_upload = remove_security_proxy_and_shout_at_engineer(
+ package_upload)
+ method = getattr(
+ naked_package_upload, status_to_method[upload_status])
method()
return package_upload
@@ -220,7 +224,9 @@
changes_file_name=changes_file_name,
changes_file_content=changes_file_content,
upload_status=upload_status)
- package_upload.addSource(spr)
+ naked_package_upload = remove_security_proxy_and_shout_at_engineer(
+ package_upload)
+ naked_package_upload.addSource(spr)
if filename is None:
filename = "%s_%s.dsc" % (sourcename, version)
@@ -472,6 +478,7 @@
self.pool_dir = self.config.poolroot
self.temp_dir = self.config.temproot
self.logger = FakeLogger()
+
def message(self, prefix, *stuff, **kw):
pass
self.logger.message = message
@@ -600,7 +607,7 @@
pub_source.publish(self.disk_pool, self.logger)
self.layer.commit()
self.assertEqual(
- pub_source.status,PackagePublishingStatus.PENDING)
+ pub_source.status, PackagePublishingStatus.PENDING)
self.assertEqual(open(foo_dsc_path).read().strip(), 'Hello world')
def testPublishingDifferentContents(self):
@@ -779,7 +786,7 @@
try:
copies = tuple(copied)
except TypeError:
- copies = (copied,)
+ copies = (copied, )
for copy in copies:
self.assertEquals(copy.component, pub_record.component)
@@ -889,7 +896,8 @@
"""Return a mock source package publishing record for the archive
and architecture used in this testcase.
- :param architecturehintlist: Architecture hint list (e.g. "i386 amd64")
+ :param architecturehintlist: Architecture hint list (e.g.
+ "i386 amd64")
"""
return super(BuildRecordCreationTests, self).getPubSource(
archive=self.archive, distroseries=self.distroseries,
@@ -930,7 +938,8 @@
def test_createMissingBuilds_restricts_explicitlist(self):
"""createMissingBuilds() should limit builds targeted at a
- variety of architectures architecture to those allowed for the archive.
+ variety of architectures architecture to those allowed for the
+ archive.
"""
pubrec = self.getPubSource(architecturehintlist='sparc i386 avr')
builds = pubrec.createMissingBuilds()
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-07-17 21:02:33 +0000
+++ lib/lp/testing/factory.py 2010-07-21 09:04:53 +0000
@@ -15,6 +15,7 @@
'GPGSigningContext',
'LaunchpadObjectFactory',
'ObjectFactory',
+ 'remove_security_proxy_and_shout_at_engineer',
]
from contextlib import nested
@@ -28,15 +29,18 @@
import os.path
from random import randint
from StringIO import StringIO
+import sys
from textwrap import dedent
from threading import local
+from types import InstanceType
import pytz
from twisted.python.util import mergeFunctionMetadata
from zope.component import ComponentLookupError, getUtility
-from zope.security.proxy import removeSecurityProxy
+from zope.security.proxy import (
+ builtin_isinstance, Proxy, ProxyFactory, removeSecurityProxy)
from canonical.autodecorate import AutoDecorate
from canonical.config import config
@@ -325,7 +329,7 @@
branch_id, rcstype, url, cvs_root, cvs_module)
-class LaunchpadObjectFactory(ObjectFactory):
+class _LaunchpadObjectFactory(ObjectFactory):
"""Factory methods for creating Launchpad objects.
All the factory methods should be callable with no parameters.
@@ -744,8 +748,8 @@
# We don't want to login() as the person used to create the product,
# so we remove the security proxy before creating the series.
naked_product = removeSecurityProxy(product)
- return naked_product.newSeries(owner=owner, name=name,
- summary=summary)
+ return ProxyFactory(
+ naked_product.newSeries(owner=owner, name=name, summary=summary))
def makeProject(self, name=None, displayname=None, title=None,
homepageurl=None, summary=None, owner=None,
@@ -828,7 +832,7 @@
url = self.getUniqueURL()
else:
raise UnknownBranchTypeError(
- 'Unrecognized branch type: %r' % (branch_type,))
+ 'Unrecognized branch type: %r' % (branch_type, ))
namespace = get_branch_namespace(
owner, product=product, distroseries=distroseries,
@@ -1671,7 +1675,7 @@
description=self.getUniqueString(),
parent_series=parent_series, owner=distribution.owner)
series.status = status
- return series
+ return ProxyFactory(series)
# Most people think of distro releases as distro series.
makeDistroSeries = makeDistroRelease
@@ -1780,7 +1784,7 @@
def makeRecipeText(self, *branches):
if len(branches) == 0:
- branches = (self.makeAnyBranch(),)
+ branches = (self.makeAnyBranch(), )
base_branch = branches[0]
other_branches = branches[1:]
text = MINIMAL_RECIPE_TEXT % base_branch.bzr_identity
@@ -1810,9 +1814,11 @@
owner = self.makePerson()
if distroseries is None:
distroseries = self.makeDistroSeries()
- distroseries.nominatedarchindep = distroseries.newArch(
- 'i386', ProcessorFamily.get(1), False, owner,
- supports_virtualized=True)
+ naked_distroseries = removeSecurityProxy(distroseries)
+ naked_distroseries.nominatedarchindep = (
+ naked_distroseries.newArch(
+ 'i386', ProcessorFamily.get(1), False, owner,
+ supports_virtualized=True))
if name is None:
name = self.getUniqueString().decode('utf8')
@@ -1903,6 +1909,7 @@
ddd57463774cae9b50e70cd51221281b 185913 ed_0.2.orig.tar.gz
f9e1e5f13725f581919e9bfd62272a05 8506 ed_0.2-20.diff.gz
"""))
+
class Changes:
architectures = ['source']
logger = QuietFakeLogger()
@@ -1936,7 +1943,7 @@
productseries = self.makeProductSeries(owner=owner)
# Make it use Translations, otherwise there's little point
# to us creating a template for it.
- productseries.product.official_rosetta = True
+ removeSecurityProxy(productseries).product.official_rosetta = True
templateset = getUtility(IPOTemplateSet)
subset = templateset.getSubset(
distroseries, sourcepackagename, productseries)
@@ -2711,3 +2718,56 @@
new_uuid = getUtility(ITemporaryStorageManager).new(blob, expires)
return getUtility(ITemporaryStorageManager).fetch(new_uuid)
+
+
+class LaunchpadObjectFactory:
+ """A wrapper around _LaunchpadObjectFactory.
+
+ Ensure that each object created by a _LaunchpadObjectFactory method
+ is either of a simple Python type or is security proxied.
+
+ A warning message s printed to stderr if a factory method creates
+ an object without a security proxy.
+ """
+
+ def __init__(self):
+ self._factory = _LaunchpadObjectFactory()
+
+ def __getattr__(self, name):
+ attr = getattr(self._factory, name)
+ if callable(attr):
+
+ def guarded_method(*args, **kw):
+ result = attr(*args, **kw)
+ if builtin_isinstance(result, Proxy):
+ return result
+ if result is None:
+ return result
+ if type(result) not in (
+ int, str, unicode, Message, DSCFile, InstanceType, tuple,
+ datetime):
+ message = (
+ "Unproxied object returned by "
+ "LaunchpadObjectFactory.%s" % name)
+ print >>sys.stderr, message
+ return result
+ return guarded_method
+ else:
+ return attr
+
+
+def remove_security_proxy_and_shout_at_engineer(obj):
+ """Remove an object's security proxy and print a warning.
+
+ A number of LaunchpadObjectFactory methods returned objects without
+ a security proxy. This is now no longer possible, but a number of
+ tests rely on unrestricted access to object attributes.
+
+ This function should only beused in existing tests if a test fails
+ because a newer version of LaunchpadObjectFactory returns a security
+ proxied object.
+ """
+ print >>sys.stderr, (
+ "\ncalled removeSecurityProxy() for %r without a check if this "
+ "reasonable" % obj)
+ return removeSecurityProxy(obj)
=== modified file 'lib/lp/translations/browser/tests/test_breadcrumbs.py'
--- lib/lp/translations/browser/tests/test_breadcrumbs.py 2010-04-28 10:13:00 +0000
+++ lib/lp/translations/browser/tests/test_breadcrumbs.py 2010-07-21 09:04:53 +0000
@@ -9,6 +9,7 @@
from lp.services.worlddata.interfaces.language import ILanguageSet
from lp.testing.breadcrumbs import BaseBreadcrumbTestCase
+from lp.testing.factory import remove_security_proxy_and_shout_at_engineer
from lp.translations.interfaces.distroserieslanguage import (
IDistroSeriesLanguageSet)
from lp.translations.interfaces.productserieslanguage import (
@@ -101,7 +102,8 @@
name='crumb-tester', displayname="Crumb Tester")
series = self.factory.makeDistroRelease(
name="test", version="1.0", distribution=distribution)
- series.hide_all_translations = False
+ naked_series = remove_security_proxy_and_shout_at_engineer(series)
+ naked_series.hide_all_translations = False
serieslanguage = getUtility(IDistroSeriesLanguageSet).getDummy(
series, self.language)
=== modified file 'lib/lp/translations/doc/translations-export-to-branch.txt'
--- lib/lp/translations/doc/translations-export-to-branch.txt 2010-06-16 07:22:57 +0000
+++ lib/lp/translations/doc/translations-export-to-branch.txt 2010-07-21 09:04:53 +0000
@@ -220,8 +220,12 @@
>>> from email import message_from_string
>>> from lp.services.mail import stub
>>> from lp.codehosting.vfs import get_rw_server
+ >>> from lp.testing.factory import (
+ ... remove_security_proxy_and_shout_at_engineer)
>>> productseries = factory.makeProductSeries()
- >>> productseries.translations_branch = factory.makeBranch()
+ >>> naked_productseries = remove_security_proxy_and_shout_at_engineer(
+ ... productseries)
+ >>> naked_productseries.translations_branch = factory.makeBranch()
>>> template = factory.makePOTemplate(productseries=productseries)
>>> potmsgset = factory.makePOTMsgSet(template)
>>> pofile = removeSecurityProxy(
=== modified file 'lib/lp/translations/stories/buildfarm/xx-build-summary.txt'
--- lib/lp/translations/stories/buildfarm/xx-build-summary.txt 2010-03-16 11:09:46 +0000
+++ lib/lp/translations/stories/buildfarm/xx-build-summary.txt 2010-07-21 09:04:53 +0000
@@ -14,6 +14,8 @@
... ILibraryFileAliasSet)
>>> from canonical.launchpad.scripts.logger import QuietFakeLogger
>>> from lp.buildmaster.interfaces.buildqueue import IBuildQueueSet
+ >>> from lp.testing.factory import (
+ ... remove_security_proxy_and_shout_at_engineer)
>>> from lp.testing.fakemethod import FakeMethod
>>> from lp.translations.interfaces.translations import (
... TranslationsBranchImportMode)
@@ -29,12 +31,15 @@
>>> productseries = factory.makeProductSeries(owner=owner)
>>> product = productseries.product
- >>> product.official_rosetta = True
+ >>> naked_product = remove_security_proxy_and_shout_at_engineer(product)
+ >>> naked_product.official_rosetta = True
>>> branch = factory.makeProductBranch(product=product, owner=owner)
>>> branch_url = branch.unique_name
- >>> productseries.branch = factory.makeBranch()
- >>> productseries.translations_autoimport_mode = (
+ >>> naked_productseries = remove_security_proxy_and_shout_at_engineer(
+ ... productseries)
+ >>> naked_productseries.branch = factory.makeBranch()
+ >>> naked_productseries.translations_autoimport_mode = (
... TranslationsBranchImportMode.IMPORT_TEMPLATES)
>>> specific_job = factory.makeTranslationTemplatesBuildJob(branch=branch)
>>> buildqueue = getUtility(IBuildQueueSet).getByJob(specific_job.job)