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