Merge lp:~cjwatson/launchpad/remove-txlongpoll into lp:launchpad
- remove-txlongpoll
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18930 |
Proposed branch: | lp:~cjwatson/launchpad/remove-txlongpoll |
Merge into: | lp:launchpad |
Diff against target: |
2646 lines (+12/-2250) 36 files modified
Makefile (+2/-2) configs/development/launchpad-lazr.conf (+0/-5) configs/development/local-launchpad-apache (+0/-1) configs/testrunner/launchpad-lazr.conf (+0/-5) constraints.txt (+0/-2) database/sampledata/current-dev.sql (+1/-3) lib/lp/app/javascript/longpoll.js (+0/-217) lib/lp/app/javascript/tests/test_longpoll.html (+0/-47) lib/lp/app/javascript/tests/test_longpoll.js (+0/-283) lib/lp/app/longpoll/__init__.py (+0/-50) lib/lp/app/longpoll/tests/test_longpoll.py (+0/-121) lib/lp/app/templates/base-layout-macros.pt (+1/-9) lib/lp/code/browser/branchmergeproposal.py (+1/-5) lib/lp/code/browser/tests/test_branchmergeproposal.py (+1/-35) lib/lp/code/javascript/branchmergeproposal.updater.js (+0/-263) lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html (+0/-98) lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js (+0/-162) lib/lp/code/templates/branchmergeproposal-index.pt (+2/-22) lib/lp/scripts/runlaunchpad.py (+1/-25) lib/lp/scripts/tests/test_runlaunchpad.py (+1/-5) lib/lp/services/config/schema-lazr.conf (+0/-10) lib/lp/services/configure.zcml (+1/-2) lib/lp/services/features/flags.py (+1/-8) lib/lp/services/longpoll/adapters/event.py (+0/-71) lib/lp/services/longpoll/adapters/storm.py (+0/-116) lib/lp/services/longpoll/adapters/subscriber.py (+0/-58) lib/lp/services/longpoll/adapters/tests/test_event.py (+0/-78) lib/lp/services/longpoll/adapters/tests/test_storm.py (+0/-170) lib/lp/services/longpoll/adapters/tests/test_subscriber.py (+0/-137) lib/lp/services/longpoll/configure.zcml (+0/-15) lib/lp/services/longpoll/interfaces.py (+0/-64) lib/lp/services/longpoll/testing.py (+0/-57) lib/lp/services/longpoll/tests/test_interfaces.py (+0/-33) lib/lp/services/txlongpoll/server.py (+0/-31) lib/lp/services/txlongpoll/tests/test_server.py (+0/-38) setup.py (+0/-2) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/remove-txlongpoll |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+366122@code.launchpad.net |
Commit message
Remove txlongpoll.
Description of the change
The Launchpad integration for this was never fully rolled out, is broken in some hard-to-fix ways, and is unlikely to be fixed.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' |
2 | --- Makefile 2018-06-06 12:46:56 +0000 |
3 | +++ Makefile 2019-04-16 14:35:10 +0000 |
4 | @@ -284,7 +284,7 @@ |
5 | bin/test -f $(TESTFLAGS) $(TESTOPTS) |
6 | |
7 | run: build inplace stop |
8 | - bin/run -r librarian,bing-webservice,memcached,rabbitmq,txlongpoll \ |
9 | + bin/run -r librarian,bing-webservice,memcached,rabbitmq \ |
10 | -i $(LPCONFIG) |
11 | |
12 | run-testapp: LPCONFIG=testrunner-appserver |
13 | @@ -303,7 +303,7 @@ |
14 | run_all: build inplace stop |
15 | bin/run \ |
16 | -r librarian,sftp,forker,mailman,codebrowse,bing-webservice,\ |
17 | - memcached,rabbitmq,txlongpoll -i $(LPCONFIG) |
18 | + memcached,rabbitmq -i $(LPCONFIG) |
19 | |
20 | run_codebrowse: compile |
21 | BZR_PLUGIN_PATH=bzrplugins $(PY) scripts/start-loggerhead.py |
22 | |
23 | === modified file 'configs/development/launchpad-lazr.conf' |
24 | --- configs/development/launchpad-lazr.conf 2018-06-14 16:28:45 +0000 |
25 | +++ configs/development/launchpad-lazr.conf 2019-04-16 14:35:10 +0000 |
26 | @@ -193,11 +193,6 @@ |
27 | store_search_url: https://api.snapcraft.io/ |
28 | tools_source: deb http://ppa.launchpad.net/snappy-dev/snapcraft-daily/ubuntu %(series)s main |
29 | |
30 | -[txlongpoll] |
31 | -launch: True |
32 | -frontend_port: 22435 |
33 | -uri: /+longpoll/ |
34 | - |
35 | [rosetta] |
36 | global_suggestions_enabled: True |
37 | generate_templates: True |
38 | |
39 | === modified file 'configs/development/local-launchpad-apache' |
40 | --- configs/development/local-launchpad-apache 2017-01-10 17:26:29 +0000 |
41 | +++ configs/development/local-launchpad-apache 2019-04-16 14:35:10 +0000 |
42 | @@ -32,7 +32,6 @@ |
43 | SSLCertificateKeyFile /etc/apache2/ssl/launchpad.key |
44 | |
45 | ProxyPreserveHost on |
46 | - ProxyPass /+longpoll/ http://localhost:22435/ retry=1 |
47 | ProxyPass /+combo ! |
48 | ProxyPass / http://localhost:8086/ retry=1 |
49 | |
50 | |
51 | === modified file 'configs/testrunner/launchpad-lazr.conf' |
52 | --- configs/testrunner/launchpad-lazr.conf 2018-05-22 07:53:52 +0000 |
53 | +++ configs/testrunner/launchpad-lazr.conf 2019-04-16 14:35:10 +0000 |
54 | @@ -164,11 +164,6 @@ |
55 | password: none |
56 | virtual_host: none |
57 | |
58 | -[txlongpoll] |
59 | -launch: False |
60 | -frontend_port: none |
61 | -uri: none |
62 | - |
63 | [rosetta] |
64 | generate_templates: True |
65 | |
66 | |
67 | === modified file 'constraints.txt' |
68 | --- constraints.txt 2019-04-08 07:13:18 +0000 |
69 | +++ constraints.txt 2019-04-16 14:35:10 +0000 |
70 | @@ -365,8 +365,6 @@ |
71 | Twisted[conch,tls]==18.4.0 |
72 | txAMQP==0.6.2 |
73 | txfixtures==0.4.2 |
74 | -txlongpoll==0.2.12 |
75 | -txlongpollfixture==0.1.3 |
76 | txpkgupload==0.2 |
77 | typing==3.6.2 |
78 | unittest2==1.1.0 |
79 | |
80 | === modified file 'database/sampledata/current-dev.sql' |
81 | --- database/sampledata/current-dev.sql 2017-12-15 12:15:25 +0000 |
82 | +++ database/sampledata/current-dev.sql 2019-04-16 14:35:10 +0000 |
83 | @@ -1,4 +1,4 @@ |
84 | --- Copyright 2010-2017 Canonical Ltd. This software is licensed under the |
85 | +-- Copyright 2010-2019 Canonical Ltd. This software is licensed under the |
86 | -- GNU Affero General Public License version 3 (see the file LICENSE). |
87 | -- Created using pg_dump (PostgreSQL) 9.3.5 |
88 | |
89 | @@ -3825,7 +3825,6 @@ |
90 | ALTER TABLE featureflag DISABLE TRIGGER ALL; |
91 | |
92 | INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 0, 'js.combo_loader.enabled', 'true', '2012-05-18 07:34:39.239649'); |
93 | -INSERT INTO featureflag (scope, priority, flag, value, date_modified) VALUES ('default', 1, 'longpoll.merge_proposals.enabled', 'true', '2012-05-18 07:34:39.239649'); |
94 | |
95 | |
96 | ALTER TABLE featureflag ENABLE TRIGGER ALL; |
97 | @@ -3833,7 +3832,6 @@ |
98 | |
99 | ALTER TABLE featureflagchangelogentry DISABLE TRIGGER ALL; |
100 | |
101 | -INSERT INTO featureflagchangelogentry (id, date_changed, diff, comment, person) VALUES (1, '2011-10-06 12:44:04.37357', '+longpoll.merge_proposals.enabled default 1 true', 'Enable long-poll for merge proposals in development.', 16); |
102 | |
103 | |
104 | ALTER TABLE featureflagchangelogentry ENABLE TRIGGER ALL; |
105 | |
106 | === removed file 'lib/lp/app/javascript/longpoll.js' |
107 | --- lib/lp/app/javascript/longpoll.js 2017-07-20 13:29:41 +0000 |
108 | +++ lib/lp/app/javascript/longpoll.js 1970-01-01 00:00:00 +0000 |
109 | @@ -1,217 +0,0 @@ |
110 | -/* Copyright 2011 Canonical Ltd. This software is licensed under the |
111 | - * GNU Affero General Public License version 3 (see the file LICENSE). |
112 | - * |
113 | - * The Launchpad Longpoll module provides the functionnality to deal |
114 | - * with longpolling on the JavaScript side. |
115 | - * |
116 | - * The module method setupLongPollManager is called in every template and |
117 | - * the long poll machinery will only be started if LP.cache.longpoll |
118 | - * is populated. |
119 | - * |
120 | - * Usually the only thing you will want to do to use the long polling feature |
121 | - * is: |
122 | - * |
123 | - * a) to make sure LP.cache.longpoll is populated with 'key' and 'uri'. |
124 | - * |
125 | - * b) to create Javascript handlers for the events which will be fired: |
126 | - * - event_key will be fired when the event on the server side is triggered |
127 | - * (event_key being the name of the event on the server side). |
128 | - * - see below for other events fired by the longpoll machinery. |
129 | - * |
130 | - * @module longpoll |
131 | - */ |
132 | -YUI.add('lp.app.longpoll', function(Y) { |
133 | - |
134 | -var namespace = Y.namespace('lp.app.longpoll'); |
135 | - |
136 | -// Event fired when the long polling request starts. |
137 | -namespace.longpoll_start_event = 'lp.app.longpoll.start'; |
138 | - |
139 | -// Event fired each time the long polling request fails (to connect or |
140 | -// to parse the returned result). |
141 | -namespace.longpoll_fail_event = 'lp.app.longpoll.failure'; |
142 | - |
143 | -// Event fired when the delay between each failed connection is set to |
144 | -// a long delay (after MAX_SHORT_DELAY_FAILED_ATTEMPTS failed attempts). |
145 | -namespace.longpoll_longdelay = 'lp.app.longpoll.longdelay'; |
146 | - |
147 | -// Event fired when the delay between each failed connection is set back |
148 | -// to a short delay. |
149 | -namespace.longpoll_shortdelay = 'lp.app.longpoll.shortdelay'; |
150 | - |
151 | -namespace._manager = null; |
152 | - |
153 | -// After MAX_SHORT_DELAY_FAILED_ATTEMPTS failed connections (real failed |
154 | -// connections or connection getting an invalid return) separated |
155 | -// by SHORT_DELAY (millisec), wait LONG_DELAY (millisec) between |
156 | -// each failed connection. |
157 | -namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS = 5; |
158 | -namespace.SHORT_DELAY = 1000; |
159 | -namespace.LONG_DELAY = 3*60*1000; |
160 | - |
161 | -/** |
162 | - * |
163 | - * A Long Poll Manager creates and manages a long polling connexion |
164 | - * to the server to fetch events. This class is not directly used |
165 | - * but managed through 'setupLongPollManager' which creates and |
166 | - * initialises a singleton LongPollManager. |
167 | - * |
168 | - * @class LongPollManager |
169 | - */ |
170 | -function LongPollManager(config) { |
171 | - LongPollManager.superclass.constructor.apply(this, arguments); |
172 | -} |
173 | - |
174 | -LongPollManager.NAME = "longPollManager"; |
175 | - |
176 | -Y.extend(LongPollManager, Y.Base, { |
177 | - initializer : function(cfg) { |
178 | - this._started = false; |
179 | - this._failed_attempts = 0; |
180 | - this._repoll = true; |
181 | - this._sequence = 0; |
182 | - }, |
183 | - |
184 | - setConnectionInfos : function(key, uri) { |
185 | - this.key = key; |
186 | - this.uri = uri; |
187 | - }, |
188 | - |
189 | - _io : function (uri, config) { |
190 | - Y.io(uri, config); |
191 | - }, |
192 | - |
193 | - successPoll : function (id, response) { |
194 | - try { |
195 | - var data = Y.JSON.parse(response.responseText); |
196 | - if (!data.hasOwnProperty('event_key')) { |
197 | - throw new Error("Response has no event_key"); |
198 | - } |
199 | - Y.fire(data.event_key, data); |
200 | - return true; |
201 | - } |
202 | - catch (e) { |
203 | - Y.fire(namespace.longpoll_fail_event, e); |
204 | - return false; |
205 | - } |
206 | - }, |
207 | - |
208 | - failurePoll : function () { |
209 | - Y.fire(namespace.longpoll_fail_event); |
210 | - }, |
211 | - |
212 | - /** |
213 | - * Return the delay (milliseconds) to wait before trying to reconnect |
214 | - * again after a failed connection. |
215 | - * |
216 | - * The rationale here is that: |
217 | - * 1. We should not try to reconnect instantaneously after a failed |
218 | - * connection. |
219 | - * 2. After a certain number of failed connections, we should set the |
220 | - * delay between two failed connection to a bigger number because |
221 | - * the server may be having problems. |
222 | - * |
223 | - * @method _pollDelay |
224 | - */ |
225 | - _pollDelay : function() { |
226 | - this._failed_attempts = this._failed_attempts + 1; |
227 | - if (this._failed_attempts >= |
228 | - namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS) { |
229 | - Y.fire(namespace.longpoll_longdelay); |
230 | - return namespace.LONG_DELAY; |
231 | - } |
232 | - else { |
233 | - return namespace.SHORT_DELAY; |
234 | - } |
235 | - }, |
236 | - |
237 | - /** |
238 | - * Relaunch a connection to the server after a successful or |
239 | - * a failed connection. |
240 | - * |
241 | - * @method repoll |
242 | - * @param {Boolean} failed: whether or not the previous connection |
243 | - * has failed. |
244 | - */ |
245 | - repoll : function(failed) { |
246 | - if (!failed) { |
247 | - if (this._failed_attempts >= |
248 | - namespace.MAX_SHORT_DELAY_FAILED_ATTEMPTS) { |
249 | - Y.fire(namespace.longpoll_shortdelay); |
250 | - } |
251 | - this._failed_attempts = 0; |
252 | - if (this._repoll) { |
253 | - this.poll(); |
254 | - } |
255 | - } |
256 | - else { |
257 | - var delay = this._pollDelay(); |
258 | - if (this._repoll) { |
259 | - Y.later(delay, this, this.poll); |
260 | - } |
261 | - } |
262 | - }, |
263 | - |
264 | - poll : function() { |
265 | - var that = this; |
266 | - var config = { |
267 | - method: "GET", |
268 | - sync: false, |
269 | - on: { |
270 | - failure: function(id, response) { |
271 | - if (Y.Lang.isValue(response) && |
272 | - Y.Lang.isValue(response.status) && |
273 | - (response.status === 408 || |
274 | - response.status === 504)) { |
275 | - // If the error code is: |
276 | - // - 408 Request timeout |
277 | - // - 504 Gateway timeout |
278 | - // Then ignore the error and start |
279 | - // polling again. |
280 | - that.repoll(false); |
281 | - } |
282 | - else { |
283 | - that.failurePoll(); |
284 | - that.repoll(true); |
285 | - } |
286 | - }, |
287 | - success: function(id, response) { |
288 | - var res = that.successPoll(id, response); |
289 | - that.repoll(res); |
290 | - } |
291 | - } |
292 | - }; |
293 | - this._sequence = this._sequence + 1; |
294 | - var queue_uri = this.uri + |
295 | - "?uuid=" + this.key + |
296 | - "&sequence=" + this._sequence; |
297 | - if (!this._started) { |
298 | - Y.fire(namespace.longpoll_start_event); |
299 | - this._started = true; |
300 | - } |
301 | - this._io(queue_uri, config); |
302 | - } |
303 | -}); |
304 | - |
305 | -namespace.LongPollManager = LongPollManager; |
306 | - |
307 | -namespace.getLongPollManager = function() { |
308 | - if (!Y.Lang.isValue(namespace._manager)) { |
309 | - namespace._manager = new namespace.LongPollManager(); |
310 | - } |
311 | - return namespace._manager; |
312 | -}; |
313 | - |
314 | -namespace.setupLongPollManager = function() { |
315 | - if (Y.Object.owns(LP.cache, 'longpoll') && |
316 | - Y.Lang.isValue(LP.cache.longpoll)) { |
317 | - var key = LP.cache.longpoll.key; |
318 | - var uri = LP.cache.longpoll.uri; |
319 | - var longpollmanager = namespace.getLongPollManager(); |
320 | - longpollmanager.setConnectionInfos(key, uri); |
321 | - longpollmanager.poll(); |
322 | - return longpollmanager; |
323 | - } |
324 | -}; |
325 | - |
326 | -}, "0.1", {"requires":["base", "event", "json", "io"]}); |
327 | |
328 | === removed file 'lib/lp/app/javascript/tests/test_longpoll.html' |
329 | --- lib/lp/app/javascript/tests/test_longpoll.html 2012-10-26 09:54:28 +0000 |
330 | +++ lib/lp/app/javascript/tests/test_longpoll.html 1970-01-01 00:00:00 +0000 |
331 | @@ -1,47 +0,0 @@ |
332 | -<!DOCTYPE html> |
333 | -<!-- |
334 | -Copyright 2012 Canonical Ltd. This software is licensed under the |
335 | -GNU Affero General Public License version 3 (see the file LICENSE). |
336 | ---> |
337 | - |
338 | -<html> |
339 | - <head> |
340 | - <title>Test longpoll</title> |
341 | - |
342 | - <!-- YUI and test setup --> |
343 | - <script type="text/javascript" |
344 | - src="../../../../../build/js/yui/yui/yui.js"> |
345 | - </script> |
346 | - <link rel="stylesheet" |
347 | - href="../../../../../build/js/yui/console/assets/console-core.css" /> |
348 | - <link rel="stylesheet" |
349 | - href="../../../../../build/js/yui/test-console/assets/skins/sam/test-console.css" /> |
350 | - <link rel="stylesheet" |
351 | - href="../../../../../build/js/yui/test/assets/skins/sam/test.css" /> |
352 | - |
353 | - <script type="text/javascript" |
354 | - src="../../../../../build/js/lp/app/testing/testrunner.js"></script> |
355 | - |
356 | - <link rel="stylesheet" href="../../../app/javascript/testing/test.css" /> |
357 | - |
358 | - <!-- Dependencies --> |
359 | - <script type="text/javascript" |
360 | - src="../../../../../build/js/lp/app/client.js"></script> |
361 | - |
362 | - <!-- The module under test. --> |
363 | - <script type="text/javascript" src="../longpoll.js"></script> |
364 | - |
365 | - <!-- Placeholder for any css asset for this module. --> |
366 | - <!-- <link rel="stylesheet" href="../assets/longpoll-core.css" /> --> |
367 | - |
368 | - <!-- The test suite. --> |
369 | - <script type="text/javascript" src="test_longpoll.js"></script> |
370 | - |
371 | - </head> |
372 | - <body class="yui3-skin-sam"> |
373 | - <ul id="suites"> |
374 | - <!-- <li>lp.large_indicator.test</li> --> |
375 | - <li>lp.longpoll.test</li> |
376 | - </ul> |
377 | - </body> |
378 | -</html> |
379 | |
380 | === removed file 'lib/lp/app/javascript/tests/test_longpoll.js' |
381 | --- lib/lp/app/javascript/tests/test_longpoll.js 2017-07-24 15:37:03 +0000 |
382 | +++ lib/lp/app/javascript/tests/test_longpoll.js 1970-01-01 00:00:00 +0000 |
383 | @@ -1,283 +0,0 @@ |
384 | -/* Copyright 2011 Canonical Ltd. This software is licensed under the |
385 | - * GNU Affero General Public License version 3 (see the file LICENSE). */ |
386 | -YUI.add('lp.longpoll.test', function (Y) { |
387 | - var longpoll = Y.lp.app.longpoll; |
388 | - |
389 | - var tests = Y.namespace('lp.longpoll.test'); |
390 | - tests.suite = new Y.Test.Suite('longpoll Tests'); |
391 | - |
392 | - tests.suite.add(new Y.Test.Case({ |
393 | - name: 'TestLongPollSingleton', |
394 | - tearDown: function() { |
395 | - // Cleanup the singleton; |
396 | - longpoll._manager = null; |
397 | - }, |
398 | - |
399 | - testGetSingletonLongPollManager: function() { |
400 | - Y.Assert.isNull(longpoll._manager); |
401 | - var manager = longpoll.getLongPollManager(); |
402 | - Y.Assert.isNotNull(longpoll._manager); |
403 | - var manager2 = longpoll.getLongPollManager(); |
404 | - Y.Assert.areSame(manager, manager2); |
405 | - }, |
406 | - |
407 | - testInitLongPollManagerNoLongPoll: function() { |
408 | - // if LP.cache.longpoll.key is undefined: no longpoll manager |
409 | - // is created by setupLongPollManager. |
410 | - window.LP = { |
411 | - links: {}, |
412 | - cache: {} |
413 | - }; |
414 | - |
415 | - longpoll.setupLongPollManager(true); |
416 | - Y.Assert.isNull(longpoll._manager); |
417 | - }, |
418 | - |
419 | - testInitLongPollManagerLongPoll: function() { |
420 | - window.LP = { |
421 | - links: {}, |
422 | - cache: { |
423 | - longpoll: { |
424 | - key: 'key', |
425 | - uri: '/+longpoll/' |
426 | - } |
427 | - } |
428 | - }; |
429 | - |
430 | - longpoll.setupLongPollManager(true); |
431 | - Y.Assert.isNotNull(longpoll._manager); |
432 | - } |
433 | - })); |
434 | - |
435 | - tests.suite.add(new Y.Test.Case({ |
436 | - name: 'TestLongPoll', |
437 | - |
438 | - setUp: function() { |
439 | - var manager = longpoll.getLongPollManager(); |
440 | - manager._repoll = false; |
441 | - this.createBaseLP(); |
442 | - }, |
443 | - |
444 | - tearDown: function() { |
445 | - // Cleanup the singleton; |
446 | - longpoll._manager = null; |
447 | - }, |
448 | - |
449 | - createBaseLP:function() { |
450 | - window.LP = { |
451 | - links: {}, |
452 | - cache: {} |
453 | - }; |
454 | - }, |
455 | - |
456 | - setupLPCache: function() { |
457 | - LP.cache.longpoll = { |
458 | - key: 'key', |
459 | - uri: '/+longpoll/' |
460 | - }; |
461 | - }, |
462 | - |
463 | - setupLongPoll: function(nb_calls) { |
464 | - this.setupLPCache(); |
465 | - return longpoll.setupLongPollManager(true); |
466 | - }, |
467 | - |
468 | - testInitLongPollManagerQueueName: function() { |
469 | - var manager = this.setupLongPoll(); |
470 | - Y.Assert.areEqual(LP.cache.longpoll.key, manager.key); |
471 | - Y.Assert.areEqual(LP.cache.longpoll.uri, manager.uri); |
472 | - Y.Assert.isFalse(Y.Lang.isValue(manager.nb_calls)); |
473 | - }, |
474 | - |
475 | - testPollStarted: function() { |
476 | - var fired = false; |
477 | - Y.on(longpoll.longpoll_start_event, function() { |
478 | - fired = true; |
479 | - }); |
480 | - this.setupLongPoll(); |
481 | - Y.Assert.isTrue(fired, "Start event not fired."); |
482 | - }, |
483 | - |
484 | - testPollFailure: function() { |
485 | - var fired = false; |
486 | - Y.on(longpoll.longpoll_fail_event, function() { |
487 | - fired = true; |
488 | - }); |
489 | - // Monkeypatch io to simulate failure. |
490 | - var manager = longpoll.getLongPollManager(); |
491 | - manager._io = function(uri, config) { |
492 | - config.on.failure(); |
493 | - }; |
494 | - this.setupLongPoll(); |
495 | - Y.Assert.isTrue(fired, "Failure event not fired."); |
496 | - }, |
497 | - |
498 | - testSuccessPollInvalidData: function() { |
499 | - var manager = longpoll.getLongPollManager(); |
500 | - var custom_response = "{{"; |
501 | - var response = { |
502 | - responseText: custom_response |
503 | - }; |
504 | - var res = manager.successPoll("2", response); |
505 | - Y.Assert.isFalse(res); |
506 | - }, |
507 | - |
508 | - testSuccessPollMalformedData: function() { |
509 | - var manager = longpoll.getLongPollManager(); |
510 | - var response = { |
511 | - responseText: '{ "something": "6" }' |
512 | - }; |
513 | - var res = manager.successPoll("2", response); |
514 | - Y.Assert.isFalse(res); |
515 | - }, |
516 | - |
517 | - testSuccessPollWellformedData: function() { |
518 | - var manager = longpoll.getLongPollManager(); |
519 | - var response = { |
520 | - responseText: '{ "event_key": "4", "something": "6"}' |
521 | - }; |
522 | - var res = manager.successPoll("2", response); |
523 | - Y.Assert.isTrue(res); |
524 | - }, |
525 | - |
526 | - testPollDelay: function() { |
527 | - // Create event listeners. |
528 | - var longdelay_event_fired = false; |
529 | - Y.on(longpoll.longpoll_longdelay, function(data) { |
530 | - longdelay_event_fired = true; |
531 | - }); |
532 | - var shortdelay_event_fired = false; |
533 | - Y.on(longpoll.longpoll_shortdelay, function(data) { |
534 | - shortdelay_event_fired = true; |
535 | - }); |
536 | - var manager = longpoll.getLongPollManager(); |
537 | - // Monkeypatch io to simulate failure. |
538 | - manager._io = function(uri, config) { |
539 | - config.on.failure(); |
540 | - }; |
541 | - Y.Assert.areEqual(0, manager._failed_attempts); |
542 | - this.setupLongPoll(); |
543 | - Y.Assert.areEqual(1, manager._failed_attempts); |
544 | - var i, delay; |
545 | - for (i=0; i<longpoll.MAX_SHORT_DELAY_FAILED_ATTEMPTS-2; i++) { |
546 | - Y.Assert.areEqual(i+1, manager._failed_attempts); |
547 | - delay = manager._pollDelay(); |
548 | - Y.Assert.areEqual(delay, longpoll.SHORT_DELAY); |
549 | - } |
550 | - // After MAX_SHORT_DELAY_FAILED_ATTEMPTS failed attempts, the |
551 | - // delay returned by _pollDelay is LONG_DELAY and |
552 | - // longpoll_longdelay is fired. |
553 | - Y.Assert.isFalse(longdelay_event_fired); |
554 | - delay = manager._pollDelay(); |
555 | - Y.Assert.isTrue(longdelay_event_fired); |
556 | - Y.Assert.areEqual(delay, longpoll.LONG_DELAY); |
557 | - |
558 | - // Monkeypatch io to simulate success. |
559 | - manager._io = function(uri, config) { |
560 | - config.on.success(); |
561 | - }; |
562 | - // After a success, longpoll.longpoll_shortdelay is fired. |
563 | - Y.Assert.isFalse(shortdelay_event_fired); |
564 | - delay = manager.poll(); |
565 | - Y.Assert.isTrue(shortdelay_event_fired); |
566 | - }, |
567 | - |
568 | - testPollUriSequence: function() { |
569 | - // Each new polling increases the sequence parameter: |
570 | - // /+longpoll/?uuid=key&sequence=1 |
571 | - // /+longpoll/?uuid=key&sequence=2 |
572 | - // /+longpoll/?uuid=key&sequence=3 |
573 | - // .. |
574 | - var count = 0; |
575 | - // Monkeypatch io to simulate failure. |
576 | - var manager = longpoll.getLongPollManager(); |
577 | - manager._io = function(uri, config) { |
578 | - Y.Assert.areEqual( |
579 | - '/+longpoll/?uuid=key&sequence=' + (count+1), |
580 | - uri); |
581 | - count = count + 1; |
582 | - var response = { |
583 | - responseText: '{"i":2}' |
584 | - }; |
585 | - config.on.success(2, response); |
586 | - }; |
587 | - this.setupLongPoll(); |
588 | - var request; |
589 | - for (request=1; request<10; request++) { |
590 | - Y.Assert.isTrue(count === request, "Uri not requested."); |
591 | - manager.poll(); |
592 | - } |
593 | - }, |
594 | - |
595 | - _testDoesNotFail: function(error_code) { |
596 | - // Assert that, when the longpoll request receives an error |
597 | - // with code error_code, it is not treated as a failed |
598 | - // connection attempt. |
599 | - var manager = longpoll.getLongPollManager(); |
600 | - // Monkeypatch io to simulate a request timeout. |
601 | - manager._io = function(uri, config) { |
602 | - var response = {status: error_code}; |
603 | - config.on.failure(4, response); |
604 | - }; |
605 | - |
606 | - Y.Assert.areEqual(0, manager._failed_attempts); |
607 | - this.setupLongPoll(); |
608 | - Y.Assert.areEqual(0, manager._failed_attempts); |
609 | - }, |
610 | - |
611 | - test408RequestTimeoutHandling: function() { |
612 | - this._testDoesNotFail(408); |
613 | - }, |
614 | - |
615 | - test504GatewayTimeoutHandling: function() { |
616 | - this._testDoesNotFail(504); |
617 | - }, |
618 | - |
619 | - testPollPayLoadBad: function() { |
620 | - // If a non valid response is returned, longpoll_fail_event |
621 | - // is fired. |
622 | - var fired = false; |
623 | - Y.on(longpoll.longpoll_fail_event, function() { |
624 | - fired = true; |
625 | - }); |
626 | - var manager = longpoll.getLongPollManager(); |
627 | - // Monkeypatch io. |
628 | - manager._io = function(uri, config) { |
629 | - var response = { |
630 | - responseText: "{non valid json" |
631 | - }; |
632 | - config.on.success(2, response); |
633 | - }; |
634 | - this.setupLongPoll(); |
635 | - Y.Assert.isTrue(fired, "Failure event not fired."); |
636 | - }, |
637 | - |
638 | - testPollPayLoadOk: function() { |
639 | - // Create a valid message. |
640 | - var custom_response = { |
641 | - 'event_key': 'my-event', |
642 | - 'something': {something_else: 1234} |
643 | - }; |
644 | - var fired = false; |
645 | - Y.on(custom_response.event_key, function(data) { |
646 | - fired = true; |
647 | - Y.Assert.areEqual(data, custom_response); |
648 | - }); |
649 | - var manager = longpoll.getLongPollManager(); |
650 | - // Monkeypatch io. |
651 | - manager._io = function(uri, config) { |
652 | - var response = { |
653 | - responseText: Y.JSON.stringify(custom_response) |
654 | - }; |
655 | - config.on.success(2, response); |
656 | - }; |
657 | - this.setupLongPoll(); |
658 | - Y.Assert.isTrue(fired, "Custom event not fired."); |
659 | - } |
660 | - })); |
661 | - |
662 | -}, '0.1', { |
663 | - requires: [ |
664 | - 'lp.testing.runner', 'test', 'test-console', 'node-event-simulate', |
665 | - 'json', 'lp.app.longpoll'] |
666 | -}); |
667 | |
668 | === removed directory 'lib/lp/app/longpoll' |
669 | === removed file 'lib/lp/app/longpoll/__init__.py' |
670 | --- lib/lp/app/longpoll/__init__.py 2012-02-21 22:46:28 +0000 |
671 | +++ lib/lp/app/longpoll/__init__.py 1970-01-01 00:00:00 +0000 |
672 | @@ -1,50 +0,0 @@ |
673 | -# Copyright 2011-2012 Canonical Ltd. This software is licensed under the |
674 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
675 | - |
676 | -"""Long-poll infrastructure.""" |
677 | - |
678 | -__metaclass__ = type |
679 | -__all__ = [ |
680 | - "emit", |
681 | - "subscribe", |
682 | - ] |
683 | - |
684 | -from lazr.restful.utils import get_current_browser_request |
685 | -from zope.component import getAdapter |
686 | - |
687 | -from lp.services.longpoll.interfaces import ( |
688 | - ILongPollEvent, |
689 | - ILongPollSubscriber, |
690 | - ) |
691 | - |
692 | - |
693 | -def subscribe(target, event_name=u"", request=None): |
694 | - """Convenience method to subscribe the current request. |
695 | - |
696 | - :param target: Something that can be adapted to `ILongPollEvent`. |
697 | - :param event_name: The name of the event to subscribe to. This is used to |
698 | - look up a named adapter from `target` to `ILongPollEvent`. |
699 | - :param request: The request for which to get an `ILongPollSubscriber`. It |
700 | - a request is not specified the currently active request is used. |
701 | - :return: The `ILongPollEvent` that has been subscribed to. |
702 | - """ |
703 | - event = getAdapter(target, ILongPollEvent, name=event_name) |
704 | - if request is None: |
705 | - request = get_current_browser_request() |
706 | - subscriber = ILongPollSubscriber(request) |
707 | - subscriber.subscribe(event) |
708 | - return event |
709 | - |
710 | - |
711 | -def emit(source, event_name=u"", **data): |
712 | - """Convenience method to emit a message for an event. |
713 | - |
714 | - :param source: Something that can be adapted to `ILongPollEvent`. |
715 | - :param event_name: The name of the event to subscribe to. This is used to |
716 | - look up a named adapter from `target` to `ILongPollEvent`. |
717 | - :param data: See `ILongPollEvent.emit`. |
718 | - :return: The `ILongPollEvent` that has been emitted. |
719 | - """ |
720 | - event = getAdapter(source, ILongPollEvent, name=event_name) |
721 | - event.emit(**data) |
722 | - return event |
723 | |
724 | === removed directory 'lib/lp/app/longpoll/tests' |
725 | === removed file 'lib/lp/app/longpoll/tests/__init__.py' |
726 | === removed file 'lib/lp/app/longpoll/tests/test_longpoll.py' |
727 | --- lib/lp/app/longpoll/tests/test_longpoll.py 2015-07-09 12:18:51 +0000 |
728 | +++ lib/lp/app/longpoll/tests/test_longpoll.py 1970-01-01 00:00:00 +0000 |
729 | @@ -1,121 +0,0 @@ |
730 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
731 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
732 | - |
733 | -"""Tests for lp.app.longpoll.""" |
734 | - |
735 | -__metaclass__ = type |
736 | - |
737 | -from zope.component import ( |
738 | - adapter, |
739 | - getUtility, |
740 | - ) |
741 | -from zope.interface import ( |
742 | - Attribute, |
743 | - implementer, |
744 | - Interface, |
745 | - ) |
746 | - |
747 | -from lp.app.longpoll import ( |
748 | - emit, |
749 | - subscribe, |
750 | - ) |
751 | -from lp.services.longpoll.interfaces import ( |
752 | - ILongPollEvent, |
753 | - ILongPollSubscriber, |
754 | - ) |
755 | -from lp.services.messaging.interfaces import IMessageSession |
756 | -from lp.services.webapp.servers import LaunchpadTestRequest |
757 | -from lp.testing import TestCase |
758 | -from lp.testing.fixture import ZopeAdapterFixture |
759 | -from lp.testing.layers import LaunchpadFunctionalLayer |
760 | - |
761 | - |
762 | -class IFakeObject(Interface): |
763 | - |
764 | - ident = Attribute("ident") |
765 | - |
766 | - |
767 | -@implementer(IFakeObject) |
768 | -class FakeObject: |
769 | - |
770 | - def __init__(self, ident): |
771 | - self.ident = ident |
772 | - |
773 | - |
774 | -@adapter(IFakeObject) |
775 | -@implementer(ILongPollEvent) |
776 | -class FakeEvent: |
777 | - |
778 | - def __init__(self, source): |
779 | - self.source = source |
780 | - |
781 | - @property |
782 | - def event_key(self): |
783 | - return "event-key-%s" % self.source.ident |
784 | - |
785 | - def emit(self, **data): |
786 | - # Don't cargo-cult this; see .adapters.event.LongPollEvent instead. |
787 | - session = getUtility(IMessageSession) |
788 | - producer = session.getProducer(self.event_key) |
789 | - producer.sendNow(data) |
790 | - |
791 | - |
792 | -class TestFunctions(TestCase): |
793 | - |
794 | - layer = LaunchpadFunctionalLayer |
795 | - |
796 | - def test_subscribe(self): |
797 | - # subscribe() gets the ILongPollEvent for the given target and the |
798 | - # ILongPollSubscriber for the given request (or the current request is |
799 | - # discovered). It subscribes the latter to the event, then returns the |
800 | - # event. |
801 | - session = getUtility(IMessageSession) |
802 | - request = LaunchpadTestRequest() |
803 | - an_object = FakeObject(12345) |
804 | - with ZopeAdapterFixture(FakeEvent): |
805 | - event = subscribe(an_object, request=request) |
806 | - self.assertIsInstance(event, FakeEvent) |
807 | - self.assertEqual("event-key-12345", event.event_key) |
808 | - session.flush() |
809 | - # Emitting an event-key-12345 event will put something on the |
810 | - # subscriber's queue. |
811 | - event_data = {"1234": 5678} |
812 | - event.emit(**event_data) |
813 | - subscriber = ILongPollSubscriber(request) |
814 | - subscribe_queue = session.getConsumer(subscriber.subscribe_key) |
815 | - message = subscribe_queue.receive(timeout=5) |
816 | - self.assertEqual(event_data, message) |
817 | - |
818 | - def test_subscribe_to_named_event(self): |
819 | - # When an event_name is given to subscribe(), a named adapter is used |
820 | - # to get the ILongPollEvent for the given target. |
821 | - request = LaunchpadTestRequest() |
822 | - an_object = FakeObject(12345) |
823 | - with ZopeAdapterFixture(FakeEvent, name="foo"): |
824 | - event = subscribe(an_object, event_name="foo", request=request) |
825 | - self.assertIsInstance(event, FakeEvent) |
826 | - |
827 | - def test_emit(self): |
828 | - # emit() gets the ILongPollEvent for the given target and passes the |
829 | - # given data to its emit() method. It then returns the event. |
830 | - an_object = FakeObject(12345) |
831 | - with ZopeAdapterFixture(FakeEvent): |
832 | - event = emit(an_object) |
833 | - session = getUtility(IMessageSession) |
834 | - producer = session.getProducer(event.event_key) |
835 | - subscribe_queue = session.getConsumer("whatever") |
836 | - producer.associateConsumerNow(subscribe_queue) |
837 | - # Emit the event again; the subscribe queue was not associated |
838 | - # with the event before now. |
839 | - event_data = {"8765": 4321} |
840 | - event = emit(an_object, **event_data) |
841 | - message = subscribe_queue.receive(timeout=5) |
842 | - self.assertEqual(event_data, message) |
843 | - |
844 | - def test_emit_named_event(self): |
845 | - # When an event_name is given to emit(), a named adapter is used to |
846 | - # get the ILongPollEvent for the given target. |
847 | - an_object = FakeObject(12345) |
848 | - with ZopeAdapterFixture(FakeEvent, name="foo"): |
849 | - event = emit(an_object, "foo") |
850 | - self.assertIsInstance(event, FakeEvent) |
851 | |
852 | === modified file 'lib/lp/app/templates/base-layout-macros.pt' |
853 | --- lib/lp/app/templates/base-layout-macros.pt 2019-01-05 09:54:44 +0000 |
854 | +++ lib/lp/app/templates/base-layout-macros.pt 2019-04-16 14:35:10 +0000 |
855 | @@ -120,7 +120,7 @@ |
856 | //<![CDATA[ |
857 | LPJS.use('base', 'node', 'console', 'event', |
858 | 'oop', 'lp', 'lp.app.foldables','lp.app.sorttable', |
859 | - 'lp.app.inlinehelp', 'lp.app.links', 'lp.app.longpoll', |
860 | + 'lp.app.inlinehelp', 'lp.app.links', |
861 | 'lp.bugs.bugtask_index', 'lp.bugs.subscribers', |
862 | 'lp.app.ellipsis', 'lp.code.branchmergeproposal.diff', |
863 | 'lp.views.global', |
864 | @@ -135,14 +135,6 @@ |
865 | Y.lp.activate_collapsibles(); |
866 | Y.lp.app.foldables.activate(); |
867 | Y.lp.app.links.check_valid_lp_links(); |
868 | - // Longpolling will only start if |
869 | - // LP.cache.longpoll is populated. |
870 | - // We use Y.later to work around a Safari/Chrome 'feature': |
871 | - // The mouse cursor stays 'busy' until all the requests started during |
872 | - // page load are finished. Hence we want the long poll request to start |
873 | - // right *after* the page has loaded. |
874 | - Y.later(0, Y.lp.app.longpoll, Y.lp.app.longpoll.setupLongPollManager); |
875 | - |
876 | }); |
877 | |
878 | Y.on('lp:context:web_link:changed', function(e) { |
879 | |
880 | === modified file 'lib/lp/code/browser/branchmergeproposal.py' |
881 | --- lib/lp/code/browser/branchmergeproposal.py 2019-01-31 14:45:32 +0000 |
882 | +++ lib/lp/code/browser/branchmergeproposal.py 2019-04-16 14:35:10 +0000 |
883 | @@ -1,4 +1,4 @@ |
884 | -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
885 | +# Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
886 | # GNU Affero General Public License version 3 (see the file LICENSE). |
887 | |
888 | """Views, navigation and actions for BranchMergeProposals.""" |
889 | @@ -69,7 +69,6 @@ |
890 | vocabulary_to_choice_edit_items, |
891 | ) |
892 | from lp.app.browser.tales import DateTimeFormatterAPI |
893 | -from lp.app.longpoll import subscribe |
894 | from lp.code.adapters.branch import BranchMergeProposalNoPreviewDiffDelta |
895 | from lp.code.browser.codereviewcomment import CodeReviewDisplayComment |
896 | from lp.code.browser.decorations import DecoratedBranch |
897 | @@ -628,9 +627,6 @@ |
898 | else: |
899 | cache.objects['branch_diff_link'] = ( |
900 | canonical_url(self.context.parent) + '/+diff/') |
901 | - if getFeatureFlag("longpoll.merge_proposals.enabled"): |
902 | - cache.objects['merge_proposal_event_key'] = subscribe( |
903 | - self.context).event_key |
904 | |
905 | @action('Claim', name='claim') |
906 | def claim_action(self, action, data): |
907 | |
908 | === modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py' |
909 | --- lib/lp/code/browser/tests/test_branchmergeproposal.py 2019-01-31 14:21:09 +0000 |
910 | +++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2019-04-16 14:35:10 +0000 |
911 | @@ -1,4 +1,4 @@ |
912 | -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
913 | +# Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
914 | # GNU Affero General Public License version 3 (see the file LICENSE). |
915 | |
916 | """Unit tests for BranchMergeProposals.""" |
917 | @@ -1576,26 +1576,6 @@ |
918 | git_api.notify(bmp.source_git_repository.getInternalPath())) |
919 | self.assertTrue(view.pending_diff) |
920 | |
921 | - def test_subscribe_to_merge_proposal_events_flag_disabled(self): |
922 | - # If the longpoll.merge_proposals.enabled flag is not enabled the user |
923 | - # is *not* subscribed to events relating to the merge proposal. |
924 | - bmp = self.factory.makeBranchMergeProposal() |
925 | - view = create_initialized_view(bmp, '+index', current_request=True) |
926 | - cache = IJSONRequestCache(view.request) |
927 | - self.assertNotIn("longpoll", cache.objects) |
928 | - self.assertNotIn("merge_proposal_event_key", cache.objects) |
929 | - |
930 | - def test_subscribe_to_merge_proposal_events_flag_enabled(self): |
931 | - # If the longpoll.merge_proposals.enabled flag is enabled the user is |
932 | - # subscribed to events relating to the merge proposal. |
933 | - bmp = self.factory.makeBranchMergeProposal() |
934 | - self.useContext(feature_flags()) |
935 | - set_feature_flag('longpoll.merge_proposals.enabled', 'enabled') |
936 | - view = create_initialized_view(bmp, '+index', current_request=True) |
937 | - cache = IJSONRequestCache(view.request) |
938 | - self.assertIn("longpoll", cache.objects) |
939 | - self.assertIn("merge_proposal_event_key", cache.objects) |
940 | - |
941 | def test_description_is_meta_description(self): |
942 | description = ( |
943 | "I'd like to make the bmp description appear as the meta " |
944 | @@ -2076,20 +2056,6 @@ |
945 | browser = self.getViewBrowser(bmp) |
946 | assert 'unf_pbasyvpgf' in browser.contents |
947 | |
948 | - def test_pending_diff_message_with_longpoll_enabled(self): |
949 | - # If the longpoll feature flag is enabled then the message |
950 | - # displayed for a pending diff indicates that it'll update |
951 | - # automatically. See also |
952 | - # lib/lp/code/stories/branches/xx-branchmergeproposals.txt |
953 | - self.useContext(feature_flags()) |
954 | - set_feature_flag('longpoll.merge_proposals.enabled', 'enabled') |
955 | - bmp = self.factory.makeBranchMergeProposal() |
956 | - browser = self.getViewBrowser(bmp) |
957 | - self.assertIn( |
958 | - "An updated diff is being calculated and will appear " |
959 | - "automatically when ready.", |
960 | - browser.contents) |
961 | - |
962 | def test_short_conversation_comments_not_truncated(self): |
963 | """Short comments should not be truncated.""" |
964 | comment = self.factory.makeCodeReviewComment(body='x y' * 100) |
965 | |
966 | === removed file 'lib/lp/code/javascript/branchmergeproposal.updater.js' |
967 | --- lib/lp/code/javascript/branchmergeproposal.updater.js 2014-03-31 19:40:35 +0000 |
968 | +++ lib/lp/code/javascript/branchmergeproposal.updater.js 1970-01-01 00:00:00 +0000 |
969 | @@ -1,263 +0,0 @@ |
970 | -/* Copyright 2011 Canonical Ltd. This software is licensed under the |
971 | - * GNU Affero General Public License version 3 (see the file LICENSE). |
972 | - * |
973 | - * Code for updating the diff when a new version is available. |
974 | - * |
975 | - * @module lp.code.branchmergeproposal.updater |
976 | - * @requires node, lp.client |
977 | - */ |
978 | - |
979 | -YUI.add('lp.code.branchmergeproposal.updater', function(Y) { |
980 | - |
981 | -var namespace = Y.namespace('lp.code.branchmergeproposal.updater'); |
982 | - |
983 | -function UpdaterWidget(config) { |
984 | - UpdaterWidget.superclass.constructor.apply(this, arguments); |
985 | -} |
986 | - |
987 | -Y.mix(UpdaterWidget, { |
988 | - |
989 | - NAME: 'updaterWidget', |
990 | - |
991 | - ATTRS: { |
992 | - |
993 | - /** |
994 | - * The LP client to use. If none is provided, one will be |
995 | - * created during initialization. |
996 | - * |
997 | - * @attribute lp_client |
998 | - */ |
999 | - lp_client: { |
1000 | - value: null |
1001 | - }, |
1002 | - |
1003 | - /** |
1004 | - * The summary node. |
1005 | - * |
1006 | - * @attribute summary_node |
1007 | - */ |
1008 | - summary_node: { |
1009 | - value: null, |
1010 | - writeOnce: "initOnly" |
1011 | - }, |
1012 | - |
1013 | - /** |
1014 | - * Whether or not this MP is still 'pending'. |
1015 | - * |
1016 | - * @attribute pending |
1017 | - * @readOnly |
1018 | - */ |
1019 | - pending: { |
1020 | - readOnly: true, |
1021 | - getter: function() { |
1022 | - return !Y.Lang.isValue( |
1023 | - this.get('srcNode').one('.diff-content')); |
1024 | - } |
1025 | - }, |
1026 | - |
1027 | - /** |
1028 | - * The HTML code for the stats diff. |
1029 | - * |
1030 | - * @attribute diff_stats |
1031 | - */ |
1032 | - diff_stats: { |
1033 | - getter: function() { |
1034 | - var summary_node = this.get('summary_node'); |
1035 | - if (!Y.Lang.isValue(summary_node) || |
1036 | - !Y.Lang.isValue(summary_node.one( |
1037 | - '#summary-row-b-diff'))) { |
1038 | - return null; |
1039 | - } |
1040 | - return summary_node.one( |
1041 | - '#summary-row-b-diff').one('td').get('innerHTML'); |
1042 | - }, |
1043 | - setter: function(value) { |
1044 | - this._setup_diff_stats_container(); |
1045 | - var container = this.get( |
1046 | - 'summary_node').one('#summary-row-b-diff').one('td'); |
1047 | - container.set('innerHTML', value); |
1048 | - } |
1049 | - } |
1050 | - } |
1051 | - |
1052 | -}); |
1053 | - |
1054 | -Y.extend(UpdaterWidget, Y.Widget, { |
1055 | - |
1056 | - /* |
1057 | - * The initializer method that is called from the base Plugin class. |
1058 | - * |
1059 | - * @method initializer |
1060 | - * @protected |
1061 | - */ |
1062 | - initializer: function(cfg){ |
1063 | - // If we have not been provided with a Launchpad Client, then |
1064 | - // create one now: |
1065 | - if (null === this.get("lp_client")){ |
1066 | - // Create our own instance of the LP client. |
1067 | - this.set("lp_client", new Y.lp.client.Launchpad()); |
1068 | - } |
1069 | - this.set('summary_node', cfg.summary_node); |
1070 | - }, |
1071 | - |
1072 | - /* |
1073 | - * Set the proper icon to indicate the diff is updating. |
1074 | - * |
1075 | - * @method set_status_updating |
1076 | - */ |
1077 | - set_status_updating: function() { |
1078 | - this.cleanup_status(); |
1079 | - this._set_status('spinner', 'Update in progress.'); |
1080 | - }, |
1081 | - |
1082 | - /* |
1083 | - * Set the proper icon to indicate the diff will be updated when the |
1084 | - * new version is available. |
1085 | - * |
1086 | - * @method set_status_longpolling |
1087 | - */ |
1088 | - set_status_longpolling: function() { |
1089 | - this.cleanup_status(); |
1090 | - this._set_status( |
1091 | - 'longpoll_loading', |
1092 | - 'The diff will be updated as soon as a new version is available.'); |
1093 | - }, |
1094 | - |
1095 | - /* |
1096 | - * Set the proper icon to indicate that the diff update is broken. |
1097 | - * |
1098 | - * @method set_status_longpollerror |
1099 | - */ |
1100 | - set_status_longpollerror: function() { |
1101 | - this.cleanup_status(); |
1102 | - this._set_status( |
1103 | - 'longpoll_error', |
1104 | - 'Diff update error, please reload to see the changes.'); |
1105 | - }, |
1106 | - |
1107 | - /* |
1108 | - * Add a status image to the diff title. |
1109 | - * |
1110 | - * @method _set_status |
1111 | - */ |
1112 | - _set_status: function(image_name, title) { |
1113 | - var image = Y.Node.create('<img />') |
1114 | - .set('src', '/@@/' + image_name) |
1115 | - .set('title', title); |
1116 | - this.get('srcNode').one('h2').append(image); |
1117 | - }, |
1118 | - |
1119 | - /* |
1120 | - * Remove the status image to the diff title. |
1121 | - * |
1122 | - * @method cleanup_status |
1123 | - */ |
1124 | - cleanup_status: function() { |
1125 | - this._setup_diff_container(); |
1126 | - this.get('srcNode').all('h2 img').remove(); |
1127 | - }, |
1128 | - |
1129 | - /* |
1130 | - * Add a row in the page summary table to display the diff stats |
1131 | - * if needed. |
1132 | - * |
1133 | - * @method _setup_diff_stats_container |
1134 | - */ |
1135 | - _setup_diff_stats_container: function() { |
1136 | - if (!Y.Lang.isValue(this.get('diff_stats'))) { |
1137 | - var summary_node = this.get('summary_node'); |
1138 | - var diff_stats = Y.Node.create('<tr />') |
1139 | - .set('id', 'summary-row-b-diff') |
1140 | - .append(Y.Node.create('<th />') |
1141 | - .set("text", "Diff against target:")) |
1142 | - .append(Y.Node.create('<td />')); |
1143 | - summary_node.one( |
1144 | - '#summary-row-9-target-branch').insert(diff_stats, 'after'); |
1145 | - } |
1146 | - }, |
1147 | - |
1148 | - /* |
1149 | - * Populate the widget with the required nodes to display the diff |
1150 | - * if needed. |
1151 | - * |
1152 | - * @method _setup_diff_container |
1153 | - */ |
1154 | - _setup_diff_container: function() { |
1155 | - if (this.get('pending')) { |
1156 | - // Cleanup.get('srcNode'). |
1157 | - this.get('srcNode').empty(); |
1158 | - // Create the diff container. |
1159 | - var review_diff = Y.Node.create('<div />') |
1160 | - .set('id', 'review-diff') |
1161 | - .append(Y.Node.create('<h2 />') |
1162 | - .set("text", "Preview Diff ")) |
1163 | - .append(Y.Node.create('<div />') |
1164 | - .addClass("diff-navigator")) |
1165 | - .append(Y.Node.create('<div />') |
1166 | - .addClass("diff-content")); |
1167 | - this.get('srcNode').append(review_diff); |
1168 | - } |
1169 | - }, |
1170 | - |
1171 | - /* |
1172 | - * Update the page diff stats. |
1173 | - * |
1174 | - * @method update |
1175 | - */ |
1176 | - update: function() { |
1177 | - this.update_stats(); |
1178 | - }, |
1179 | - |
1180 | - /* |
1181 | - * Update the diff stats with the last version. |
1182 | - * |
1183 | - * @method update_stats |
1184 | - */ |
1185 | - update_stats: function() { |
1186 | - var self = this; |
1187 | - var config = { |
1188 | - on: { |
1189 | - success: function(diff_stats) { |
1190 | - self.set('diff_stats', diff_stats); |
1191 | - // (re)connect the js scroller link. |
1192 | - Y.lp.code.branchmergeproposal.reviewcomment.link_scroller( |
1193 | - '#proposal-summary a.diff-link', '#review-diff'); |
1194 | - var node = self.get('summary_node'); |
1195 | - Y.lp.anim.green_flash({node: node}).run(); |
1196 | - self.fire(self.NAME + '.updated'); |
1197 | - }, |
1198 | - failure: function() { |
1199 | - var node = self.get('summary_node'); |
1200 | - Y.lp.anim.red_flash({node: node}).run(); |
1201 | - }, |
1202 | - start: function() { |
1203 | - self.set_status_updating(); |
1204 | - }, |
1205 | - end: function() { |
1206 | - self.cleanup_status(); |
1207 | - } |
1208 | - } |
1209 | - }; |
1210 | - var mp_uri = LP.cache.context.web_link; |
1211 | - this.get('lp_client').get(mp_uri + "/++diff-stats", config); |
1212 | - } |
1213 | - |
1214 | -}); |
1215 | - |
1216 | -/* |
1217 | - * Export UpdaterWidget. |
1218 | - */ |
1219 | -namespace.UpdaterWidget = UpdaterWidget; |
1220 | - |
1221 | -/* |
1222 | - * Returns true if the event fired means that the preview_diff field of the |
1223 | - * MP has been updated. |
1224 | - * |
1225 | - */ |
1226 | -namespace.is_mp_diff_updated = function(event_data) { |
1227 | - return (event_data.what === "modified" && |
1228 | - event_data.edited_fields.indexOf("preview_diff") >= 0); |
1229 | -}; |
1230 | - |
1231 | -}, '0.1', {requires: ['node', 'lp.client', 'lp.anim', |
1232 | - 'lp.code.branchmergeproposal.reviewcomment']}); |
1233 | |
1234 | === removed file 'lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html' |
1235 | --- lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html 2012-10-26 09:54:28 +0000 |
1236 | +++ lib/lp/code/javascript/tests/test_branchmergeproposal.updater.html 1970-01-01 00:00:00 +0000 |
1237 | @@ -1,98 +0,0 @@ |
1238 | -<!DOCTYPE html> |
1239 | -<!-- |
1240 | -Copyright 2012 Canonical Ltd. This software is licensed under the |
1241 | -GNU Affero General Public License version 3 (see the file LICENSE). |
1242 | ---> |
1243 | - |
1244 | -<html> |
1245 | - <head> |
1246 | - <title>Test branchmergeproposal</title> |
1247 | - |
1248 | - <!-- YUI and test setup --> |
1249 | - <script type="text/javascript" |
1250 | - src="../../../../../build/js/yui/yui/yui.js"> |
1251 | - </script> |
1252 | - <link rel="stylesheet" |
1253 | - href="../../../../../build/js/yui/console/assets/console-core.css" /> |
1254 | - <link rel="stylesheet" |
1255 | - href="../../../../../build/js/yui/test-console/assets/skins/sam/test-console.css" /> |
1256 | - <link rel="stylesheet" |
1257 | - href="../../../../../build/js/yui/test/assets/skins/sam/test.css" /> |
1258 | - |
1259 | - <script type="text/javascript" |
1260 | - src="../../../../../build/js/lp/app/testing/testrunner.js"></script> |
1261 | - |
1262 | - <link rel="stylesheet" href="../../../app/javascript/testing/test.css" /> |
1263 | - |
1264 | - <!-- Dependencies --> |
1265 | - <script type="text/javascript" |
1266 | - src="../../../../../build/js/lp/..."></script> |
1267 | - <script type="text/javascript" |
1268 | - src="../../../../../build/js/lp/app/client.js"></script> |
1269 | - <script type="text/javascript" |
1270 | - src="../../../../../build/js/lp/app/lp.js"></script> |
1271 | - <script type="text/javascript" |
1272 | - src="../../../../../build/js/lp/app/anim/anim.js"></script> |
1273 | - <script type="text/javascript" |
1274 | - src="../../../../../build/js/lp/app/extras/extras.js"></script> |
1275 | - <script type="text/javascript" |
1276 | - src="../../../../../build/js/lp/app/testing/mockio.js"></script> |
1277 | - <script type="text/javascript" |
1278 | - src="../../../../../build/js/lp/code/branchmergeproposal.reviewcomment.js"></script> |
1279 | - |
1280 | - <!-- The module under test. --> |
1281 | - <script type="text/javascript" src="../branchmergeproposal.updater.js"></script> |
1282 | - |
1283 | - <!-- Any css assert for this module. --> |
1284 | - <!-- <link rel="stylesheet" href="../assets/branchmergeproposal-core.css" /> --> |
1285 | - |
1286 | - <!-- The test suite. --> |
1287 | - <script type="text/javascript" src="test_branchmergeproposal.updater.js"></script> |
1288 | - |
1289 | - <!-- expected variable --> |
1290 | - <script type="text/javascript"> |
1291 | - var LP = { |
1292 | - cache: {}, |
1293 | - links: {} |
1294 | - }; |
1295 | - </script> |
1296 | - |
1297 | - </head> |
1298 | - <body class="yui3-skin-sam"> |
1299 | - <ul id="suites"> |
1300 | - <!-- <li>lp.large_indicator.test</li> --> |
1301 | - <li>lp.branchmergeproposal.updater.test</li> |
1302 | - </ul> |
1303 | - <div id="placeholder" style="display:none;"> |
1304 | - </div> |
1305 | - |
1306 | - <script type="text/x-template" id="pending-mp"> |
1307 | - <table id="proposal-summary"> |
1308 | - <tr id="summary-row-9-target-branch"> |
1309 | - <th>Merge into:</th> |
1310 | - <td> |
1311 | - <a href="/~me/project/branch" |
1312 | - class="sprite branch">lp://dev/~me/project/branch</a> |
1313 | - </td> |
1314 | - </tr> |
1315 | - </table> |
1316 | - <div id="diff-area"> |
1317 | - <div class="pending-update" id="diff-pending-update"> |
1318 | - <h3>Updating diff...</h3> |
1319 | - <p>An updated diff will be available in a few minutes.</p> |
1320 | - </div> |
1321 | - </div> |
1322 | - </script> |
1323 | - |
1324 | - <script type="text/x-template" id="current-mp"> |
1325 | - <div id="diff-area"> |
1326 | - <div id="review-diff"> |
1327 | - <h2>Preview Diff</h2> |
1328 | - <div class="diff-content">Example diff</div> |
1329 | - </div> |
1330 | - </div> |
1331 | - </script> |
1332 | - |
1333 | - |
1334 | - </body> |
1335 | -</html> |
1336 | |
1337 | === removed file 'lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js' |
1338 | --- lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js 2014-03-31 19:40:35 +0000 |
1339 | +++ lib/lp/code/javascript/tests/test_branchmergeproposal.updater.js 1970-01-01 00:00:00 +0000 |
1340 | @@ -1,162 +0,0 @@ |
1341 | -/* Copyright 2011 Canonical Ltd. This software is licensed under the |
1342 | - * GNU Affero General Public License version 3 (see the file LICENSE). |
1343 | - * |
1344 | - * Tests for lp.code.branchmergeproposal.updater. |
1345 | - * |
1346 | - */ |
1347 | -YUI.add('lp.branchmergeproposal.updater.test', function (Y) { |
1348 | -var module = Y.lp.code.branchmergeproposal.updater; |
1349 | -var UpdaterWidget = module.UpdaterWidget; |
1350 | - |
1351 | - |
1352 | -var tests = Y.namespace('lp.branchmergeproposal.updater.test'); |
1353 | -tests.suite = new Y.Test.Suite("BranchMergeProposal Updater Tests"); |
1354 | - |
1355 | -/* |
1356 | - * Tests for when the updater is built on top of a pending diff. |
1357 | - * |
1358 | - */ |
1359 | - |
1360 | -var pending_mp = Y.one('#pending-mp').getContent(); |
1361 | - |
1362 | -tests.suite.add(new Y.Test.Case({ |
1363 | - |
1364 | - name: 'branchmergeproposal-updater-pending-tests', |
1365 | - |
1366 | - setUp: function() { |
1367 | - Y.one("#placeholder") |
1368 | - .empty() |
1369 | - .append(Y.Node.create(pending_mp)); |
1370 | - var diff_area = Y.one('#diff-area'); |
1371 | - var summary_node = Y.one('#proposal-summary'); |
1372 | - this.updater = new UpdaterWidget( |
1373 | - {srcNode: diff_area, summary_node: summary_node}); |
1374 | - |
1375 | - LP.cache.context = { |
1376 | - web_link: "https://code.launchpad.dev/~foo/bar/foobr/+merge/123"}; |
1377 | - |
1378 | - }, |
1379 | - |
1380 | - tearDown: function() { |
1381 | - this.updater.destroy(); |
1382 | - }, |
1383 | - |
1384 | - test_default_values: function() { |
1385 | - Y.Assert.isTrue(this.updater.get('pending')); |
1386 | - Y.Assert.isNull(this.updater.get('diff_stats')); |
1387 | - }, |
1388 | - |
1389 | - test__setup_diff_container: function() { |
1390 | - this.updater._setup_diff_container(); |
1391 | - Y.Assert.isFalse(this.updater.get('pending')); |
1392 | - Y.Assert.areEqual( |
1393 | - "Preview Diff ", |
1394 | - this.updater.get( |
1395 | - 'srcNode').one('#review-diff h2').get('text')); |
1396 | - Y.Assert.areEqual( |
1397 | - "", |
1398 | - this.updater.get( |
1399 | - 'srcNode').one('.diff-content').get('text')); |
1400 | - }, |
1401 | - |
1402 | - test__setup_diff_stats_container: function() { |
1403 | - Y.Assert.isNull(this.updater.get('diff_stats')); |
1404 | - this.updater._setup_diff_stats_container(); |
1405 | - Y.Assert.areEqual('', this.updater.get('diff_stats')); |
1406 | - }, |
1407 | - |
1408 | - test_set_diff_stats: function() { |
1409 | - this.updater.set('diff_stats', '13 lines (+4/-0) 1 file modified'); |
1410 | - Y.Assert.areEqual( |
1411 | - '13 lines (+4/-0) 1 file modified', |
1412 | - this.updater.get('diff_stats')); |
1413 | - }, |
1414 | - |
1415 | - test_set_status_updating: function() { |
1416 | - this.updater.set_status_updating(); |
1417 | - Y.Assert.areEqual( |
1418 | - '/@@/spinner', |
1419 | - Y.one('h2').one('img').getAttribute('src')); |
1420 | - }, |
1421 | - |
1422 | - test_set_status_longpolling: function() { |
1423 | - this.updater.set_status_longpolling(); |
1424 | - Y.Assert.areEqual( |
1425 | - '/@@/longpoll_loading', |
1426 | - Y.one('h2').one('img').getAttribute('src')); |
1427 | - }, |
1428 | - |
1429 | - test_set_status_longpollerror: function() { |
1430 | - this.updater.set_status_longpollerror(); |
1431 | - Y.Assert.areEqual( |
1432 | - '/@@/longpoll_error', |
1433 | - Y.one('h2').one('img').getAttribute('src')); |
1434 | - }, |
1435 | - |
1436 | - test_cleanup_status: function() { |
1437 | - this.updater._setup_diff_container(); |
1438 | - this.updater.set_status_updating(); |
1439 | - this.updater.cleanup_status(); |
1440 | - Y.Assert.areEqual( |
1441 | - 'Preview Diff ', |
1442 | - Y.one('h2').get('innerHTML')); |
1443 | - }, |
1444 | - |
1445 | - test_update_stats_success: function() { |
1446 | - var mockio = new Y.lp.testing.mockio.MockIo(); |
1447 | - this.updater.get('lp_client').io_provider = mockio; |
1448 | - Y.Assert.isNull(this.updater.get('diff_stats')); |
1449 | - this.updater.update_stats(); |
1450 | - mockio.success({ |
1451 | - responseText: '13 lines (+4/-0) 1 file modified', |
1452 | - responseHeaders: {'Content-Type': 'text/html'}}); |
1453 | - |
1454 | - Y.Assert.areEqual( |
1455 | - '13 lines (+4/-0) 1 file modified', |
1456 | - this.updater.get('diff_stats')); |
1457 | - }, |
1458 | - |
1459 | - test_update_fires_event: function() { |
1460 | - var fired = false; |
1461 | - var mockio = new Y.lp.testing.mockio.MockIo(); |
1462 | - this.updater.get('lp_client').io_provider = mockio; |
1463 | - this.updater.on(this.updater.NAME + '.updated', function() { |
1464 | - fired = true; |
1465 | - }); |
1466 | - this.updater.update(); |
1467 | - mockio.success({ |
1468 | - responseText: '13 lines (+4/-0) 1 file modified', |
1469 | - responseHeaders: {'Content-Type': 'text/html'}}); |
1470 | - Y.Assert.isTrue(fired); |
1471 | - } |
1472 | - |
1473 | -})); |
1474 | - |
1475 | - |
1476 | -tests.suite.add(new Y.Test.Case({ |
1477 | - |
1478 | - name: 'branchmergeproposal-updater-utilities', |
1479 | - |
1480 | - test_is_mp_diff_updated_modified: function() { |
1481 | - var data = {what: 'modified', edited_fields: ['preview_diff']}; |
1482 | - Y.Assert.isTrue(module.is_mp_diff_updated(data)); |
1483 | - }, |
1484 | - |
1485 | - test_is_mp_diff_updater_deleted: function() { |
1486 | - var data = {what: 'deleted'}; |
1487 | - Y.Assert.isFalse(module.is_mp_diff_updated(data)); |
1488 | - }, |
1489 | - |
1490 | - test_is_mp_diff_updated_title_changed: function() { |
1491 | - var data = {what: 'modified', edited_fields: ['title']}; |
1492 | - Y.Assert.isFalse(module.is_mp_diff_updated(data)); |
1493 | - } |
1494 | - |
1495 | -})); |
1496 | - |
1497 | - |
1498 | -}, '0.1', { |
1499 | - requires: ['lp.testing.runner', 'test', 'dump', 'test-console', 'node', |
1500 | - 'lp.testing.mockio', 'event', |
1501 | - 'lp.code.branchmergeproposal.updater'] |
1502 | -}); |
1503 | |
1504 | === modified file 'lib/lp/code/templates/branchmergeproposal-index.pt' |
1505 | --- lib/lp/code/templates/branchmergeproposal-index.pt 2019-01-31 13:48:34 +0000 |
1506 | +++ lib/lp/code/templates/branchmergeproposal-index.pt 2019-04-16 14:35:10 +0000 |
1507 | @@ -185,13 +185,10 @@ |
1508 | <div class="yui-g" tal:condition="python: not view.show_diff_update_link and view.pending_diff"> |
1509 | <div class="pending-update" id="diff-pending-update"> |
1510 | <h3>Updating diff...</h3> |
1511 | - <p tal:condition="not: features/longpoll.merge_proposals.enabled"> |
1512 | + <p> |
1513 | An updated diff will be available in a few minutes. Reload to see the |
1514 | changes. |
1515 | </p> |
1516 | - <p tal:condition="features/longpoll.merge_proposals.enabled"> |
1517 | - An updated diff is being calculated and will appear automatically when ready. |
1518 | - </p> |
1519 | </div> |
1520 | </div> |
1521 | <div class="yui-g" tal:condition="view/show_diff_update_link"> |
1522 | @@ -226,7 +223,7 @@ |
1523 | conf = <tal:status-config replace="view/status_config" /> |
1524 | LPJS.use('io-base', 'lp.code.branchmergeproposal.reviewcomment', |
1525 | 'lp.code.branchmergeproposal.status', 'lp.app.comment', |
1526 | - 'lp.code.branchmergeproposal.updater', 'lp.app.widgets.expander', |
1527 | + 'lp.app.widgets.expander', |
1528 | 'lp.code.branch.revisionexpander', |
1529 | 'lp.code.branchmergeproposal.inlinecomments', function(Y) { |
1530 | |
1531 | @@ -252,23 +249,6 @@ |
1532 | diffnav.render(); |
1533 | } |
1534 | |
1535 | - if (Y.Lang.isValue(LP.cache.merge_proposal_event_key)) { |
1536 | - var upt = Y.lp.code.branchmergeproposal.updater; |
1537 | - var cfg = { |
1538 | - srcNode: Y.one('#diff-area'), |
1539 | - summary_node: Y.one('#proposal-summary') |
1540 | - }; |
1541 | - var updater = new upt.UpdaterWidget(cfg); |
1542 | - Y.on(LP.cache.merge_proposal_event_key, function(data) { |
1543 | - if (upt.is_mp_diff_updated(data)) { |
1544 | - updater.update(); |
1545 | - } |
1546 | - }); |
1547 | - updater.on(updater.NAME + '.updated', function() { |
1548 | - diffnav.render(); |
1549 | - }); |
1550 | - } |
1551 | - |
1552 | LP.cache.comment_context = LP.cache.context; |
1553 | var cl = new Y.lp.app.comment.CommentList({ |
1554 | comment_list_container: Y.one('#conversation') |
1555 | |
1556 | === modified file 'lib/lp/scripts/runlaunchpad.py' |
1557 | --- lib/lp/scripts/runlaunchpad.py 2018-05-21 20:30:16 +0000 |
1558 | +++ lib/lp/scripts/runlaunchpad.py 2019-04-16 14:35:10 +0000 |
1559 | @@ -1,4 +1,4 @@ |
1560 | -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
1561 | +# Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
1562 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1563 | |
1564 | __metaclass__ = type |
1565 | @@ -27,7 +27,6 @@ |
1566 | ) |
1567 | from lp.services.rabbit.server import RabbitServer |
1568 | from lp.services.sitesearch import bingtestservice |
1569 | -from lp.services.txlongpoll.server import TxLongPollServer |
1570 | |
1571 | |
1572 | def make_abspath(path): |
1573 | @@ -237,28 +236,6 @@ |
1574 | self.useFixture(self.server) |
1575 | |
1576 | |
1577 | -class TxLongPollService(Service): |
1578 | - """A TxLongPoll service.""" |
1579 | - |
1580 | - @property |
1581 | - def should_launch(self): |
1582 | - return config.txlongpoll.launch |
1583 | - |
1584 | - def launch(self): |
1585 | - twistd_bin = os.path.join(config.root, 'bin', 'twistd') |
1586 | - broker_hostname, broker_port = as_host_port( |
1587 | - config.rabbitmq.host, None, None) |
1588 | - self.server = TxLongPollServer( |
1589 | - twistd_bin=twistd_bin, |
1590 | - frontend_port=config.txlongpoll.frontend_port, |
1591 | - broker_user=config.rabbitmq.userid, |
1592 | - broker_password=config.rabbitmq.password, |
1593 | - broker_vhost=config.rabbitmq.virtual_host, |
1594 | - broker_host=broker_hostname, |
1595 | - broker_port=broker_port) |
1596 | - self.useFixture(self.server) |
1597 | - |
1598 | - |
1599 | def stop_process(process): |
1600 | """kill process and BLOCK until process dies. |
1601 | |
1602 | @@ -284,7 +261,6 @@ |
1603 | 'codebrowse': CodebrowseService(), |
1604 | 'memcached': MemcachedService(), |
1605 | 'rabbitmq': RabbitService(), |
1606 | - 'txlongpoll': TxLongPollService(), |
1607 | } |
1608 | |
1609 | |
1610 | |
1611 | === modified file 'lib/lp/scripts/tests/test_runlaunchpad.py' |
1612 | --- lib/lp/scripts/tests/test_runlaunchpad.py 2018-05-21 20:30:16 +0000 |
1613 | +++ lib/lp/scripts/tests/test_runlaunchpad.py 2019-04-16 14:35:10 +0000 |
1614 | @@ -1,4 +1,4 @@ |
1615 | -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the |
1616 | +# Copyright 2009-2019 Canonical Ltd. This software is licensed under the |
1617 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1618 | |
1619 | """Tests for runlaunchpad.py""" |
1620 | @@ -156,10 +156,6 @@ |
1621 | if config.rabbitmq.launch: |
1622 | expected.append(SERVICES['rabbitmq']) |
1623 | |
1624 | - # TxLongPoll may or may not be asked to run. |
1625 | - if config.txlongpoll.launch: |
1626 | - expected.append(SERVICES['txlongpoll']) |
1627 | - |
1628 | expected = sorted(expected) |
1629 | self.assertEqual(expected, services) |
1630 | |
1631 | |
1632 | === modified file 'lib/lp/services/config/schema-lazr.conf' |
1633 | --- lib/lp/services/config/schema-lazr.conf 2019-03-26 20:51:38 +0000 |
1634 | +++ lib/lp/services/config/schema-lazr.conf 2019-04-16 14:35:10 +0000 |
1635 | @@ -1601,16 +1601,6 @@ |
1636 | # datatype: string |
1637 | virtual_host: none |
1638 | |
1639 | -[txlongpoll] |
1640 | -# Should TxLongPoll be launched by default? |
1641 | -# datatype: boolean |
1642 | -launch: False |
1643 | -# The port at which TxLongPoll is listening. |
1644 | -# datatype: string |
1645 | -frontend_port: none |
1646 | -# The uri that should be a proxy to the TxLongPoll server. |
1647 | -uri: /+longpoll/ |
1648 | - |
1649 | [request_daily_builds] |
1650 | dbuser: request-daily-builds |
1651 | |
1652 | |
1653 | === modified file 'lib/lp/services/configure.zcml' |
1654 | --- lib/lp/services/configure.zcml 2018-03-16 14:50:01 +0000 |
1655 | +++ lib/lp/services/configure.zcml 2019-04-16 14:35:10 +0000 |
1656 | @@ -1,4 +1,4 @@ |
1657 | -<!-- Copyright 2010-2011 Canonical Ltd. This software is licensed under the |
1658 | +<!-- Copyright 2010-2019 Canonical Ltd. This software is licensed under the |
1659 | GNU Affero General Public License version 3 (see the file LICENSE). |
1660 | --> |
1661 | |
1662 | @@ -16,7 +16,6 @@ |
1663 | <include package=".inlinehelp" file="meta.zcml" /> |
1664 | <include package=".job" /> |
1665 | <include package=".librarian" /> |
1666 | - <include package=".longpoll" /> |
1667 | <include package=".mail" /> |
1668 | <include package=".memcache" /> |
1669 | <include package=".messages" /> |
1670 | |
1671 | === modified file 'lib/lp/services/features/flags.py' |
1672 | --- lib/lp/services/features/flags.py 2018-05-21 20:30:16 +0000 |
1673 | +++ lib/lp/services/features/flags.py 2019-04-16 14:35:10 +0000 |
1674 | @@ -1,4 +1,4 @@ |
1675 | -# Copyright 2010-2018 Canonical Ltd. This software is licensed under the |
1676 | +# Copyright 2010-2019 Canonical Ltd. This software is licensed under the |
1677 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1678 | |
1679 | __all__ = [ |
1680 | @@ -171,13 +171,6 @@ |
1681 | 'None are enabled', |
1682 | '', |
1683 | ''), |
1684 | - ('longpoll.merge_proposals.enabled', |
1685 | - 'boolean', |
1686 | - ('Enables the longpoll mechanism for merge proposals so that diffs, ' |
1687 | - 'for example, are updated in-page when they are ready.'), |
1688 | - '', |
1689 | - '', |
1690 | - ''), |
1691 | ('ajax.batch_navigator.enabled', |
1692 | 'boolean', |
1693 | ('If true, batch navigators which have been wired to do so use ajax ' |
1694 | |
1695 | === removed directory 'lib/lp/services/longpoll' |
1696 | === removed file 'lib/lp/services/longpoll/__init__.py' |
1697 | === removed directory 'lib/lp/services/longpoll/adapters' |
1698 | === removed file 'lib/lp/services/longpoll/adapters/__init__.py' |
1699 | === removed file 'lib/lp/services/longpoll/adapters/event.py' |
1700 | --- lib/lp/services/longpoll/adapters/event.py 2015-07-09 12:18:51 +0000 |
1701 | +++ lib/lp/services/longpoll/adapters/event.py 1970-01-01 00:00:00 +0000 |
1702 | @@ -1,71 +0,0 @@ |
1703 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
1704 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1705 | - |
1706 | -"""Long poll adapters.""" |
1707 | - |
1708 | -__metaclass__ = type |
1709 | -__all__ = [ |
1710 | - "generate_event_key", |
1711 | - "LongPollEvent", |
1712 | - ] |
1713 | - |
1714 | -from zope.component import getUtility |
1715 | - |
1716 | -from lp.services.messaging.interfaces import IMessageSession |
1717 | - |
1718 | - |
1719 | -def router_factory(event_key): |
1720 | - """Get a router for the given `event_key`.""" |
1721 | - return getUtility(IMessageSession).getProducer(event_key) |
1722 | - |
1723 | - |
1724 | -def generate_event_key(*components): |
1725 | - """Generate a suitable event name.""" |
1726 | - if len(components) == 0: |
1727 | - raise AssertionError( |
1728 | - "Event keys must contain at least one component.") |
1729 | - return "longpoll.event.%s" % ".".join( |
1730 | - str(component) for component in components) |
1731 | - |
1732 | - |
1733 | -class LongPollEvent: |
1734 | - """Base-class for event adapters. |
1735 | - |
1736 | - Sub-classes need to define the `event_key` property and declare something |
1737 | - along the lines of:: |
1738 | - |
1739 | - @adapter(IAwesomeThing) |
1740 | - @implementer(ILongPollEvent) |
1741 | - class LongPollAwesomeThingEvent(LongPollEvent): |
1742 | - ... |
1743 | - |
1744 | - Alternatively, use the `long_poll_event` class decorator:: |
1745 | - |
1746 | - @long_poll_event(IAwesomeThing) |
1747 | - class LongPollAwesomeThingEvent(LongPollEvent): |
1748 | - ... |
1749 | - |
1750 | - In both cases the adapter should be registered in a `configure.zcml` |
1751 | - somewhere sensible:: |
1752 | - |
1753 | - <adapter factory=".adapters.LongPollAwesomeThingEvent" /> |
1754 | - |
1755 | - """ |
1756 | - |
1757 | - def __init__(self, source): |
1758 | - self.source = source |
1759 | - |
1760 | - @property |
1761 | - def event_key(self): |
1762 | - """See `ILongPollEvent`.""" |
1763 | - raise NotImplementedError(self.__class__.event_key) |
1764 | - |
1765 | - def emit(self, **data): |
1766 | - """See `ILongPollEvent`. |
1767 | - |
1768 | - The data will be updated with `event_key`, a copy of `self.event_key`. |
1769 | - """ |
1770 | - event_key = self.event_key |
1771 | - data.update(event_key=event_key) |
1772 | - router = router_factory(event_key) |
1773 | - router.send(data) |
1774 | |
1775 | === removed file 'lib/lp/services/longpoll/adapters/storm.py' |
1776 | --- lib/lp/services/longpoll/adapters/storm.py 2011-10-05 15:46:30 +0000 |
1777 | +++ lib/lp/services/longpoll/adapters/storm.py 1970-01-01 00:00:00 +0000 |
1778 | @@ -1,116 +0,0 @@ |
1779 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
1780 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1781 | - |
1782 | -"""Long-poll life-cycle adapters.""" |
1783 | - |
1784 | -from __future__ import absolute_import |
1785 | - |
1786 | -__metaclass__ = type |
1787 | -__all__ = [] |
1788 | - |
1789 | -from lazr.lifecycle.interfaces import ( |
1790 | - IObjectCreatedEvent, |
1791 | - IObjectDeletedEvent, |
1792 | - IObjectModifiedEvent, |
1793 | - ) |
1794 | -from storm.base import Storm |
1795 | -from storm.info import ( |
1796 | - get_cls_info, |
1797 | - get_obj_info, |
1798 | - ) |
1799 | -from zope.component import adapter |
1800 | -from zope.interface.interfaces import IAttribute |
1801 | -from zope.security.proxy import removeSecurityProxy |
1802 | - |
1803 | -from lp.services.longpoll.adapters.event import ( |
1804 | - generate_event_key, |
1805 | - LongPollEvent, |
1806 | - ) |
1807 | -from lp.services.longpoll.interfaces import ( |
1808 | - ILongPollEvent, |
1809 | - long_poll_event, |
1810 | - ) |
1811 | - |
1812 | - |
1813 | -def gen_primary_key(model_instance): |
1814 | - """Generate the primary key values for the given model instance.""" |
1815 | - cls_info = get_obj_info(model_instance).cls_info |
1816 | - for primary_key_column in cls_info.primary_key: |
1817 | - yield primary_key_column.__get__(model_instance) |
1818 | - |
1819 | - |
1820 | -def get_primary_key(model_instance): |
1821 | - """Return the primary key for the given model instance. |
1822 | - |
1823 | - If the primary key contains only one value it is returned, otherwise all |
1824 | - the primary key values are returned in a tuple. |
1825 | - """ |
1826 | - pkey = tuple(gen_primary_key(model_instance)) |
1827 | - return pkey[0] if len(pkey) == 1 else pkey |
1828 | - |
1829 | - |
1830 | -@long_poll_event(Storm) |
1831 | -class LongPollStormEvent(LongPollEvent): |
1832 | - """A `ILongPollEvent` for events of `Storm` objects. |
1833 | - |
1834 | - This class knows how to construct a stable event key given a Storm object. |
1835 | - """ |
1836 | - |
1837 | - @property |
1838 | - def event_key(self): |
1839 | - """See `ILongPollEvent`. |
1840 | - |
1841 | - Constructs the key from the table name and primary key values of the |
1842 | - Storm model object. |
1843 | - """ |
1844 | - cls_info = get_obj_info(self.source).cls_info |
1845 | - return generate_event_key( |
1846 | - cls_info.table.name.lower(), |
1847 | - *gen_primary_key(self.source)) |
1848 | - |
1849 | - |
1850 | -@long_poll_event(type(Storm)) |
1851 | -class LongPollStormCreationEvent(LongPollEvent): |
1852 | - """A `ILongPollEvent` for events of `Storm` *classes*. |
1853 | - |
1854 | - This class knows how to construct a stable event key given a Storm class. |
1855 | - """ |
1856 | - |
1857 | - @property |
1858 | - def event_key(self): |
1859 | - """See `ILongPollEvent`. |
1860 | - |
1861 | - Constructs the key from the table name of the Storm class. |
1862 | - """ |
1863 | - cls_info = get_cls_info(self.source) |
1864 | - return generate_event_key( |
1865 | - cls_info.table.name.lower()) |
1866 | - |
1867 | - |
1868 | -@adapter(Storm, IObjectCreatedEvent) |
1869 | -def object_created(model_instance, object_event): |
1870 | - """Subscription handler for `Storm` creation events.""" |
1871 | - model_class = removeSecurityProxy(model_instance).__class__ |
1872 | - event = ILongPollEvent(model_class) |
1873 | - event.emit(what="created", id=get_primary_key(model_instance)) |
1874 | - |
1875 | - |
1876 | -@adapter(Storm, IObjectDeletedEvent) |
1877 | -def object_deleted(model_instance, object_event): |
1878 | - """Subscription handler for `Storm` deletion events.""" |
1879 | - event = ILongPollEvent(model_instance) |
1880 | - event.emit(what="deleted", id=get_primary_key(model_instance)) |
1881 | - |
1882 | - |
1883 | -@adapter(Storm, IObjectModifiedEvent) |
1884 | -def object_modified(model_instance, object_event): |
1885 | - """Subscription handler for `Storm` modification events.""" |
1886 | - edited_fields = object_event.edited_fields |
1887 | - if edited_fields is not None and len(edited_fields) != 0: |
1888 | - edited_field_names = sorted( |
1889 | - (field.__name__ if IAttribute.providedBy(field) else field) |
1890 | - for field in edited_fields) |
1891 | - event = ILongPollEvent(model_instance) |
1892 | - event.emit( |
1893 | - what="modified", edited_fields=edited_field_names, |
1894 | - id=get_primary_key(model_instance)) |
1895 | |
1896 | === removed file 'lib/lp/services/longpoll/adapters/subscriber.py' |
1897 | --- lib/lp/services/longpoll/adapters/subscriber.py 2015-07-09 12:18:51 +0000 |
1898 | +++ lib/lp/services/longpoll/adapters/subscriber.py 1970-01-01 00:00:00 +0000 |
1899 | @@ -1,58 +0,0 @@ |
1900 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
1901 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1902 | - |
1903 | -"""Long poll adapters.""" |
1904 | - |
1905 | -__metaclass__ = type |
1906 | -__all__ = [ |
1907 | - "generate_subscribe_key", |
1908 | - "LongPollApplicationRequestSubscriber", |
1909 | - ] |
1910 | - |
1911 | -from uuid import uuid4 |
1912 | - |
1913 | -from lazr.restful.interfaces import IJSONRequestCache |
1914 | -from zope.component import ( |
1915 | - adapter, |
1916 | - getUtility, |
1917 | - ) |
1918 | -from zope.interface import implementer |
1919 | -from zope.publisher.interfaces import IApplicationRequest |
1920 | - |
1921 | -from lp.services.config import config |
1922 | -from lp.services.longpoll.interfaces import ILongPollSubscriber |
1923 | -from lp.services.messaging.interfaces import IMessageSession |
1924 | - |
1925 | - |
1926 | -def generate_subscribe_key(): |
1927 | - """Generate a suitable new, unique, subscribe key.""" |
1928 | - return "longpoll.subscribe.%s" % uuid4() |
1929 | - |
1930 | - |
1931 | -@adapter(IApplicationRequest) |
1932 | -@implementer(ILongPollSubscriber) |
1933 | -class LongPollApplicationRequestSubscriber: |
1934 | - |
1935 | - def __init__(self, request): |
1936 | - self.request = request |
1937 | - |
1938 | - @property |
1939 | - def subscribe_key(self): |
1940 | - objects = IJSONRequestCache(self.request).objects |
1941 | - if "longpoll" in objects: |
1942 | - return objects["longpoll"]["key"] |
1943 | - return None |
1944 | - |
1945 | - def subscribe(self, event): |
1946 | - cache = IJSONRequestCache(self.request) |
1947 | - if "longpoll" not in cache.objects: |
1948 | - cache.objects["longpoll"] = { |
1949 | - "uri": config.txlongpoll.uri, |
1950 | - "key": generate_subscribe_key(), |
1951 | - "subscriptions": [], |
1952 | - } |
1953 | - session = getUtility(IMessageSession) |
1954 | - subscribe_queue = session.getConsumer(self.subscribe_key) |
1955 | - producer = session.getProducer(event.event_key) |
1956 | - producer.associateConsumer(subscribe_queue) |
1957 | - cache.objects["longpoll"]["subscriptions"].append(event.event_key) |
1958 | |
1959 | === removed directory 'lib/lp/services/longpoll/adapters/tests' |
1960 | === removed file 'lib/lp/services/longpoll/adapters/tests/__init__.py' |
1961 | === removed file 'lib/lp/services/longpoll/adapters/tests/test_event.py' |
1962 | --- lib/lp/services/longpoll/adapters/tests/test_event.py 2015-07-08 16:05:11 +0000 |
1963 | +++ lib/lp/services/longpoll/adapters/tests/test_event.py 1970-01-01 00:00:00 +0000 |
1964 | @@ -1,78 +0,0 @@ |
1965 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
1966 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1967 | - |
1968 | -"""Long-poll event adapter tests.""" |
1969 | - |
1970 | -__metaclass__ = type |
1971 | - |
1972 | -from zope.interface import implementer |
1973 | - |
1974 | -from lp.services.longpoll.adapters.event import ( |
1975 | - generate_event_key, |
1976 | - LongPollEvent, |
1977 | - ) |
1978 | -from lp.services.longpoll.interfaces import ILongPollEvent |
1979 | -from lp.services.longpoll.testing import ( |
1980 | - capture_longpoll_emissions, |
1981 | - LongPollEventRecord, |
1982 | - ) |
1983 | -from lp.testing import TestCase |
1984 | -from lp.testing.layers import LaunchpadFunctionalLayer |
1985 | -from lp.testing.matchers import Contains |
1986 | - |
1987 | - |
1988 | -@implementer(ILongPollEvent) |
1989 | -class FakeEvent(LongPollEvent): |
1990 | - |
1991 | - @property |
1992 | - def event_key(self): |
1993 | - return "event-key-%s" % self.source |
1994 | - |
1995 | - |
1996 | -class TestLongPollEvent(TestCase): |
1997 | - |
1998 | - layer = LaunchpadFunctionalLayer |
1999 | - |
2000 | - def test_interface(self): |
2001 | - event = FakeEvent("source") |
2002 | - self.assertProvides(event, ILongPollEvent) |
2003 | - |
2004 | - def test_event_key(self): |
2005 | - # event_key is not implemented in LongPollEvent; subclasses must |
2006 | - # provide it. |
2007 | - event = LongPollEvent("source") |
2008 | - self.assertRaises(NotImplementedError, getattr, event, "event_key") |
2009 | - |
2010 | - def test_emit(self): |
2011 | - # LongPollEvent.emit() sends the given data to `event_key`. |
2012 | - event = FakeEvent("source") |
2013 | - event_data = {"hello": 1234} |
2014 | - with capture_longpoll_emissions() as log: |
2015 | - event.emit(**event_data) |
2016 | - expected_message = LongPollEventRecord( |
2017 | - event_key=event.event_key, |
2018 | - data=dict(event_data, event_key=event.event_key)) |
2019 | - self.assertThat(log, Contains(expected_message)) |
2020 | - |
2021 | - |
2022 | -class TestFunctions(TestCase): |
2023 | - |
2024 | - def test_generate_event_key_no_components(self): |
2025 | - self.assertRaises( |
2026 | - AssertionError, generate_event_key) |
2027 | - |
2028 | - def test_generate_event_key(self): |
2029 | - self.assertEqual( |
2030 | - "longpoll.event.event-name", |
2031 | - generate_event_key("event-name")) |
2032 | - self.assertEqual( |
2033 | - "longpoll.event.source-name.event-name", |
2034 | - generate_event_key("source-name", "event-name")) |
2035 | - self.assertEqual( |
2036 | - "longpoll.event.type-name.source-name.event-name", |
2037 | - generate_event_key("type-name", "source-name", "event-name")) |
2038 | - |
2039 | - def test_generate_event_key_stringifies_components(self): |
2040 | - self.assertEqual( |
2041 | - "longpoll.event.job.1234.COMPLETED", |
2042 | - generate_event_key("job", 1234, "COMPLETED")) |
2043 | |
2044 | === removed file 'lib/lp/services/longpoll/adapters/tests/test_storm.py' |
2045 | --- lib/lp/services/longpoll/adapters/tests/test_storm.py 2012-01-01 02:58:52 +0000 |
2046 | +++ lib/lp/services/longpoll/adapters/tests/test_storm.py 1970-01-01 00:00:00 +0000 |
2047 | @@ -1,170 +0,0 @@ |
2048 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
2049 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2050 | - |
2051 | -"""Long-poll event adapter tests.""" |
2052 | - |
2053 | -__metaclass__ = type |
2054 | - |
2055 | -from lazr.lifecycle.event import ( |
2056 | - ObjectCreatedEvent, |
2057 | - ObjectDeletedEvent, |
2058 | - ObjectModifiedEvent, |
2059 | - ) |
2060 | -from storm.base import Storm |
2061 | -from storm.properties import Int |
2062 | -from zope.event import notify |
2063 | -from zope.interface import Attribute |
2064 | - |
2065 | -from lp.services.longpoll.adapters.storm import ( |
2066 | - gen_primary_key, |
2067 | - get_primary_key, |
2068 | - ) |
2069 | -from lp.services.longpoll.interfaces import ILongPollEvent |
2070 | -from lp.services.longpoll.testing import ( |
2071 | - capture_longpoll_emissions, |
2072 | - LongPollEventRecord, |
2073 | - ) |
2074 | -from lp.testing import TestCase |
2075 | -from lp.testing.layers import LaunchpadFunctionalLayer |
2076 | -from lp.testing.matchers import Provides |
2077 | - |
2078 | - |
2079 | -class FakeStormClass(Storm): |
2080 | - |
2081 | - __storm_table__ = 'FakeTable' |
2082 | - |
2083 | - id = Int(primary=True) |
2084 | - |
2085 | - |
2086 | -class FakeStormCompoundPrimaryKeyClass(Storm): |
2087 | - |
2088 | - __storm_table__ = 'FakeTableWithCompoundPrimaryKey' |
2089 | - __storm_primary__ = 'id1', 'id2' |
2090 | - |
2091 | - id1 = Int() |
2092 | - id2 = Int() |
2093 | - |
2094 | - |
2095 | -class TestFunctions(TestCase): |
2096 | - |
2097 | - def test_gen_primary_key(self): |
2098 | - # gen_primary_key() returns an iterable of values from the model |
2099 | - # instance's primary key. |
2100 | - storm_object = FakeStormClass() |
2101 | - storm_object.id = 1234 |
2102 | - self.assertEqual([1234], list(gen_primary_key(storm_object))) |
2103 | - |
2104 | - def test_gen_primary_key_compound_key(self): |
2105 | - # gen_primary_key() returns an iterable of values from the model |
2106 | - # instance's primary key. |
2107 | - storm_object = FakeStormCompoundPrimaryKeyClass() |
2108 | - storm_object.id1 = 1234 |
2109 | - storm_object.id2 = 5678 |
2110 | - self.assertEqual([1234, 5678], list(gen_primary_key(storm_object))) |
2111 | - |
2112 | - def test_get_primary_key(self): |
2113 | - # get_primary_key() returns the value of the model instance's primary |
2114 | - # key. |
2115 | - storm_object = FakeStormClass() |
2116 | - storm_object.id = 1234 |
2117 | - self.assertEqual(1234, get_primary_key(storm_object)) |
2118 | - |
2119 | - def test_get_primary_key_compound_key(self): |
2120 | - # get_primary_key() returns a tuple of all the values in the model |
2121 | - # instance's primary key when the model uses a compound primary key. |
2122 | - storm_object = FakeStormCompoundPrimaryKeyClass() |
2123 | - storm_object.id1 = 1234 |
2124 | - storm_object.id2 = 5678 |
2125 | - self.assertEqual((1234, 5678), get_primary_key(storm_object)) |
2126 | - |
2127 | - |
2128 | -class TestStormLifecycle(TestCase): |
2129 | - |
2130 | - layer = LaunchpadFunctionalLayer |
2131 | - |
2132 | - def test_storm_event_adapter(self): |
2133 | - storm_object = FakeStormClass() |
2134 | - storm_object.id = 1234 |
2135 | - event = ILongPollEvent(storm_object) |
2136 | - self.assertThat(event, Provides(ILongPollEvent)) |
2137 | - self.assertEqual( |
2138 | - "longpoll.event.faketable.1234", |
2139 | - event.event_key) |
2140 | - |
2141 | - def test_storm_creation_event_adapter(self): |
2142 | - event = ILongPollEvent(FakeStormClass) |
2143 | - self.assertThat(event, Provides(ILongPollEvent)) |
2144 | - self.assertEqual( |
2145 | - "longpoll.event.faketable", |
2146 | - event.event_key) |
2147 | - |
2148 | - def test_storm_object_created(self): |
2149 | - storm_object = FakeStormClass() |
2150 | - storm_object.id = 1234 |
2151 | - with capture_longpoll_emissions() as log: |
2152 | - notify(ObjectCreatedEvent(storm_object)) |
2153 | - expected = LongPollEventRecord( |
2154 | - "longpoll.event.faketable", { |
2155 | - "event_key": "longpoll.event.faketable", |
2156 | - "what": "created", |
2157 | - "id": 1234, |
2158 | - }) |
2159 | - self.assertEqual([expected], log) |
2160 | - |
2161 | - def test_storm_object_deleted(self): |
2162 | - storm_object = FakeStormClass() |
2163 | - storm_object.id = 1234 |
2164 | - with capture_longpoll_emissions() as log: |
2165 | - notify(ObjectDeletedEvent(storm_object)) |
2166 | - expected = LongPollEventRecord( |
2167 | - "longpoll.event.faketable.1234", { |
2168 | - "event_key": "longpoll.event.faketable.1234", |
2169 | - "what": "deleted", |
2170 | - "id": 1234, |
2171 | - }) |
2172 | - self.assertEqual([expected], log) |
2173 | - |
2174 | - def test_storm_object_modified(self): |
2175 | - storm_object = FakeStormClass() |
2176 | - storm_object.id = 1234 |
2177 | - with capture_longpoll_emissions() as log: |
2178 | - object_event = ObjectModifiedEvent( |
2179 | - storm_object, storm_object, ("itchy", "scratchy")) |
2180 | - notify(object_event) |
2181 | - expected = LongPollEventRecord( |
2182 | - "longpoll.event.faketable.1234", { |
2183 | - "event_key": "longpoll.event.faketable.1234", |
2184 | - "what": "modified", |
2185 | - "edited_fields": ["itchy", "scratchy"], |
2186 | - "id": 1234, |
2187 | - }) |
2188 | - self.assertEqual([expected], log) |
2189 | - |
2190 | - def test_storm_object_modified_no_edited_fields(self): |
2191 | - # A longpoll event is not emitted unless edited_fields is populated. |
2192 | - storm_object = FakeStormClass() |
2193 | - storm_object.id = 1234 |
2194 | - with capture_longpoll_emissions() as log: |
2195 | - notify(ObjectModifiedEvent(storm_object, storm_object, None)) |
2196 | - self.assertEqual([], log) |
2197 | - with capture_longpoll_emissions() as log: |
2198 | - notify(ObjectModifiedEvent(storm_object, storm_object, ())) |
2199 | - self.assertEqual([], log) |
2200 | - |
2201 | - def test_storm_object_modified_edited_fields_are_zope_attributes(self): |
2202 | - # The names of IAttribute fields in edited_fields are used in the |
2203 | - # longpoll event. |
2204 | - storm_object = FakeStormClass() |
2205 | - storm_object.id = 1234 |
2206 | - with capture_longpoll_emissions() as log: |
2207 | - object_event = ObjectModifiedEvent( |
2208 | - storm_object, storm_object, ("foo", Attribute("bar"))) |
2209 | - notify(object_event) |
2210 | - expected = LongPollEventRecord( |
2211 | - "longpoll.event.faketable.1234", { |
2212 | - "event_key": "longpoll.event.faketable.1234", |
2213 | - "what": "modified", |
2214 | - "edited_fields": ["bar", "foo"], |
2215 | - "id": 1234, |
2216 | - }) |
2217 | - self.assertEqual([expected], log) |
2218 | |
2219 | === removed file 'lib/lp/services/longpoll/adapters/tests/test_subscriber.py' |
2220 | --- lib/lp/services/longpoll/adapters/tests/test_subscriber.py 2015-07-08 16:05:11 +0000 |
2221 | +++ lib/lp/services/longpoll/adapters/tests/test_subscriber.py 1970-01-01 00:00:00 +0000 |
2222 | @@ -1,137 +0,0 @@ |
2223 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
2224 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2225 | - |
2226 | -"""Long-poll subscriber adapter tests.""" |
2227 | - |
2228 | -__metaclass__ = type |
2229 | - |
2230 | -from itertools import count |
2231 | - |
2232 | -from lazr.restful.interfaces import IJSONRequestCache |
2233 | -from testtools.matchers import ( |
2234 | - Not, |
2235 | - StartsWith, |
2236 | - ) |
2237 | -from zope.component import getUtility |
2238 | -from zope.interface import implementer |
2239 | - |
2240 | -from lp.services.longpoll.adapters.subscriber import ( |
2241 | - generate_subscribe_key, |
2242 | - LongPollApplicationRequestSubscriber, |
2243 | - ) |
2244 | -from lp.services.longpoll.interfaces import ( |
2245 | - ILongPollEvent, |
2246 | - ILongPollSubscriber, |
2247 | - ) |
2248 | -from lp.services.messaging.interfaces import IMessageSession |
2249 | -from lp.services.webapp.servers import LaunchpadTestRequest |
2250 | -from lp.testing import TestCase |
2251 | -from lp.testing.layers import LaunchpadFunctionalLayer |
2252 | -from lp.testing.matchers import Contains |
2253 | - |
2254 | - |
2255 | -@implementer(ILongPollEvent) |
2256 | -class FakeEvent: |
2257 | - |
2258 | - event_key_indexes = count(1) |
2259 | - |
2260 | - def __init__(self): |
2261 | - self.event_key = "event-key-%d" % next(self.event_key_indexes) |
2262 | - |
2263 | - |
2264 | -class TestLongPollSubscriber(TestCase): |
2265 | - |
2266 | - layer = LaunchpadFunctionalLayer |
2267 | - |
2268 | - def test_interface(self): |
2269 | - request = LaunchpadTestRequest() |
2270 | - subscriber = LongPollApplicationRequestSubscriber(request) |
2271 | - self.assertProvides(subscriber, ILongPollSubscriber) |
2272 | - |
2273 | - def test_subscribe_key(self): |
2274 | - request = LaunchpadTestRequest() |
2275 | - subscriber = LongPollApplicationRequestSubscriber(request) |
2276 | - # A subscribe key is not generated yet. |
2277 | - self.assertIs(subscriber.subscribe_key, None) |
2278 | - # It it only generated on the first subscription. |
2279 | - subscriber.subscribe(FakeEvent()) |
2280 | - subscribe_key = subscriber.subscribe_key |
2281 | - self.assertIsInstance(subscribe_key, str) |
2282 | - self.assertNotEqual(0, len(subscribe_key)) |
2283 | - # It remains the same for later subscriptions. |
2284 | - subscriber.subscribe(FakeEvent()) |
2285 | - self.assertEqual(subscribe_key, subscriber.subscribe_key) |
2286 | - |
2287 | - def test_adapter(self): |
2288 | - request = LaunchpadTestRequest() |
2289 | - subscriber = ILongPollSubscriber(request) |
2290 | - self.assertIsInstance( |
2291 | - subscriber, LongPollApplicationRequestSubscriber) |
2292 | - # A difference subscriber is returned on subsequent adaptions, but it |
2293 | - # has the same subscribe_key. |
2294 | - subscriber2 = ILongPollSubscriber(request) |
2295 | - self.assertIsNot(subscriber, subscriber2) |
2296 | - self.assertEqual(subscriber.subscribe_key, subscriber2.subscribe_key) |
2297 | - |
2298 | - def test_subscribe_queue(self): |
2299 | - # LongPollApplicationRequestSubscriber.subscribe() creates a new queue |
2300 | - # with a new unique name that is bound to the event's event_key. |
2301 | - request = LaunchpadTestRequest() |
2302 | - event = FakeEvent() |
2303 | - subscriber = ILongPollSubscriber(request) |
2304 | - subscriber.subscribe(event) |
2305 | - message = '{"hello": 1234}' |
2306 | - session = getUtility(IMessageSession) |
2307 | - routing_key = session.getProducer(event.event_key) |
2308 | - routing_key.send(message) |
2309 | - session.flush() |
2310 | - subscribe_queue = session.getConsumer(subscriber.subscribe_key) |
2311 | - self.assertEqual( |
2312 | - message, subscribe_queue.receive(timeout=5)) |
2313 | - |
2314 | - def test_json_cache_not_populated_on_init(self): |
2315 | - # LongPollApplicationRequestSubscriber does not put the name of the |
2316 | - # new queue into the JSON cache. |
2317 | - request = LaunchpadTestRequest() |
2318 | - cache = IJSONRequestCache(request) |
2319 | - self.assertThat(cache.objects, Not(Contains("longpoll"))) |
2320 | - ILongPollSubscriber(request) |
2321 | - self.assertThat(cache.objects, Not(Contains("longpoll"))) |
2322 | - |
2323 | - def test_longpoll_uri_config(self): |
2324 | - # The JSON cache contains config.txlongpoll.uri. |
2325 | - self.pushConfig("txlongpoll", uri="/+longpoll/") |
2326 | - request = LaunchpadTestRequest() |
2327 | - cache = IJSONRequestCache(request) |
2328 | - ILongPollSubscriber(request).subscribe(FakeEvent()) |
2329 | - self.assertEqual('/+longpoll/', cache.objects["longpoll"]["uri"]) |
2330 | - |
2331 | - def test_json_cache_populated_on_subscribe(self): |
2332 | - # To aid with debugging the event_key of subscriptions are added to |
2333 | - # the JSON cache. |
2334 | - request = LaunchpadTestRequest() |
2335 | - cache = IJSONRequestCache(request) |
2336 | - event1 = FakeEvent() |
2337 | - ILongPollSubscriber(request).subscribe(event1) # Side-effects! |
2338 | - self.assertThat(cache.objects, Contains("longpoll")) |
2339 | - self.assertThat(cache.objects["longpoll"], Contains("key")) |
2340 | - self.assertThat(cache.objects["longpoll"], Contains("subscriptions")) |
2341 | - self.assertEqual( |
2342 | - [event1.event_key], |
2343 | - cache.objects["longpoll"]["subscriptions"]) |
2344 | - # More events can be subscribed. |
2345 | - event2 = FakeEvent() |
2346 | - ILongPollSubscriber(request).subscribe(event2) |
2347 | - self.assertEqual( |
2348 | - [event1.event_key, event2.event_key], |
2349 | - cache.objects["longpoll"]["subscriptions"]) |
2350 | - |
2351 | - |
2352 | -class TestFunctions(TestCase): |
2353 | - |
2354 | - def test_generate_subscribe_key(self): |
2355 | - subscribe_key = generate_subscribe_key() |
2356 | - expected_prefix = "longpoll.subscribe." |
2357 | - self.assertThat(subscribe_key, StartsWith(expected_prefix)) |
2358 | - # The key contains a 36 character UUID. |
2359 | - self.assertEqual(len(expected_prefix) + 36, len(subscribe_key)) |
2360 | |
2361 | === removed file 'lib/lp/services/longpoll/configure.zcml' |
2362 | --- lib/lp/services/longpoll/configure.zcml 2011-10-05 15:14:53 +0000 |
2363 | +++ lib/lp/services/longpoll/configure.zcml 1970-01-01 00:00:00 +0000 |
2364 | @@ -1,15 +0,0 @@ |
2365 | -<!-- Copyright 2011 Canonical Ltd. This software is licensed under the |
2366 | - GNU Affero General Public License version 3 (see the file LICENSE). |
2367 | ---> |
2368 | -<configure |
2369 | - xmlns="http://namespaces.zope.org/zope" |
2370 | - xmlns:browser="http://namespaces.zope.org/browser" |
2371 | - xmlns:i18n="http://namespaces.zope.org/i18n" |
2372 | - i18n_domain="launchpad"> |
2373 | - <adapter factory=".adapters.storm.LongPollStormEvent" /> |
2374 | - <adapter factory=".adapters.storm.LongPollStormCreationEvent" /> |
2375 | - <adapter factory=".adapters.subscriber.LongPollApplicationRequestSubscriber" /> |
2376 | - <subscriber handler=".adapters.storm.object_created" /> |
2377 | - <subscriber handler=".adapters.storm.object_deleted" /> |
2378 | - <subscriber handler=".adapters.storm.object_modified" /> |
2379 | -</configure> |
2380 | |
2381 | === removed file 'lib/lp/services/longpoll/interfaces.py' |
2382 | --- lib/lp/services/longpoll/interfaces.py 2011-09-20 19:04:19 +0000 |
2383 | +++ lib/lp/services/longpoll/interfaces.py 1970-01-01 00:00:00 +0000 |
2384 | @@ -1,64 +0,0 @@ |
2385 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
2386 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2387 | - |
2388 | -"""Long-poll infrastructure interfaces.""" |
2389 | - |
2390 | -__metaclass__ = type |
2391 | -__all__ = [ |
2392 | - "ILongPollEvent", |
2393 | - "ILongPollSubscriber", |
2394 | - "long_poll_event", |
2395 | - ] |
2396 | - |
2397 | -from zope.component import adapter |
2398 | -from zope.interface import ( |
2399 | - Attribute, |
2400 | - classImplements, |
2401 | - Interface, |
2402 | - ) |
2403 | - |
2404 | - |
2405 | -class ILongPollEvent(Interface): |
2406 | - |
2407 | - source = Attribute("The event source.") |
2408 | - |
2409 | - event_key = Attribute( |
2410 | - "The key with which events will be emitted. Should be predictable " |
2411 | - "and stable.") |
2412 | - |
2413 | - def emit(**data): |
2414 | - """Emit the given data to `event_key`. |
2415 | - |
2416 | - :param data: Any data structures that can be dumped as JSON. |
2417 | - """ |
2418 | - |
2419 | - |
2420 | -class ILongPollSubscriber(Interface): |
2421 | - |
2422 | - subscribe_key = Attribute( |
2423 | - "The key which the subscriber must know in order to be able " |
2424 | - "to long-poll for subscribed events. Should be infeasible to " |
2425 | - "guess, a UUID for example.") |
2426 | - |
2427 | - def subscribe(event): |
2428 | - """Subscribe to the given event. |
2429 | - |
2430 | - :type event: ILongPollEvent |
2431 | - """ |
2432 | - |
2433 | - |
2434 | -def long_poll_event(source_spec): |
2435 | - """Class decorator to declare an `ILongPollEvent`. |
2436 | - |
2437 | - :param source_spec: An interface or other specification understood by |
2438 | - `zope.component` (a plain class can be passed too) that defines the |
2439 | - source of an event. `IJob` or `storm.base.Storm` for example. |
2440 | - """ |
2441 | - declare_adapter = adapter(source_spec) |
2442 | - |
2443 | - def declare_event(cls): |
2444 | - classImplements(cls, ILongPollEvent) |
2445 | - declare_adapter(cls) |
2446 | - return cls |
2447 | - |
2448 | - return declare_event |
2449 | |
2450 | === removed file 'lib/lp/services/longpoll/testing.py' |
2451 | --- lib/lp/services/longpoll/testing.py 2011-09-23 16:36:56 +0000 |
2452 | +++ lib/lp/services/longpoll/testing.py 1970-01-01 00:00:00 +0000 |
2453 | @@ -1,57 +0,0 @@ |
2454 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
2455 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2456 | - |
2457 | -"""Things that help with testing of longpoll.""" |
2458 | - |
2459 | -__metaclass__ = type |
2460 | -__all__ = [ |
2461 | - "capture_longpoll_emissions", |
2462 | - "LongPollEventRecord", |
2463 | - ] |
2464 | - |
2465 | -from collections import namedtuple |
2466 | -from contextlib import contextmanager |
2467 | -from functools import partial |
2468 | - |
2469 | -from lp.services.longpoll.adapters import event |
2470 | - |
2471 | - |
2472 | -LongPollEventRecord = namedtuple( |
2473 | - "LongPollEventRecord", ("event_key", "data")) |
2474 | - |
2475 | - |
2476 | -class LoggingRouter: |
2477 | - """A test double for `IMessageProducer`. |
2478 | - |
2479 | - Saves messages as `LongPollEventRecord` tuples to a log. |
2480 | - |
2481 | - :param log: A callable accepting a single `LongPollEventRecord`. |
2482 | - :param routing_key: See `IMessageSession.getProducer`. |
2483 | - """ |
2484 | - |
2485 | - def __init__(self, log, routing_key): |
2486 | - self.log = log |
2487 | - self.routing_key = routing_key |
2488 | - |
2489 | - def send(self, data): |
2490 | - record = LongPollEventRecord(self.routing_key, data) |
2491 | - self.log(record) |
2492 | - |
2493 | - |
2494 | -@contextmanager |
2495 | -def capture_longpoll_emissions(): |
2496 | - """Capture longpoll emissions while this context is in force. |
2497 | - |
2498 | - This returns a list in which `LongPollEventRecord` tuples will be |
2499 | - recorded, in the order they're emitted. |
2500 | - |
2501 | - Note that normal event emission is *suppressed globally* while this |
2502 | - context is in force; *all* events will be stored in the log. |
2503 | - """ |
2504 | - log = [] |
2505 | - original_router_factory = event.router_factory |
2506 | - event.router_factory = partial(LoggingRouter, log.append) |
2507 | - try: |
2508 | - yield log |
2509 | - finally: |
2510 | - event.router_factory = original_router_factory |
2511 | |
2512 | === removed directory 'lib/lp/services/longpoll/tests' |
2513 | === removed file 'lib/lp/services/longpoll/tests/__init__.py' |
2514 | === removed file 'lib/lp/services/longpoll/tests/test_interfaces.py' |
2515 | --- lib/lp/services/longpoll/tests/test_interfaces.py 2011-09-20 19:04:19 +0000 |
2516 | +++ lib/lp/services/longpoll/tests/test_interfaces.py 1970-01-01 00:00:00 +0000 |
2517 | @@ -1,33 +0,0 @@ |
2518 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
2519 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2520 | - |
2521 | -"""Long-poll interface tests.""" |
2522 | - |
2523 | -__metaclass__ = type |
2524 | - |
2525 | -from zope.component import adaptedBy |
2526 | -from zope.interface import Interface |
2527 | - |
2528 | -from lp.services.longpoll.interfaces import ( |
2529 | - ILongPollEvent, |
2530 | - long_poll_event, |
2531 | - ) |
2532 | -from lp.testing import TestCase |
2533 | - |
2534 | - |
2535 | -class IEventSourceInterface(Interface): |
2536 | - """Test interface for an event source.""" |
2537 | - |
2538 | - |
2539 | -class TestLongPollInterfaces(TestCase): |
2540 | - |
2541 | - def test_long_poll_event(self): |
2542 | - # long_poll_event is a class decorator that declares a class as an |
2543 | - # ILongPollEvent. |
2544 | - @long_poll_event(IEventSourceInterface) |
2545 | - class Something: |
2546 | - """An example event source.""" |
2547 | - self.assertTrue(ILongPollEvent.implementedBy(Something)) |
2548 | - self.assertEqual( |
2549 | - (IEventSourceInterface,), |
2550 | - adaptedBy(Something)) |
2551 | |
2552 | === removed directory 'lib/lp/services/txlongpoll' |
2553 | === removed file 'lib/lp/services/txlongpoll/__init__.py' |
2554 | === removed file 'lib/lp/services/txlongpoll/server.py' |
2555 | --- lib/lp/services/txlongpoll/server.py 2011-09-30 07:28:45 +0000 |
2556 | +++ lib/lp/services/txlongpoll/server.py 1970-01-01 00:00:00 +0000 |
2557 | @@ -1,31 +0,0 @@ |
2558 | -# Copyright 2011 Canonical Ltd. This software is licensed under the |
2559 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2560 | - |
2561 | -"""TxLongPoll server fixture.""" |
2562 | - |
2563 | -__metaclass__ = type |
2564 | -__all__ = [ |
2565 | - 'TxLongPollServer', |
2566 | - ] |
2567 | - |
2568 | -from textwrap import dedent |
2569 | - |
2570 | -from txlongpollfixture.server import TxLongPollFixture |
2571 | - |
2572 | - |
2573 | -class TxLongPollServer(TxLongPollFixture): |
2574 | - """A TxLongPoll server fixture with Launchpad-specific config. |
2575 | - |
2576 | - :ivar service_config: A snippet of .ini that describes the `txlongpoll` |
2577 | - configuration. |
2578 | - """ |
2579 | - |
2580 | - def setUp(self): |
2581 | - super(TxLongPollServer, self).setUp() |
2582 | - setattr( |
2583 | - self, 'service_config', |
2584 | - dedent("""\ |
2585 | - [txlongpoll] |
2586 | - frontend_port: %d |
2587 | - """ % ( |
2588 | - self.config.frontend_port))) |
2589 | |
2590 | === removed directory 'lib/lp/services/txlongpoll/tests' |
2591 | === removed file 'lib/lp/services/txlongpoll/tests/__init__.py' |
2592 | === removed file 'lib/lp/services/txlongpoll/tests/test_server.py' |
2593 | --- lib/lp/services/txlongpoll/tests/test_server.py 2017-09-23 03:13:41 +0000 |
2594 | +++ lib/lp/services/txlongpoll/tests/test_server.py 1970-01-01 00:00:00 +0000 |
2595 | @@ -1,38 +0,0 @@ |
2596 | -# Copyright 2011-2017 Canonical Ltd. This software is licensed under the |
2597 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2598 | - |
2599 | -"""Tests for lp.services.rabbit.TxLongPollServer.""" |
2600 | - |
2601 | -__metaclass__ = type |
2602 | - |
2603 | -from ConfigParser import SafeConfigParser |
2604 | -import os |
2605 | -from StringIO import StringIO |
2606 | - |
2607 | -from lp.services.config import config |
2608 | -from lp.services.txlongpoll.server import TxLongPollServer |
2609 | -from lp.testing import TestCase |
2610 | -from lp.testing.layers import RabbitMQLayer |
2611 | - |
2612 | - |
2613 | -class TestTxLongPollServer(TestCase): |
2614 | - |
2615 | - layer = RabbitMQLayer |
2616 | - |
2617 | - def test_service_config(self): |
2618 | - # TxLongPollServer pokes some .ini configuration into its |
2619 | - # service_config attributes. |
2620 | - twistd_bin = os.path.join(config.root, 'bin', 'twistd') |
2621 | - fixture = self.useFixture(TxLongPollServer( |
2622 | - broker_user='guest', broker_password='guest', broker_vhost='/', |
2623 | - broker_port=123, frontend_port=None, |
2624 | - twistd_bin=twistd_bin)) |
2625 | - service_config = SafeConfigParser() |
2626 | - service_config.readfp(StringIO(getattr(fixture, 'service_config'))) |
2627 | - self.assertEqual(["txlongpoll"], service_config.sections()) |
2628 | - # txlongpoll section |
2629 | - expected = { |
2630 | - "frontend_port": "%d" % fixture.config.frontend_port, |
2631 | - } |
2632 | - observed = dict(service_config.items("txlongpoll")) |
2633 | - self.assertEqual(expected, observed) |
2634 | |
2635 | === modified file 'setup.py' |
2636 | --- setup.py 2018-07-16 10:51:04 +0000 |
2637 | +++ setup.py 2019-04-16 14:35:10 +0000 |
2638 | @@ -227,8 +227,6 @@ |
2639 | 'treq', |
2640 | 'Twisted[conch,tls]', |
2641 | 'txfixtures', |
2642 | - 'txlongpoll', |
2643 | - 'txlongpollfixture', |
2644 | 'txpkgupload', |
2645 | 'virtualenv-tools3', |
2646 | 'wadllib', |