Merge ~cjwatson/launchpad:modern-ztk-prepare into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 26ffdc98ac940d66cdc441b7ff6aa02966f15207
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:modern-ztk-prepare
Merge into: launchpad:master
Diff against target: 290 lines (+78/-22)
11 files modified
lib/lp/code/interfaces/gitref.py (+4/-2)
lib/lp/code/interfaces/gitrepository.py (+3/-1)
lib/lp/registry/interfaces/poll.py (+4/-2)
lib/lp/services/fields/__init__.py (+5/-3)
lib/lp/services/job/runner.py (+11/-0)
lib/lp/services/mail/mbox.py (+8/-1)
lib/lp/services/mail/stub.py (+15/-1)
lib/lp/services/scripts/__init__.py (+3/-3)
lib/lp/services/webapp/tests/test_session.py (+21/-1)
lib/lp/testing/factory.py (+1/-2)
lib/lp/testing/matchers.py (+3/-6)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+375697@code.launchpad.net

Commit message

Fix various problems upgrading to modern ZTK

Description of the change

I've been working on upgrading Launchpad to modern versions of its Zope Toolkit dependencies. This bundles a collection of small fixes discovered in the process, all of which are needed to make tests pass after the upgrade. This doesn't actually apply any package upgrades; that will come later.

I'd particularly like to get the change to lp.services.webapp.tests.test_session landed before upgrading, since that will allow us to verify that session IDs match before and after upgrading zope.session.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) wrote :

looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/code/interfaces/gitref.py b/lib/lp/code/interfaces/gitref.py
2index 16198eb..3c5f693 100644
3--- a/lib/lp/code/interfaces/gitref.py
4+++ b/lib/lp/code/interfaces/gitref.py
5@@ -1,4 +1,4 @@
6-# Copyright 2015-2018 Canonical Ltd. This software is licensed under the
7+# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 """Git reference ("ref") interfaces."""
11@@ -419,7 +419,9 @@ class IGitRefEdit(Interface):
12 # _schema_circular_imports.py.
13 value_type=InlineObject(schema=Interface),
14 description=_(dedent("""\
15- The new list of grants for this reference. For example::
16+ The new list of grants for this reference.
17+
18+ For example::
19
20 [
21 {
22diff --git a/lib/lp/code/interfaces/gitrepository.py b/lib/lp/code/interfaces/gitrepository.py
23index 2fca251..386ad62 100644
24--- a/lib/lp/code/interfaces/gitrepository.py
25+++ b/lib/lp/code/interfaces/gitrepository.py
26@@ -812,7 +812,9 @@ class IGitRepositoryEdit(IWebhookTarget):
27 # _schema_circular_imports.py.
28 value_type=InlineObject(schema=Interface),
29 description=_(dedent("""\
30- The new list of rules for this repository. For example::
31+ The new list of rules for this repository.
32+
33+ For example::
34
35 [
36 {
37diff --git a/lib/lp/registry/interfaces/poll.py b/lib/lp/registry/interfaces/poll.py
38index 75a6af2..8f630d6 100644
39--- a/lib/lp/registry/interfaces/poll.py
40+++ b/lib/lp/registry/interfaces/poll.py
41@@ -1,4 +1,4 @@
42-# Copyright 2009 Canonical Ltd. This software is licensed under the
43+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
44 # GNU Affero General Public License version 3 (see the file LICENSE).
45
46 __all__ = [
47@@ -171,7 +171,9 @@ class IPoll(Interface):
48 raise Invalid(
49 "A poll cannot close at the time (or before) it opens.")
50 now = datetime.now(pytz.UTC)
51- twelve_hours_ahead = now + timedelta(hours=12)
52+ # Allow a bit of slack to account for time between form creation and
53+ # validation.
54+ twelve_hours_ahead = now + timedelta(hours=12) - timedelta(seconds=60)
55 start_date = poll.dateopens.astimezone(pytz.UTC)
56 if start_date < twelve_hours_ahead:
57 raise Invalid(
58diff --git a/lib/lp/services/fields/__init__.py b/lib/lp/services/fields/__init__.py
59index ee9e16f..6bbc0b3 100644
60--- a/lib/lp/services/fields/__init__.py
61+++ b/lib/lp/services/fields/__init__.py
62@@ -1,4 +1,4 @@
63-# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
64+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
65 # GNU Affero General Public License version 3 (see the file LICENSE).
66
67 __metaclass__ = type
68@@ -65,7 +65,10 @@ from lazr.uri import (
69 URI,
70 )
71 from zope.component import getUtility
72-from zope.interface import implementer
73+from zope.interface import (
74+ implementer,
75+ Interface,
76+ )
77 from zope.schema import (
78 Bool,
79 Bytes,
80@@ -83,7 +86,6 @@ from zope.schema.interfaces import (
81 IBytes,
82 IDate,
83 IDatetime,
84- Interface,
85 IObject,
86 IText,
87 ITextLine,
88diff --git a/lib/lp/services/job/runner.py b/lib/lp/services/job/runner.py
89index 635e075..800d303 100644
90--- a/lib/lp/services/job/runner.py
91+++ b/lib/lp/services/job/runner.py
92@@ -267,6 +267,17 @@ class BaseRunnableJob(BaseRunnableJobSource):
93 response = self.runViaCelery(ignore_result)
94 if not ignore_result:
95 BaseRunnableJob.celery_responses.append(response)
96+ # transaction >= 1.6.0 removes data managers from the
97+ # transaction before calling after-commit hooks; this means that
98+ # the transaction begun in runViaCelery to look up details of
99+ # the job doesn't get committed or rolled back, and so
100+ # subsequent SQL statements executed by the caller confusingly
101+ # see a database snapshot from before the Celery job was run,
102+ # even if they use the block_on_job test helper. Since
103+ # runViaCelery never issues any data-modifying statements
104+ # itself, the least confusing thing to do here is to just roll
105+ # back its transaction.
106+ transaction.abort()
107
108 def celeryRunOnCommit(self):
109 """Configure transaction so that commit runs this job via Celery."""
110diff --git a/lib/lp/services/mail/mbox.py b/lib/lp/services/mail/mbox.py
111index 0ae2444..1767523 100644
112--- a/lib/lp/services/mail/mbox.py
113+++ b/lib/lp/services/mail/mbox.py
114@@ -1,4 +1,4 @@
115-# Copyright 2009 Canonical Ltd. This software is licensed under the
116+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
117 # GNU Affero General Public License version 3 (see the file LICENSE).
118
119 """An IMailer that stores messages in a specified mbox file."""
120@@ -69,3 +69,10 @@ class MboxMailer:
121 chained_mailer = getUtility(IMailer, self.mailer)
122 chained_mailer.send(fromaddr, toaddrs, message)
123 return message_id
124+
125+ def vote(self, fromaddr, toaddrs, message):
126+ pass
127+
128+ def abort(self):
129+ # We don't do any work until send() is called, so aborting is trivial.
130+ pass
131diff --git a/lib/lp/services/mail/stub.py b/lib/lp/services/mail/stub.py
132index 98d40da..1492057 100644
133--- a/lib/lp/services/mail/stub.py
134+++ b/lib/lp/services/mail/stub.py
135@@ -1,4 +1,4 @@
136-# Copyright 2009 Canonical Ltd. This software is licensed under the
137+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
138 # GNU Affero General Public License version 3 (see the file LICENSE).
139
140 """A stub IMailer for use in development and unittests."""
141@@ -52,6 +52,13 @@ class StubMailer:
142 sendmail = getUtility(IMailer, self.mailer)
143 sendmail.send(self.from_addr, self.to_addrs, message)
144
145+ def vote(self, fromaddr, toaddrs, message):
146+ pass
147+
148+ def abort(self):
149+ # We don't do any work until send() is called, so aborting is trivial.
150+ pass
151+
152
153 test_emails = []
154
155@@ -67,3 +74,10 @@ class TestMailer:
156
157 def send(self, from_addr, to_addrs, message):
158 test_emails.append((from_addr, to_addrs, message))
159+
160+ def vote(self, fromaddr, toaddrs, message):
161+ pass
162+
163+ def abort(self):
164+ # We don't do any work until send() is called, so aborting is trivial.
165+ pass
166diff --git a/lib/lp/services/scripts/__init__.py b/lib/lp/services/scripts/__init__.py
167index 51456c2..d4a362d 100644
168--- a/lib/lp/services/scripts/__init__.py
169+++ b/lib/lp/services/scripts/__init__.py
170@@ -1,4 +1,4 @@
171-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
172+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
173 # GNU Affero General Public License version 3 (see the file LICENSE).
174
175 """Library functions for use in all scripts.
176@@ -20,10 +20,10 @@ import os
177 import sys
178 import threading
179
180+import zope.component.hooks
181 from zope.configuration.config import ConfigurationMachine
182 from zope.security.management import setSecurityPolicy
183 import zope.sendmail.delivery
184-import zope.site.hooks
185
186 from lp.services.config import config
187 from lp.services.database.postgresql import ConnectionString
188@@ -76,7 +76,7 @@ def execute_zcml_for_scripts(use_web_security=False):
189 from zope.configuration import xmlconfig
190
191 # Hook up custom component architecture calls
192- zope.site.hooks.setHooks()
193+ zope.component.hooks.setHooks()
194
195 # Load server-independent site config
196 context = ConfigurationMachine()
197diff --git a/lib/lp/services/webapp/tests/test_session.py b/lib/lp/services/webapp/tests/test_session.py
198index 91c2bfa..b99ff4f 100644
199--- a/lib/lp/services/webapp/tests/test_session.py
200+++ b/lib/lp/services/webapp/tests/test_session.py
201@@ -1,10 +1,13 @@
202-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
203+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
204 # GNU Affero General Public License version 3 (see the file LICENSE).
205
206 import datetime
207+import hashlib
208+import hmac
209
210 from testtools import TestCase
211 from testtools.matchers import Contains
212+from zope.session.session import digestEncode
213
214 from lp.services.webapp.login import (
215 isFreshLogin,
216@@ -67,6 +70,23 @@ class TestLaunchpadCookieClientIdManager(TestCase):
217 dict(request.response.getHeaders())['Set-Cookie'],
218 Contains('; httponly;'))
219
220+ def test_stable_client_id(self):
221+ # Changing the HMAC algorithm used for client IDs would invalidate
222+ # existing sessions, so be careful that that doesn't happen
223+ # accidentally across upgrades.
224+ request = LaunchpadTestRequest()
225+ idmanager = LaunchpadCookieClientIdManager()
226+ idmanager._secret = 'secret'
227+ data = b'random'
228+ s = digestEncode(hashlib.sha1(data).digest())
229+ mac = hmac.new(
230+ s, idmanager.secret.encode(), digestmod=hashlib.sha1).digest()
231+ sid = (s + digestEncode(mac)).decode()
232+ idmanager.setRequestId(request, sid)
233+ # getRequestId will only return the previously-set ID if it was
234+ # generated using the correct secret with the correct algorithm.
235+ self.assertEqual(sid, idmanager.getRequestId(request))
236+
237
238 class TestSessionRelatedFunctions(TestCaseWithFactory):
239
240diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
241index 39d2725..f0310f3 100644
242--- a/lib/lp/testing/factory.py
243+++ b/lib/lp/testing/factory.py
244@@ -64,7 +64,6 @@ from zope.component import (
245 getUtility,
246 )
247 from zope.security.proxy import (
248- builtin_isinstance,
249 Proxy,
250 ProxyFactory,
251 removeSecurityProxy,
252@@ -4955,7 +4954,7 @@ def is_security_proxied_or_harmless(obj):
253 """Check that the object is security wrapped or a harmless object."""
254 if obj is None:
255 return True
256- if builtin_isinstance(obj, Proxy):
257+ if isinstance(obj, Proxy):
258 return True
259 if type(obj) in unwrapped_types:
260 return True
261diff --git a/lib/lp/testing/matchers.py b/lib/lp/testing/matchers.py
262index ec9d22b..86266e6 100644
263--- a/lib/lp/testing/matchers.py
264+++ b/lib/lp/testing/matchers.py
265@@ -1,4 +1,4 @@
266-# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
267+# Copyright 2010-2019 Canonical Ltd. This software is licensed under the
268 # GNU Affero General Public License version 3 (see the file LICENSE).
269
270 __metaclass__ = type
271@@ -38,10 +38,7 @@ from zope.interface.exceptions import (
272 DoesNotImplement,
273 )
274 from zope.interface.verify import verifyObject
275-from zope.security.proxy import (
276- builtin_isinstance,
277- Proxy,
278- )
279+from zope.security.proxy import Proxy
280
281 from lp.services.database.sqlbase import flush_database_caches
282 from lp.services.webapp import canonical_url
283@@ -249,7 +246,7 @@ class IsProxied(Matcher):
284 return "Is proxied."
285
286 def match(self, matchee):
287- if not builtin_isinstance(matchee, Proxy):
288+ if not isinstance(matchee, Proxy):
289 return IsNotProxied(matchee)
290 return None
291

Subscribers

People subscribed via source and target branches

to status/vote changes: