Merge lp:~gmb/launchpad/include-bnl-bug-651108 into lp:launchpad

Proposed by Graham Binns on 2010-10-21
Status: Merged
Approved by: Brad Crittenden on 2010-10-22
Approved revision: 11683
Merge reported by: Graham Binns
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/include-bnl-bug-651108
Merge into: lp:launchpad
Prerequisite: lp:~gmb/launchpad/add-profiling-feature-flag
Diff against target: 739 lines (+233/-112)
9 files modified
lib/canonical/launchpad/doc/profiling.txt (+5/-21)
lib/lp/app/configure.zcml (+14/-0)
lib/lp/bugs/browser/bugsubscription.py (+59/-15)
lib/lp/bugs/browser/tests/test_bugsubscription_views.py (+65/-0)
lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions-advanced-features.txt (+42/-0)
lib/lp/services/features/testing.py (+0/-1)
lib/lp/services/memcache/doc/tales-cache.txt (+2/-11)
lib/lp/services/profile/profile.py (+2/-10)
lib/lp/services/profile/tests.py (+44/-54)
To merge this branch: bzr merge lp:~gmb/launchpad/include-bnl-bug-651108
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code 2010-10-21 Approve on 2010-10-22
Review via email: mp+39047@code.launchpad.net

Commit Message

A feature-flagged bug_notififcation_level field has been added to BugSubscriptionSubscribeSelfView.

Description of the Change

This branch adds a feature-flagged bug_notification_level field to BugSubscriptionSubscribeSelf View. This is the first step in allowing users to chose what level their direct subscriptions work at.

== lib/lp/bugs/browser/bugsubscription.py ==

 - I've added the bug_notification_level field and feature-flagged it.
 - I've updated the subscribe_action() and _handleSubscribe() methods to deal with bug_notification_levels correctly.

== lib/lp/bugs/browser/tests/test_bugsubscription_views.py ==

 - I've added a test to show that when the malone.advanced-subscriptions.enabled flag is turned on, the bug_notification_level field appears in the UI.

== lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions-advanced-features.txt ==

 - I've added a unit test to show that when the advanced subscriptions features are turned on the bug_notification_level field is used to create a subscription at the appropriate level.

To post a comment you must log in.
Brad Crittenden (bac) wrote :

Hi Graham,

Thanks for this branch. I find it particularly interesting as I've not yet done nor reviewed an feature flag branches.

Please run the super new utilities/update-copyright script before landing. I think it only updates the files you've touched, some of which need it.

At line 144 of browser/bugsubscription.py a local variable is assigned but not used. I wonder why lint doesn't catch it? Speaking of, there are some lint issues that appear to be real.

I find the multiple tests in setUpWidgets to be confusing. Please factor out the test for is_subscribed and nest the remaining tests. It should simplify things a bit.

This comment is confusing/misleading as it implies the user can toggle the features:

183 + # When a user subscribes to a bug using the advanced features on
184 + # the Bug +subscribe page, the bug notification level they...

I think this version is more readable and accurate:

183 + # When a user subscribes to a bug with the advanced features on
184 + # the Bug +subscribe page enabled, the bug notification level they...

It is odd the current diff in the MP is showing a conflict in tales-cache.txt but none exists.

review: Approve (code)
Brad Crittenden (bac) wrote :

Actually, I need to retract my approval because the diff I am shown in the MP is inaccurate. Merging your branch into devel I see a diff of 898 lines where the diff in the MP only shows 283 lines. So assuming mine is correct, I've only reviewed about a 1/4 of your changes.

review: Needs Information (code)
Brad Crittenden (bac) wrote :

OK, so I just failed to see the pre-req branch. This aspect of it looks good. You *do* have a conflict in the pre-req branch.

review: Approve (code)
Graham Binns (gmb) wrote :

On Friday, October 22, 2010, Brad Crittenden <email address hidden> wrote:
> Review: Approve code
> OK, so I just failed to see the pre-req branch.  This aspect of it looks good.  You *do* have a conflict in the pre-req branch.

Thaks for the review. I'll make the changes you suggest. Note that the
prerequisite branch is just a slight modification of one of mars'S
branches, so I may have to just extract the code I need (the test
helper) in order to be able to land this branch.

--
Graham Binns
http://grahambinns.com

11684. By Graham Binns on 2010-10-22

Review changes for brad.

11685. By Graham Binns on 2010-10-22

Reverted all but the FeatureFixture helper.

11686. By Graham Binns on 2010-10-22

De-linted.

11687. By Graham Binns on 2010-10-22

Slight code tweak.

11688. By Graham Binns on 2010-11-01

Merged mainline.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/doc/profiling.txt'
2--- lib/canonical/launchpad/doc/profiling.txt 2010-11-01 11:25:43 +0000
3+++ lib/canonical/launchpad/doc/profiling.txt 2010-08-26 15:57:56 +0000
4@@ -100,10 +100,11 @@
5
6 The feature has two modes.
7
8-- It can be configured to optionally profile requests. To turn this on you
9- must add a feature flag that sets the 'request_profiling' flag to 'on'.
10- This flag can be set directly in the database or using bin/harness. See
11- https://dev.launchpad.net/LEP/FeatureFlags for the details.
12+- It can be configured to optionally profile requests. To turn this on, in
13+ ``launchpad-lazr.conf`` (e.g.,
14+ ``configs/development/launchpad-lazr.conf``) , in the ``[profiling]``
15+ section, set ``profiling_allowed: True``. As of this writing, this
16+ is the default value for development.
17
18 Once it is turned on, you can insert /++profile++/ in the URL to get
19 basic instructions on how to use the feature. You use the
20@@ -122,19 +123,6 @@
21 lp/services/profile is hooked up properly. This is intended to be a
22 smoke test. The unit tests verify further functionality.
23
24- # XXX mars 2010-09-23
25- # We will temporarily turn on the profiling feature so that this test
26- # still passes. Profiling is currently controlled using feature flags
27- # instead of config values. This fixture can be removed once profiling for
28- # developers is active by default once again.
29- >>> from lp.services.features.testing import FeatureFixture
30- >>> fixture = FeatureFixture({'request_profiling': 'on'})
31- >>> fixture.setUp()
32-
33- >>> from lp.services.features import getFeatureFlag
34- >>> getFeatureFlag('request_profiling')
35- u'on'
36-
37 >>> response = http('GET /++profile++ HTTP/1.0')
38 >>> '<h1>Profiling Information</h1>' in response.getBody()
39 True
40@@ -226,7 +214,3 @@
41 >>> shutil.rmtree(pagetests_profile_dir)
42 >>> shutil.rmtree(profile_dir)
43 >>> old_config = config.pop('memory_profile')
44-
45-
46- # Turn profiling off
47- >>> fixture.cleanUp()
48
49=== added file 'lib/lp/app/configure.zcml'
50--- lib/lp/app/configure.zcml 1970-01-01 00:00:00 +0000
51+++ lib/lp/app/configure.zcml 2010-09-29 12:39:28 +0000
52@@ -0,0 +1,14 @@
53+<!-- Copyright 2009 Canonical Ltd. This software is licensed under the
54+ GNU Affero General Public License version 3 (see the file LICENSE).
55+-->
56+
57+<configure
58+ xmlns="http://namespaces.zope.org/zope"
59+ xmlns:browser="http://namespaces.zope.org/browser"
60+ xmlns:i18n="http://namespaces.zope.org/i18n"
61+ xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
62+ xmlns:lp="http://namespaces.canonical.com/lp"
63+ i18n_domain="launchpad">
64+ <include
65+ package=".browser"/>
66+</configure>
67
68=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
69--- lib/lp/bugs/browser/bugsubscription.py 2010-10-19 10:29:00 +0000
70+++ lib/lp/bugs/browser/bugsubscription.py 2010-11-01 11:25:46 +0000
71@@ -18,8 +18,6 @@
72 from zope import formlib
73 from zope.app.form import CustomWidgetFactory
74 from zope.app.form.browser.itemswidgets import RadioWidget
75-from zope.app.form.interfaces import IInputWidget
76-from zope.app.form.utility import setUpWidget
77 from zope.schema import Choice
78 from zope.schema.vocabulary import (
79 SimpleTerm,
80@@ -30,7 +28,6 @@
81 from canonical.launchpad.webapp import (
82 action,
83 canonical_url,
84- custom_widget,
85 LaunchpadFormView,
86 LaunchpadView,
87 )
88@@ -39,7 +36,6 @@
89 from canonical.launchpad.webapp.menu import structured
90 from lp.bugs.browser.bug import BugViewMixin
91 from lp.bugs.interfaces.bugsubscription import IBugSubscription
92-from lp.registry.enum import BugNotificationLevel
93 from lp.services import features
94 from lp.services.propertycache import cachedproperty
95
96@@ -85,10 +81,13 @@
97 """A view to handle the +subscribe page for a bug."""
98
99 schema = IBugSubscription
100- # XXX 2010-10-18 gmb bug=651108:
101- # field_names is currently empty because we don't use any of
102- # them. This will be amended in a subsequent branch.
103- field_names = []
104+
105+ @property
106+ def field_names(self):
107+ if self._use_advanced_features:
108+ return ['bug_notification_level']
109+ else:
110+ return []
111
112 @property
113 def next_url(self):
114@@ -102,7 +101,7 @@
115 # far as referrers are concerned so that we can handle privacy
116 # issues properly.
117 ignored_referrer_urls = (
118- 'localhost', self.request.getURL(), context_url)
119+ 'localhost', self.request.getURL(), context_url)
120 if referer and referer not in ignored_referrer_urls:
121 next_url = referer
122 elif self._redirecting_to_bug_list:
123@@ -112,7 +111,6 @@
124 return next_url
125
126 cancel_url = next_url
127- _redirecting_to_bug_list = False
128
129 @cachedproperty
130 def _subscribers_for_current_user(self):
131@@ -128,10 +126,21 @@
132 self._subscriber_count_for_current_user = person_count
133 return persons_for_user.values()
134
135+ @cachedproperty
136+ def _use_advanced_features(self):
137+ """Return True if advanced subscriptions features are enabled."""
138+ return features.getFeatureFlag(
139+ 'malone.advanced-subscriptions.enabled')
140+
141+ def initialize(self):
142+ """See `LaunchpadFormView`."""
143+ self._subscriber_count_for_current_user = 0
144+ self._redirecting_to_bug_list = False
145+ super(BugSubscriptionSubscribeSelfView, self).initialize()
146+
147 def setUpFields(self):
148 """See `LaunchpadFormView`."""
149 super(BugSubscriptionSubscribeSelfView, self).setUpFields()
150- bug = self.context.bug
151 if self.user is None:
152 return
153
154@@ -162,10 +171,41 @@
155 __name__='subscription', title=_("Subscription options"),
156 vocabulary=subscription_vocabulary, required=True,
157 default=default_subscription_value)
158- self.form_fields += formlib.form.Fields(subscription_field)
159+
160+ if self._use_advanced_features:
161+ # We need to pop the bug_notification_level field out of
162+ # form_fields so that we can put the subscription_field first in
163+ # the list. formlib.form.Fields doesn't have an insert() method.
164+ bug_notification_level_field = self.form_fields.select(
165+ 'bug_notification_level')
166+ self.form_fields = self.form_fields.omit('bug_notification_level')
167+ self.form_fields += formlib.form.Fields(subscription_field)
168+ self.form_fields += bug_notification_level_field
169+ self.form_fields['bug_notification_level'].custom_widget = (
170+ CustomWidgetFactory(RadioWidget))
171+ else:
172+ self.form_fields += formlib.form.Fields(subscription_field)
173 self.form_fields['subscription'].custom_widget = CustomWidgetFactory(
174 RadioWidget)
175
176+ def setUpWidgets(self):
177+ """See `LaunchpadFormView`."""
178+ super(BugSubscriptionSubscribeSelfView, self).setUpWidgets()
179+ if self._use_advanced_features:
180+ if self.user_is_subscribed:
181+ self.widgets['bug_notification_level'].visible = False
182+ else:
183+ if self._subscriber_count_for_current_user == 0:
184+ # We hide the subscription widget if the user isn't
185+ # subscribed, since we know who the subscriber is and we
186+ # don't need to present them with a single radio button.
187+ self.widgets['subscription'].visible = False
188+ else:
189+ # We show the subscription widget when the user is
190+ # subscribed via a team, because they can either
191+ # subscribe theirself or unsubscribe their team.
192+ self.widgets['subscription'].visible = True
193+
194 @cachedproperty
195 def user_is_subscribed(self):
196 """Is the user subscribed to this bug?"""
197@@ -195,14 +235,18 @@
198 subscription_person = self.widgets['subscription'].getInputValue()
199 if (not self.user_is_subscribed and
200 (subscription_person == self.user)):
201- self._handleSubscribe()
202+ if self._use_advanced_features:
203+ bug_notification_level = data['bug_notification_level']
204+ else:
205+ bug_notification_level = None
206+ self._handleSubscribe(bug_notification_level)
207 else:
208 self._handleUnsubscribe(subscription_person)
209 self.request.response.redirect(self.next_url)
210
211- def _handleSubscribe(self):
212+ def _handleSubscribe(self, level=None):
213 """Handle a subscribe request."""
214- self.context.bug.subscribe(self.user, self.user)
215+ self.context.bug.subscribe(self.user, self.user, level=level)
216 self.request.response.addNotification(
217 "You have been subscribed to this bug.")
218
219
220=== added file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
221--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 1970-01-01 00:00:00 +0000
222+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2010-11-01 11:25:46 +0000
223@@ -0,0 +1,65 @@
224+# Copyright 2010 Canonical Ltd. This software is licensed under the
225+# GNU Affero General Public License version 3 (see the file LICENSE).
226+
227+"""Tests for BugSubscription views."""
228+
229+__metaclass__ = type
230+
231+from storm.store import Store
232+
233+from canonical.launchpad.ftests import LaunchpadFormHarness
234+from canonical.testing.layers import LaunchpadFunctionalLayer
235+
236+from lp.bugs.browser.bugsubscription import BugSubscriptionSubscribeSelfView
237+from lp.bugs.model.bugsubscription import BugSubscription
238+from lp.registry.enum import BugNotificationLevel
239+from lp.services.features.testing import FeatureFixture
240+from lp.testing import person_logged_in, TestCaseWithFactory
241+
242+
243+class BugSubscriptionAdvancedFeaturesTestCase(TestCaseWithFactory):
244+
245+ layer = LaunchpadFunctionalLayer
246+
247+ def setUp(self):
248+ super(BugSubscriptionAdvancedFeaturesTestCase, self).setUp()
249+ self.useFixture(
250+ FeatureFixture({
251+ 'malone.advanced-subscriptions.enabled': 'on'}))
252+
253+ def _getBugSubscriptionForUserAndBug(self, user, bug):
254+ """Return the BugSubscription for a given user, bug combination."""
255+ store = Store.of(bug)
256+ return store.find(
257+ BugSubscription,
258+ BugSubscription.person == user,
259+ BugSubscription.bug == bug).one()
260+
261+ def test_subscribe_uses_bug_notification_level(self):
262+ # When a user subscribes to a bug using the advanced features on
263+ # the Bug +subscribe page, the bug notification level they
264+ # choose is taken into account.
265+ bug = self.factory.makeBug()
266+ # We unsubscribe the bug's owner because if we don't there will
267+ # be two COMMENTS-level subscribers.
268+ with person_logged_in(bug.owner):
269+ bug.unsubscribe(bug.owner, bug.owner)
270+
271+ for level in BugNotificationLevel.items:
272+ person = self.factory.makePerson()
273+ with person_logged_in(person):
274+ harness = LaunchpadFormHarness(
275+ bug.default_bugtask, BugSubscriptionSubscribeSelfView)
276+ form_data = {
277+ 'field.subscription': person.name,
278+ 'field.bug_notification_level': level.name,
279+ }
280+ harness.submit('continue', form_data)
281+
282+ subscription = self._getBugSubscriptionForUserAndBug(
283+ person, bug)
284+ self.assertEqual(
285+ level, subscription.bug_notification_level,
286+ "Bug notification level of subscription should be %s, is "
287+ "actually %s." % (
288+ level.name, subscription.bug_notification_level.name))
289
290=== added file 'lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions-advanced-features.txt'
291--- lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions-advanced-features.txt 1970-01-01 00:00:00 +0000
292+++ lib/lp/bugs/stories/bugs/xx-bug-personal-subscriptions-advanced-features.txt 2010-11-01 11:25:46 +0000
293@@ -0,0 +1,42 @@
294+Advanced personal subscriptions
295+-------------------------------
296+
297+There are advanced features for bug subscriptions. These require a
298+feature flag to be turned on for them to be accessible.
299+
300+ >>> from lp.services.features.model import FeatureFlag, getFeatureStore
301+ >>> feature_store = getFeatureStore()
302+ >>> ignore = feature_store.add(FeatureFlag(
303+ ... scope=u'default',
304+ ... flag=u'malone.advanced-subscriptions.enabled',
305+ ... value=u'on', priority=1))
306+ >>> feature_store.flush()
307+
308+Now when a user visits the +subscribe page of a bug they are given the
309+option to subscribe to the bug at a given BugNotificationLevel.
310+
311+ >>> from canonical.launchpad.webapp import canonical_url
312+ >>> from lp.testing.sampledata import USER_EMAIL
313+ >>> login(USER_EMAIL)
314+ >>> bug = factory.makeBug()
315+ >>> logout()
316+ >>> user_browser.open(
317+ ... canonical_url(bug.default_bugtask, view_name='+subscribe'))
318+ >>> bug_notification_level_control = user_browser.getControl(
319+ ... name='field.bug_notification_level')
320+ >>> for control in bug_notification_level_control.controls:
321+ ... print control.optionValue
322+ NOTHING
323+ LIFECYCLE
324+ METADATA
325+ COMMENTS
326+
327+The user can now subscribe to the bug at any of the given notification
328+levels. In this case, they want to subscribe to just metadata updates:
329+
330+ >>> bug_notification_level_control.getControl('Details').click()
331+ >>> user_browser.getControl('Continue').click()
332+
333+ >>> for message in find_tags_by_class(user_browser.contents, 'message'):
334+ ... print extract_text(message)
335+ You have been subscribed to this bug.
336
337=== modified file 'lib/lp/services/features/testing.py'
338--- lib/lp/services/features/testing.py 2010-11-01 11:25:43 +0000
339+++ lib/lp/services/features/testing.py 2010-11-01 11:25:46 +0000
340@@ -66,4 +66,3 @@
341 controller = FeatureController(lambda _: True, rule_source)
342 per_thread.features = controller
343 self.addCleanup(setattr, per_thread, 'features', original_controller)
344-
345
346=== modified file 'lib/lp/services/memcache/doc/tales-cache.txt'
347--- lib/lp/services/memcache/doc/tales-cache.txt 2010-11-01 11:25:43 +0000
348+++ lib/lp/services/memcache/doc/tales-cache.txt 2010-11-01 11:25:46 +0000
349@@ -348,23 +348,17 @@
350 Memcache in templates can be disabled entirely by setting the memcache flag to
351 'disabled'.
352
353-<<<<<<< TREE
354 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
355 >>> from lp.services.features.model import FeatureFlag, getFeatureStore
356+ >>> from lp.services.features.webapp import ScopesFromRequest
357+ >>> from lp.services.features.flags import FeatureController
358 >>> from lp.services.features import per_thread
359- >>> from lp.services.features.flags import FeatureController
360- >>> from lp.services.features.webapp import ScopesFromRequest
361 >>> ignore = getFeatureStore().add(FeatureFlag(
362 ... scope=u'default', flag=u'memcache', value=u'disabled',
363 ... priority=1))
364 >>> empty_request = LaunchpadTestRequest()
365 >>> per_thread.features = FeatureController(
366 ... ScopesFromRequest(empty_request).lookup)
367-=======
368- >>> from lp.services.features.testing import FeatureFixture
369- >>> fixture = FeatureFixture({'memcache': 'disabled'})
370- >>> fixture.setUp()
371->>>>>>> MERGE-SOURCE
372
373 And now what cached before will not cache.
374
375@@ -381,6 +375,3 @@
376 <div>
377 <span>second</span>
378 </div>
379-
380- # Clean up our custom flag settings.
381- >>> fixture.cleanUp()
382
383=== modified file 'lib/lp/services/profile/profile.py'
384--- lib/lp/services/profile/profile.py 2010-11-01 11:25:43 +0000
385+++ lib/lp/services/profile/profile.py 2010-08-27 14:20:28 +0000
386@@ -30,15 +30,12 @@
387 memory,
388 resident,
389 )
390-from lp.services import features
391
392
393 class ProfilingOops(Exception):
394 """Fake exception used to log OOPS information when profiling pages."""
395
396
397-# This variable holds all of the data about an active profile run for the
398-# duration of the request.
399 _profilers = threading.local()
400
401
402@@ -49,13 +46,8 @@
403 If profiling is enabled, start a profiler for this thread. If memory
404 profiling is requested, save the VSS and RSS.
405 """
406- if features.getFeatureFlag('request_profiling'):
407- # Set a flag so that the end_request hook knows that is has some work
408- # to do.
409- _profilers.active = True
410- else:
411+ if not config.profiling.profiling_allowed:
412 return
413-
414 actions = get_desired_profile_actions(event.request)
415 if config.profiling.profile_all_requests:
416 actions.add('log')
417@@ -79,7 +71,7 @@
418 @adapter(IEndRequestEvent)
419 def end_request(event):
420 """If profiling is turned on, save profile data for the request."""
421- if not getattr(_profilers, 'active', False):
422+ if not config.profiling.profiling_allowed:
423 return
424 try:
425 actions = _profilers.actions
426
427=== modified file 'lib/lp/services/profile/tests.py'
428--- lib/lp/services/profile/tests.py 2010-11-01 11:25:43 +0000
429+++ lib/lp/services/profile/tests.py 2010-08-26 22:10:56 +0000
430@@ -14,6 +14,7 @@
431 import time
432 import unittest
433
434+from lp.testing import TestCase
435 from bzrlib.lsprof import BzrProfiler
436 from zope.app.publication.interfaces import EndRequestEvent
437 from zope.component import getSiteManager
438@@ -25,11 +26,7 @@
439 )
440 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
441 from canonical.launchpad.webapp.interfaces import StartRequestEvent
442-from canonical.testing import layers
443 from lp.services.profile import profile
444-from lp.services.features.testing import FeatureFixture
445-from lp.testing import TestCase
446-
447
448 EXAMPLE_HTML_START = '''\
449 <html><head><title>Random!</title></head>
450@@ -46,8 +43,6 @@
451
452 class BaseTest(TestCase):
453
454- layer = layers.DatabaseFunctionalLayer
455-
456 def _get_request(self, path='/'):
457 """Return a test request for the given path."""
458 return LaunchpadTestRequest(PATH_INFO=path)
459@@ -63,19 +58,13 @@
460 getattr(profile._profilers, name, None), None,
461 'Profiler state (%s) is dirty; %s.' % (name, message))
462
463- def turnOnProfiling(self):
464- """Turn on the request_profiling feature flag."""
465- self.useFixture(FeatureFixture({'request_profiling': 'on'}))
466-
467- def turnOffProfiling(self):
468- """Turn off the request_profiling feature flag."""
469- self.useFixture(FeatureFixture({'request_profiling': None}))
470-
471 def pushProfilingConfig(
472- self, profile_all_requests='False', memory_profile_log=''):
473+ self, profiling_allowed='False', profile_all_requests='False',
474+ memory_profile_log=''):
475 """This is a convenience for setting profile configs."""
476 self.pushConfig(
477 'profiling',
478+ profiling_allowed=profiling_allowed,
479 profile_all_requests=profile_all_requests,
480 memory_profile_log=memory_profile_log)
481
482@@ -97,12 +86,11 @@
483 delattr(profile._profilers, name)
484 TestCase.tearDown(self)
485
486- def test_feature_flag_stops_profiling(self):
487- """The ``request_profiling`` feature flag should disable all
488+ def test_config_stops_profiling(self):
489+ """The ``profiling_allowed`` configuration should disable all
490 profiling, even if it is requested"""
491- self.turnOffProfiling()
492 self.pushProfilingConfig(
493- profile_all_requests='True',
494+ profiling_allowed='False', profile_all_requests='True',
495 memory_profile_log='.')
496 profile.start_request(self._get_start_event('/++profile++show,log'))
497 self.assertCleanProfilerState('config was ignored')
498@@ -110,7 +98,7 @@
499 def test_optional_profiling_without_marked_request_has_no_profile(self):
500 """Even if profiling is allowed, it does not happen with a normal
501 request."""
502- self.turnOnProfiling()
503+ self.pushProfilingConfig(profiling_allowed='True')
504 profile.start_request(self._get_start_event('/'))
505 self.assertEqual(profile._profilers.actions, set())
506 self.assertIs(getattr(profile._profilers, 'profiler', None), None)
507@@ -120,7 +108,7 @@
508 def test_optional_profiling_with_show_request_starts_profiling(self):
509 """If profiling is allowed and a request with the "show" marker
510 URL segment is made, profiling starts."""
511- self.turnOnProfiling()
512+ self.pushProfilingConfig(profiling_allowed='True')
513 profile.start_request(self._get_start_event('/++profile++show/'))
514 self.assertIsInstance(profile._profilers.profiler, BzrProfiler)
515 self.assertIs(
516@@ -131,7 +119,7 @@
517 def test_optional_profiling_with_log_request_starts_profiling(self):
518 """If profiling is allowed and a request with the "log" marker
519 URL segment is made, profiling starts."""
520- self.turnOnProfiling()
521+ self.pushProfilingConfig(profiling_allowed='True')
522 profile.start_request(self._get_start_event('/++profile++log/'))
523 self.assertIsInstance(profile._profilers.profiler, BzrProfiler)
524 self.assertIs(
525@@ -142,7 +130,7 @@
526 def test_optional_profiling_with_combined_request_starts_profiling(self):
527 """If profiling is allowed and a request with the "log" and
528 "show" marker URL segment is made, profiling starts."""
529- self.turnOnProfiling()
530+ self.pushProfilingConfig(profiling_allowed='True')
531 profile.start_request(self._get_start_event('/++profile++log,show/'))
532 self.assertIsInstance(profile._profilers.profiler, BzrProfiler)
533 self.assertIs(
534@@ -153,7 +141,7 @@
535 def test_optional_profiling_with_reversed_request_starts_profiling(self):
536 """If profiling is allowed and a request with the "show" and
537 "log" marker URL segment is made, profiling starts."""
538- self.turnOnProfiling()
539+ self.pushProfilingConfig(profiling_allowed='True')
540 # The fact that this is reversed from the previous request is the only
541 # difference from the previous test. Also, it doesn't have a
542 # trailing slash. :-P
543@@ -166,8 +154,8 @@
544
545 def test_forced_profiling_registers_action(self):
546 """profile_all_requests should register as a log action"""
547- self.turnOnProfiling()
548- self.pushProfilingConfig(profile_all_requests='True')
549+ self.pushProfilingConfig(
550+ profiling_allowed='True', profile_all_requests='True')
551 profile.start_request(self._get_start_event('/'))
552 self.assertIsInstance(profile._profilers.profiler, BzrProfiler)
553 self.assertIs(
554@@ -179,7 +167,7 @@
555 """If profiling is allowed and a request with the marker URL segment
556 is made incorrectly, profiling does not start and help is an action.
557 """
558- self.turnOnProfiling()
559+ self.pushProfilingConfig(profiling_allowed='True')
560 profile.start_request(self._get_start_event('/++profile++/'))
561 self.assertIs(getattr(profile._profilers, 'profiler', None), None)
562 self.assertIs(
563@@ -191,8 +179,8 @@
564 """If profiling is forced and a request with the marker URL segment
565 is made incorrectly, profiling starts and help is an action.
566 """
567- self.turnOnProfiling()
568- self.pushProfilingConfig(profile_all_requests='True')
569+ self.pushProfilingConfig(
570+ profiling_allowed='True', profile_all_requests='True')
571 profile.start_request(self._get_start_event('/++profile++/'))
572 self.assertIsInstance(profile._profilers.profiler, BzrProfiler)
573 self.assertIs(
574@@ -201,8 +189,8 @@
575 self.assertEquals(profile._profilers.actions, set(('help', 'log')))
576
577 def test_memory_profile_start(self):
578- self.turnOnProfiling()
579- self.pushProfilingConfig(memory_profile_log='.')
580+ self.pushProfilingConfig(
581+ profiling_allowed='True', memory_profile_log='.')
582 profile.start_request(self._get_start_event('/'))
583 self.assertIs(getattr(profile._profilers, 'profiler', None), None)
584 self.assertIsInstance(profile._profilers.memory_profile_start, tuple)
585@@ -210,8 +198,8 @@
586 self.assertEqual(profile._profilers.actions, set())
587
588 def test_combo_memory_and_profile_start(self):
589- self.turnOnProfiling()
590- self.pushProfilingConfig(memory_profile_log='.')
591+ self.pushProfilingConfig(
592+ profiling_allowed='True', memory_profile_log='.')
593 profile.start_request(self._get_start_event('/++profile++show/'))
594 self.assertIsInstance(profile._profilers.profiler, BzrProfiler)
595 self.assertIsInstance(profile._profilers.memory_profile_start, tuple)
596@@ -279,11 +267,10 @@
597 #########################################################################
598 # Tests
599 def test_config_stops_profiling(self):
600- """The ``request_profiling`` feature should disable all
601+ """The ``profiling_allowed`` configuration should disable all
602 profiling, even if it is requested"""
603- self.turnOffProfiling()
604 self.pushProfilingConfig(
605- profile_all_requests='True',
606+ profiling_allowed='False', profile_all_requests='True',
607 memory_profile_log=self.memory_profile_log)
608 request = self.endRequest('/++profile++show,log')
609 self.assertIs(getattr(request, 'oops', None), None)
610@@ -295,7 +282,7 @@
611 def test_optional_profiling_without_marked_request_has_no_profile(self):
612 """Even if profiling is allowed, it does not happen with a normal
613 request."""
614- self.turnOnProfiling()
615+ self.pushProfilingConfig(profiling_allowed='True')
616 request = self.endRequest('/')
617 self.assertIs(getattr(request, 'oops', None), None)
618 self.assertEqual(self.getAddedResponse(request), '')
619@@ -306,7 +293,7 @@
620 def test_optional_profiling_with_show_request_profiles(self):
621 """If profiling is allowed and a request with the "show" marker
622 URL segment is made, profiling starts."""
623- self.turnOnProfiling()
624+ self.pushProfilingConfig(profiling_allowed='True')
625 request = self.endRequest('/++profile++show/')
626 self.assertIsInstance(request.oops, ErrorReport)
627 self.assertIn('Top Inline Time', self.getAddedResponse(request))
628@@ -317,7 +304,7 @@
629 def test_optional_profiling_with_log_request_profiles(self):
630 """If profiling is allowed and a request with the "log" marker
631 URL segment is made, profiling starts."""
632- self.turnOnProfiling()
633+ self.pushProfilingConfig(profiling_allowed='True')
634 request = self.endRequest('/++profile++log/')
635 self.assertIsInstance(request.oops, ErrorReport)
636 response = self.getAddedResponse(request)
637@@ -332,7 +319,7 @@
638 def test_optional_profiling_with_combined_request_profiles(self):
639 """If profiling is allowed and a request with the "log" and
640 "show" marker URL segment is made, profiling starts."""
641- self.turnOnProfiling()
642+ self.pushProfilingConfig(profiling_allowed='True')
643 request = self.endRequest('/++profile++log,show')
644 self.assertIsInstance(request.oops, ErrorReport)
645 response = self.getAddedResponse(request)
646@@ -346,8 +333,8 @@
647
648 def test_forced_profiling_logs(self):
649 """profile_all_requests should register as a log action"""
650- self.turnOnProfiling()
651- self.pushProfilingConfig(profile_all_requests='True')
652+ self.pushProfilingConfig(
653+ profiling_allowed='True', profile_all_requests='True')
654 request = self.endRequest('/')
655 self.assertIsInstance(request.oops, ErrorReport)
656 response = self.getAddedResponse(request)
657@@ -364,7 +351,7 @@
658 """If profiling is allowed and a request with the marker URL segment
659 is made incorrectly, profiling does not start and help is an action.
660 """
661- self.turnOnProfiling()
662+ self.pushProfilingConfig(profiling_allowed='True')
663 request = self.endRequest('/++profile++')
664 self.assertIs(getattr(request, 'oops', None), None)
665 response = self.getAddedResponse(request)
666@@ -378,8 +365,8 @@
667 """If profiling is forced and a request with the marker URL segment
668 is made incorrectly, profiling starts and help is an action.
669 """
670- self.turnOnProfiling()
671- self.pushProfilingConfig(profile_all_requests='True')
672+ self.pushProfilingConfig(
673+ profiling_allowed='True', profile_all_requests='True')
674 request = self.endRequest('/++profile++')
675 self.assertIsInstance(request.oops, ErrorReport)
676 response = self.getAddedResponse(request)
677@@ -395,8 +382,9 @@
678
679 def test_memory_profile(self):
680 "Does the memory profile work?"
681- self.turnOnProfiling()
682- self.pushProfilingConfig(memory_profile_log=self.memory_profile_log)
683+ self.pushProfilingConfig(
684+ profiling_allowed='True',
685+ memory_profile_log=self.memory_profile_log)
686 request = self.endRequest('/')
687 self.assertIs(getattr(request, 'oops', None), None)
688 self.assertEqual(self.getAddedResponse(request), '')
689@@ -412,8 +400,9 @@
690
691 def test_memory_profile_with_non_defaults(self):
692 "Does the memory profile work with an oops and pageid?"
693- self.turnOnProfiling()
694- self.pushProfilingConfig(memory_profile_log=self.memory_profile_log)
695+ self.pushProfilingConfig(
696+ profiling_allowed='True',
697+ memory_profile_log=self.memory_profile_log)
698 request = self.endRequest('/++profile++show/no-such-file',
699 KeyError(), pageid='Foo')
700 log = self.getMemoryLog()
701@@ -424,8 +413,9 @@
702 self.assertCleanProfilerState()
703
704 def test_combo_memory_and_profile(self):
705- self.turnOnProfiling()
706- self.pushProfilingConfig(memory_profile_log=self.memory_profile_log)
707+ self.pushProfilingConfig(
708+ profiling_allowed='True',
709+ memory_profile_log=self.memory_profile_log)
710 request = self.endRequest('/++profile++show/')
711 self.assertIsInstance(request.oops, ErrorReport)
712 self.assertIn('Top Inline Time', self.getAddedResponse(request))
713@@ -434,7 +424,7 @@
714 self.assertCleanProfilerState()
715
716 def test_profiling_oops_is_informational(self):
717- self.turnOnProfiling()
718+ self.pushProfilingConfig(profiling_allowed='True')
719 request = self.endRequest('/++profile++show/')
720 response = self.getAddedResponse(request)
721 self.assertIsInstance(request.oops, ErrorReport)
722@@ -443,7 +433,7 @@
723 self.assertCleanProfilerState()
724
725 def test_real_oops_trumps_profiling_oops(self):
726- self.turnOnProfiling()
727+ self.pushProfilingConfig(profiling_allowed='True')
728 request = self.endRequest('/++profile++show/no-such-file',
729 KeyError('foo'))
730 self.assertIsInstance(request.oops, ErrorReport)
731@@ -454,7 +444,7 @@
732 self.assertCleanProfilerState()
733
734 def test_oopsid_is_in_profile_filename(self):
735- self.turnOnProfiling()
736+ self.pushProfilingConfig(profiling_allowed='True')
737 request = self.endRequest('/++profile++log/')
738 self.assertIn("-" + request.oopsid + "-", self.getProfilePaths()[0])
739 self.assertCleanProfilerState()