Merge lp:~flacoste/launchpad/bug-559128 into lp:launchpad

Proposed by Francis J. Lacoste
Status: Merged
Approved by: Gary Poster
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~flacoste/launchpad/bug-559128
Merge into: lp:launchpad
Diff against target: 452 lines (+121/-96)
10 files modified
lib/canonical/launchpad/scripts/__init__.py (+16/-18)
lib/canonical/launchpad/webapp/interaction.py (+62/-1)
lib/lp/bugs/scripts/checkwatches/updater.py (+3/-2)
lib/lp/code/xmlrpc/codehosting.py (+4/-8)
lib/lp/services/scripts/base.py (+3/-13)
lib/lp/testing/__init__.py (+2/-1)
lib/lp/testing/_login.py (+25/-38)
lib/lp/testing/_webservice.py (+2/-1)
scripts/bugzilla-import.py (+2/-7)
scripts/sourceforge-import.py (+2/-7)
To merge this branch: bzr merge lp:~flacoste/launchpad/bug-559128
Reviewer Review Type Date Requested Status
Gary Poster (community) Approve
Review via email: mp+23504@code.launchpad.net

Commit message

Don't import lp.testing in production code. Refactor login and login_person helper into setupInteractionByEmail and setupInteractionForPerson that can be used in production code.

Description of the change

= Summary =

This branch should re-enable edge update (bug #559128) by fixing some tech-debt
(bug #285808)

The problem with the edge update is that subunit is now installed via a
package and that package isn't installed in production. You'd wonder why
subunit would be required for running Launchpad and that's exactly the point.

It turns out that some production code paths import from lp.testing because
they use the login and login_person helper (which was bug #285808).

== Proposed fix ==

Remove tech-debts

== Pre-implementation notes ==

Nothing special.

== Implementation details ==

I moved the useful part of login and login_person into two functions
setupInteractionByEmail and setupInteractionForPerson. Those lives alongside
setupInteraction in canonical.launchpad.webapp.interaction.

The login and login_person() contains the test-specific logic around these.

I updated all production call sites to use the one from
canonical.launchpad.webapp.interaction

Since execute_zcml_for_scripts also imported testing code, i wrapped that one
only if the layers module has been imported. I would have removed it entirely,
but thought that the safety hook was worth the relative ugliness.

== Tests ==

This branch is a pure refactoring. I did test that lp.testing wasn't imported
anymore in production paths by adding a raise RuntimeError in lp.testing, it's
not trigger when calling make run. (It is before this fix.)

== Demo and Q/A ==

Nothing special

= Launchpad lint =

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

Linting changed files:
  lib/canonical/launchpad/scripts/__init__.py
  lib/lp/services/scripts/base.py
  scripts/sourceforge-import.py
  lib/lp/code/xmlrpc/codehosting.py
  lib/lp/bugs/scripts/checkwatches/updater.py
  lib/lp/testing/__init__.py
  lib/canonical/launchpad/webapp/interaction.py
  scripts/bugzilla-import.py
  lib/lp/testing/_webservice.py
  lib/lp/testing/_login.py

== Pyflakes notices ==

lib/lp/testing/_login.py
    8: 'getUtility' imported but unused

I fixed that one on a follow-up commit.

--
Francis J. Lacoste
<email address hidden>

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

Wow, very nice!

"Zopefull": nice word :-)

Very nice cleanups in the branch, such as correcting the imports from zope.security.

A niggle, but I found it a bit odd when ``setupInteractionForPerson`` called ``return setupInteractionByEmail(ANONYMOUS, participation)``. I wondered why I should call a function with the word "email" in it to get an interaction when there's no email involved. My preference would be to change setupInteraction to look like this:

def setupInteraction(principal, login=None, participation=None):
    """Sets up a new interaction with the given principal.

    The login gets added to the launch bag.

    You can optionally pass in a participation to be used. If no
    participation is given, a Participation is used.
    """
    # If principal is None, this method acts just like endInteraction.
    if principal is None:
        endInteraction()
        return

    # THIS IS THE NEW BIT
    if principal == ANONYMOUS:
        authutil = getUtility(IPlacelessAuthUtility)
        principal = authutil.unauthenticatedPrincipal()

    if participation is None:
        participation = Participation()
(... and so on, as is)

Then the line in `setupInteractionForPerson`` would read ``return setupInteraction(ANONYMOUS)``. That makes much more sense to me. I'd guess you'd also delete these lines from ``setupInteractionByEmail`` too, because they would be unnecessary:

119 + else:
120 + principal = authutil.unauthenticatedPrincipal()

As I said, that's a niggle, and I won't insist on it, but it did give me pause.

Looks like you caught all my XXXs. :-) Yay!

Thank you,

Gary

review: Approve
Revision history for this message
Francis J. Lacoste (flacoste) wrote :

On April 15, 2010, Gary Poster wrote:
> A niggle, but I found it a bit odd when ``setupInteractionForPerson``
> called ``return setupInteractionByEmail(ANONYMOUS, participation)``. I
> wondered why I should call a function with the word "email" in it to get
> an interaction when there's no email involved. My preference would be to
> change setupInteraction to look like this:
>
Well, I guess, I could call setupInteraction directly in that case. I'll do
this.

> def setupInteraction(principal, login=None, participation=None):
> """Sets up a new interaction with the given principal.

This changes the signature of the function. I'll pass on it since I don't feel
like chasing all the call sites :-)

Cheers

--
Francis J. Lacoste
<email address hidden>

Revision history for this message
Francis J. Lacoste (flacoste) wrote :

On April 15, 2010, Francis J. Lacoste wrote:
> On April 15, 2010, Gary Poster wrote:
> > A niggle, but I found it a bit odd when ``setupInteractionForPerson``
> > called ``return setupInteractionByEmail(ANONYMOUS, participation)``. I
> > wondered why I should call a function with the word "email" in it to get
> > an interaction when there's no email involved. My preference would be to
>
> > change setupInteraction to look like this:
> Well, I guess, I could call setupInteraction directly in that case. I'll do
> this.
>
> > def setupInteraction(principal, login=None, participation=None):
> > """Sets up a new interaction with the given principal.
>
> This changes the signature of the function. I'll pass on it since I don't
> feel like chasing all the call sites :-)
>
> Cheers

Disregard this, your suggestion makes perfect sense and I'll implement it.

/me shouldn't reply before the context restore has finished...

--
Francis J. Lacoste
<email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/scripts/__init__.py'
2--- lib/canonical/launchpad/scripts/__init__.py 2010-04-09 12:31:20 +0000
3+++ lib/canonical/launchpad/scripts/__init__.py 2010-04-17 00:32:28 +0000
4@@ -20,6 +20,7 @@
5
6 import atexit
7 import os
8+import sys
9 from textwrap import dedent
10 import threading
11
12@@ -32,6 +33,8 @@
13 from zope.security.simplepolicies import PermissiveSecurityPolicy
14
15 from canonical.launchpad.webapp.authorization import LaunchpadSecurityPolicy
16+from canonical.launchpad.webapp.interaction import (
17+ ANONYMOUS, setupInteractionByEmail)
18
19 from canonical import lp
20 from canonical.config import config
21@@ -91,15 +94,18 @@
22 application uses will be used. Otherwise everything protected by a
23 permission is allowed, and everything else denied.
24 """
25- # Prevent some cases of erroneous layer useage.
26- from canonical.testing import (
27- FunctionalLayer, BaseLayer, ZopelessLayer
28- )
29- assert not FunctionalLayer.isSetUp, \
30- 'Setting up Zopeless CA when Zopefull CA is already running'
31- assert not BaseLayer.isSetUp or ZopelessLayer.isSetUp, """
32- execute_zcml_for_scripts should not be called from tests.
33- Instead, your test should use the Zopeless layer.
34+
35+ # When in testing mode, prevent some cases of erroneous layer usage.
36+ # But we don't want to import that module in production usage, thus
37+ # the conditional block.
38+ if 'canonical.testing.layers' in sys.modules:
39+ from canonical.testing.layers import (
40+ FunctionalLayer, BaseLayer, ZopelessLayer)
41+ assert not FunctionalLayer.isSetUp, \
42+ 'Setting up Zopeless CA when Zopefull CA is already running'
43+ assert not BaseLayer.isSetUp or ZopelessLayer.isSetUp, """
44+ execute_zcml_for_scripts should not be called from tests.
45+ Instead, your test should use the Zopeless layer.
46 """
47
48 if config.instance_name == 'testrunner':
49@@ -147,15 +153,7 @@
50 # the proper API for having a principal / user running in scripts.
51 # The script will have full permissions because of the
52 # PermissiveSecurityPolicy set up in script.zcml.
53- # XXX gary 20-Oct-2008 bug 285808
54- # The wisdom of using a test fixture for production should be
55- # reconsidered.
56- from canonical.launchpad.ftests import login
57- # The Participation is used to specify that we do not want a
58- # LaunchpadTestRequest, which ftests normally use. shipit scripts, in
59- # particular, need to be careful, because of code in canonical_url.
60- from canonical.launchpad.webapp.interaction import Participation
61- login('launchpad.anonymous', Participation())
62+ setupInteractionByEmail(ANONYMOUS)
63
64
65 def db_options(parser):
66
67=== modified file 'lib/canonical/launchpad/webapp/interaction.py'
68--- lib/canonical/launchpad/webapp/interaction.py 2009-06-25 05:30:52 +0000
69+++ lib/canonical/launchpad/webapp/interaction.py 2010-04-17 00:32:28 +0000
70@@ -13,7 +13,20 @@
71 from zope.security.management import (
72 endInteraction, newInteraction, queryInteraction)
73
74-from canonical.launchpad.webapp.interfaces import IOpenLaunchBag
75+from canonical.launchpad.webapp.interfaces import (
76+ IOpenLaunchBag, IPlacelessAuthUtility)
77+
78+
79+__all__ = [
80+ 'ANONYMOUS',
81+ 'get_current_principal',
82+ 'setupInteraction',
83+ 'setupInteractionByEmail',
84+ 'setupInteractionForPerson',
85+ ]
86+
87+
88+ANONYMOUS = 'launchpad.anonymous'
89
90
91 def get_current_principal():
92@@ -40,6 +53,10 @@
93 endInteraction()
94 return
95
96+ if principal == ANONYMOUS:
97+ authutil = getUtility(IPlacelessAuthUtility)
98+ principal = authutil.unauthenticatedPrincipal()
99+
100 if participation is None:
101 participation = Participation()
102
103@@ -61,7 +78,51 @@
104 participation.principal = principal
105
106
107+def setupInteractionByEmail(email, participation=None):
108+ """Setup an interaction using an email.
109+
110+ If the ANONYMOUS constant is supplied as the email,
111+ an interaction for the anonymous user will be used.
112+
113+ You can optionally pass in a participation to be used. If no
114+ participation is given, an empty participation is used.
115+
116+ If the participation provides IPublicationRequest, it must implement
117+ setPrincipal(), otherwise it must allow setting its principal attribute.
118+ """
119+ authutil = getUtility(IPlacelessAuthUtility)
120+
121+ if email != ANONYMOUS:
122+ # Create an anonymous interaction first because this calls
123+ # IPersonSet.getByEmail() and since this is security wrapped, it needs
124+ # an interaction available.
125+ setupInteraction(authutil.unauthenticatedPrincipal())
126+ principal = authutil.getPrincipalByLogin(email, want_password=False)
127+ assert principal is not None, "Invalid login"
128+ if principal.person is not None and principal.person.is_team:
129+ raise AssertionError("Please do not try to login as a team")
130+ else:
131+ principal = authutil.unauthenticatedPrincipal()
132+
133+ if participation is None:
134+ participation = Participation()
135+
136+ setupInteraction(principal, login=email, participation=participation)
137+
138+
139+def setupInteractionForPerson(person, participation=None):
140+ """Setup a participation for a person."""
141+ from zope.security.proxy import removeSecurityProxy
142+ if person is None:
143+ return setupInteraction(ANONYMOUS, participation)
144+ else:
145+ # Bypass zope's security because IEmailAddress.email is not public.
146+ naked_email = removeSecurityProxy(person.preferredemail)
147+ return setupInteractionByEmail(naked_email.email, participation)
148+
149+
150 class Participation:
151+ """A very simple participation."""
152 implements(IParticipation)
153
154 interaction = None
155
156=== modified file 'lib/lp/bugs/scripts/checkwatches/updater.py'
157--- lib/lp/bugs/scripts/checkwatches/updater.py 2010-04-12 16:06:11 +0000
158+++ lib/lp/bugs/scripts/checkwatches/updater.py 2010-04-17 00:32:28 +0000
159@@ -37,6 +37,8 @@
160
161 from zope.component import getUtility
162 from zope.event import notify
163+from zope.security.management import (
164+ endInteraction, queryInteraction)
165
166 from canonical.database.constants import UTC_NOW
167 from canonical.database.sqlbase import flush_database_updates
168@@ -54,9 +56,8 @@
169 clear_request_started, get_request_start_time, set_request_started)
170 from canonical.launchpad.webapp.errorlog import (
171 ErrorReportingUtility, ScriptRequest)
172+from canonical.launchpad.webapp.interaction import setupInteraction
173 from canonical.launchpad.webapp.interfaces import IPlacelessAuthUtility
174-from canonical.launchpad.webapp.interaction import (
175- setupInteraction, endInteraction, queryInteraction)
176 from canonical.launchpad.webapp.publisher import canonical_url
177
178 from lp.bugs import externalbugtracker
179
180=== modified file 'lib/lp/code/xmlrpc/codehosting.py'
181--- lib/lp/code/xmlrpc/codehosting.py 2010-02-24 04:24:01 +0000
182+++ lib/lp/code/xmlrpc/codehosting.py 2010-04-17 00:32:28 +0000
183@@ -21,8 +21,8 @@
184 from zope.interface import implements
185 from zope.security.interfaces import Unauthorized
186 from zope.security.proxy import removeSecurityProxy
187+from zope.security.management import endInteraction
188
189-from canonical.launchpad.ftests import login_person, logout
190 from lp.code.errors import UnknownBranchTypeError
191 from lp.code.enums import BranchType
192 from lp.code.interfaces.branch import BranchCreationException
193@@ -40,11 +40,11 @@
194 from canonical.launchpad.validators import LaunchpadValidationError
195 from canonical.launchpad.webapp import LaunchpadXMLRPCView
196 from canonical.launchpad.webapp.authorization import check_permission
197+from canonical.launchpad.webapp.interaction import setupInteractionForPerson
198 from canonical.launchpad.webapp.interfaces import (
199 NameLookupFailed, NotFoundError)
200 from canonical.launchpad.xmlrpc import faults
201 from canonical.launchpad.xmlrpc.helpers import return_fault
202-from canonical.launchpad.webapp.interaction import Participation
203
204
205 UTC = pytz.timezone('UTC')
206@@ -179,15 +179,11 @@
207 requester = getUtility(IPersonSet).get(login_id)
208 if requester is None:
209 raise NotFoundError("No person with id %s." % login_id)
210- # XXX gary 21-Oct-2008 bug 285808
211- # We should reconsider using a ftest helper for production code. For now,
212- # we explicitly keep the code from using a test request by using a basic
213- # participation.
214- login_person(requester, Participation())
215+ setupInteractionForPerson(requester)
216 try:
217 return function(requester, *args, **kwargs)
218 finally:
219- logout()
220+ endInteraction()
221
222
223 class BranchFileSystem(LaunchpadXMLRPCView):
224
225=== modified file 'lib/lp/services/scripts/base.py'
226--- lib/lp/services/scripts/base.py 2009-09-21 08:13:40 +0000
227+++ lib/lp/services/scripts/base.py 2010-04-17 00:32:28 +0000
228@@ -23,8 +23,9 @@
229 from canonical.database.sqlbase import ISOLATION_LEVEL_DEFAULT
230 from canonical.launchpad import scripts
231 from canonical.launchpad.interfaces import IScriptActivitySet
232+from canonical.launchpad.webapp.interaction import (
233+ ANONYMOUS, setupInteractionByEmail)
234 from canonical.lp import initZopeless
235-from lp.testing import ANONYMOUS
236
237
238 LOCK_PATH = "/var/lock/"
239@@ -159,18 +160,7 @@
240
241 def login(self, user):
242 """Super-convenience method that avoids the import."""
243- # This import is actually quite expensive, and causes us to
244- # import circularly in pathological cases.
245- # XXX gary 20-Oct-2008 bug 285808
246- # The wisdom of using a test fixture for production should be
247- # reconsidered.
248- from canonical.launchpad.ftests import login
249- # The Participation is used to specify that we do not want a
250- # LaunchpadTestRequest, which ftests normally use. shipit scripts,
251- # in particular, need to be careful, because of code in
252- # canonical_url.
253- from canonical.launchpad.webapp.interaction import Participation
254- login(user, Participation())
255+ setupInteractionByEmail(user)
256
257 #
258 # Locking and running methods. Users only call these explicitly if
259
260=== modified file 'lib/lp/testing/__init__.py'
261--- lib/lp/testing/__init__.py 2010-04-15 14:38:43 +0000
262+++ lib/lp/testing/__init__.py 2010-04-17 00:32:28 +0000
263@@ -74,13 +74,14 @@
264
265 from canonical.launchpad.webapp import errorlog
266 from canonical.config import config
267+from canonical.launchpad.webapp.interaction import ANONYMOUS
268 from canonical.launchpad.webapp.interfaces import ILaunchBag
269 from canonical.launchpad.windmill.testing import constants
270 from lp.codehosting.vfs import branch_id_to_path, get_multi_server
271 # Import the login and logout functions here as it is a much better
272 # place to import them from in tests.
273 from lp.testing._login import (
274- ANONYMOUS, is_logged_in, login, login_person, logout)
275+ is_logged_in, login, login_person, logout)
276 # canonical.launchpad.ftests expects test_tales to be imported from here.
277 # XXX: JonathanLange 2010-01-01: Why?!
278 from lp.testing._tales import test_tales
279
280=== modified file 'lib/lp/testing/_login.py'
281--- lib/lp/testing/_login.py 2009-06-25 04:06:00 +0000
282+++ lib/lp/testing/_login.py 2010-04-17 00:32:28 +0000
283@@ -5,10 +5,9 @@
284 # pylint: disable-msg=W0602,W0603
285 __metaclass__ = type
286
287-from zope.component import getUtility
288 from zope.security.management import endInteraction
289-from canonical.launchpad.webapp.interfaces import IPlacelessAuthUtility
290-from canonical.launchpad.webapp.interaction import setupInteraction
291+from canonical.launchpad.webapp.interaction import (
292+ setupInteractionByEmail, setupInteractionForPerson)
293 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
294 from canonical.launchpad.webapp.vhosts import allvhosts
295
296@@ -16,18 +15,33 @@
297 'login',
298 'login_person',
299 'logout',
300- 'ANONYMOUS',
301 'is_logged_in']
302
303
304-ANONYMOUS = 'launchpad.anonymous'
305-
306 _logged_in = False
307
308 def is_logged_in():
309 global _logged_in
310 return _logged_in
311
312+
313+def _test_login_impl(participation):
314+ # Common implementation of the test login wrappers.
315+ # It sets the global _logged_in flag and create a default
316+ # participation if None was specified.
317+ global _logged_in
318+ _logged_in = True
319+
320+ if participation is None:
321+ # we use the main site as the host name. This is a guess, to make
322+ # canonical_url produce a real-looking host name rather than
323+ # 127.0.0.1.
324+ participation = LaunchpadTestRequest(
325+ environ={'HTTP_HOST': allvhosts.configs['mainsite'].hostname,
326+ 'SERVER_URL': allvhosts.configs['mainsite'].rooturl})
327+ return participation
328+
329+
330 def login(email, participation=None):
331 """Simulates a login, using the specified email.
332
333@@ -40,42 +54,15 @@
334 If the participation provides IPublicationRequest, it must implement
335 setPrincipal(), otherwise it must allow setting its principal attribute.
336 """
337- global _logged_in
338- _logged_in = True
339- authutil = getUtility(IPlacelessAuthUtility)
340-
341- if email != ANONYMOUS:
342- # Create an anonymous interaction first because this calls
343- # IPersonSet.getByEmail() and since this is security wrapped, it needs
344- # an interaction available.
345- setupInteraction(authutil.unauthenticatedPrincipal())
346- principal = authutil.getPrincipalByLogin(email, want_password=False)
347- assert principal is not None, "Invalid login"
348- if principal.person is not None and principal.person.is_team:
349- raise AssertionError("Please do not try to login as a team")
350- else:
351- principal = authutil.unauthenticatedPrincipal()
352-
353- if participation is None:
354- # we use the main site as the host name. This is a guess, to make
355- # canonical_url produce a real-looking host name rather than
356- # 127.0.0.1.
357- participation = LaunchpadTestRequest(
358- environ={'HTTP_HOST': allvhosts.configs['mainsite'].hostname,
359- 'SERVER_URL': allvhosts.configs['mainsite'].rooturl})
360-
361- setupInteraction(principal, login=email, participation=participation)
362+
363+ participation = _test_login_impl(participation)
364+ setupInteractionByEmail(email, participation)
365
366
367 def login_person(person, participation=None):
368 """Login the person with their preferred email."""
369- from zope.security.proxy import removeSecurityProxy
370- if person is None:
371- return login(ANONYMOUS, participation)
372- else:
373- # Bypass zope's security because IEmailAddress.email is not public.
374- naked_email = removeSecurityProxy(person.preferredemail)
375- return login(naked_email.email, participation)
376+ participation = _test_login_impl(participation)
377+ setupInteractionForPerson(person, participation)
378
379
380 def logout():
381
382=== modified file 'lib/lp/testing/_webservice.py'
383--- lib/lp/testing/_webservice.py 2010-03-30 09:18:53 +0000
384+++ lib/lp/testing/_webservice.py 2010-04-17 00:32:28 +0000
385@@ -15,11 +15,12 @@
386 from launchpadlib.credentials import AccessToken, Credentials
387 from launchpadlib.launchpad import Launchpad
388
389+from canonical.launchpad.webapp.interaction import ANONYMOUS
390 from canonical.launchpad.webapp.interfaces import OAuthPermission
391 from canonical.launchpad.interfaces import (
392 IOAuthConsumerSet, IPersonSet)
393
394-from lp.testing._login import ANONYMOUS, login, logout
395+from lp.testing._login import login, logout
396
397 def oauth_access_token_for(consumer_name, person, permission, context=None):
398 """Find or create an OAuth access token for the given person.
399
400=== modified file 'scripts/bugzilla-import.py'
401--- scripts/bugzilla-import.py 2010-02-16 15:25:52 +0000
402+++ scripts/bugzilla-import.py 2010-04-17 00:32:28 +0000
403@@ -15,8 +15,7 @@
404 from canonical.lp import initZopeless
405 from canonical.launchpad.scripts import (
406 execute_zcml_for_scripts, logger_options, logger)
407-from canonical.launchpad.ftests import login
408-from canonical.launchpad.webapp.interaction import Participation
409+from canonical.launchpad.webapp.interaction import setupInteractionByEmail
410
411 from canonical.launchpad.scripts import bugzilla
412
413@@ -81,11 +80,7 @@
414
415 execute_zcml_for_scripts()
416 ztm = initZopeless()
417- # XXX gary 21-Oct-2008 bug 285808
418- # We should reconsider using a ftest helper for production code. For now,
419- # we explicitly keep the code from using a test request by using a basic
420- # participation.
421- login('bug-importer@launchpad.net', Participation())
422+ setupInteractionByEmail('bug-importer@launchpad.net')
423
424 db = make_connection(options)
425 bz = bugzilla.Bugzilla(db)
426
427=== modified file 'scripts/sourceforge-import.py'
428--- scripts/sourceforge-import.py 2010-02-16 15:25:52 +0000
429+++ scripts/sourceforge-import.py 2010-04-17 00:32:28 +0000
430@@ -16,8 +16,7 @@
431 from canonical.launchpad.interfaces import IProductSet
432 from canonical.launchpad.scripts import (
433 execute_zcml_for_scripts, logger_options, logger)
434-from canonical.launchpad.ftests import login
435-from canonical.launchpad.webapp.interaction import Participation
436+from canonical.launchpad.webapp.interaction import setupInteractionByEmail
437
438 from canonical.launchpad.scripts.sftracker import Tracker, TrackerImporter
439
440@@ -52,11 +51,7 @@
441
442 execute_zcml_for_scripts()
443 ztm = initZopeless()
444- # XXX gary 21-Oct-2008 bug 285808
445- # We should reconsider using a ftest helper for production code. For now,
446- # we explicitly keep the code from using a test request by using a basic
447- # participation.
448- login('bug-importer@launchpad.net', Participation())
449+ setupInteractionByEmail('bug-importer@launchpad.net')
450
451 product = getUtility(IProductSet).getByName(options.product)
452 tracker = Tracker(options.dumpfile, options.dumpdir)