Merge lp:~cjwatson/launchpad/remove-txlongpoll into lp:launchpad

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
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',