Merge lp:~raoul-snyman/openlp/better-threading into lp:openlp
- better-threading
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~raoul-snyman/openlp/better-threading |
Merge into: | lp:openlp |
Diff against target: |
3036 lines (+638/-970) 26 files modified
openlp/core/api/deploy.py (+4/-4) openlp/core/api/http/server.py (+19/-20) openlp/core/api/websockets.py (+72/-89) openlp/core/app.py (+2/-5) openlp/core/common/applocation.py (+1/-1) openlp/core/common/httputils.py (+8/-7) openlp/core/lib/imagemanager.py (+22/-13) openlp/core/projectors/manager.py (+1/-2) openlp/core/projectors/pjlink.py (+2/-2) openlp/core/threading.py (+70/-14) openlp/core/ui/firsttimeform.py (+36/-39) openlp/core/ui/mainwindow.py (+44/-36) openlp/core/ui/media/systemplayer.py (+14/-14) openlp/core/version.py (+6/-7) openlp/plugins/songs/forms/songselectform.py (+9/-31) tests/functional/openlp_core/api/http/test_http.py (+19/-19) tests/functional/openlp_core/api/test_websockets.py (+10/-10) tests/functional/openlp_core/common/test_httputils.py (+2/-2) tests/functional/openlp_core/lib/test_image_manager.py (+148/-53) tests/functional/openlp_core/test_app.py (+18/-12) tests/functional/openlp_core/test_threading.py (+89/-0) tests/functional/openlp_core/ui/media/test_systemplayer.py (+0/-549) tests/functional/openlp_core/ui/test_firsttimeform.py (+15/-15) tests/functional/openlp_core/ui/test_mainwindow.py (+11/-10) tests/functional/openlp_plugins/songs/test_songselect.py (+2/-2) tests/interfaces/openlp_core/ui/test_mainwindow.py (+14/-14) |
To merge this branch: | bzr merge lp:~raoul-snyman/openlp/better-threading |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Phill | Needs Fixing | ||
Tim Bentley | Needs Fixing | ||
Review via email: mp+335801@code.launchpad.net |
This proposal has been superseded by a proposal from 2018-01-07.
Commit message
Description of the change
Major overhaul of how threading in OpenLP works. Rather than messing around with threads yourself, you create a worker object descended from ThreadWorker, implement start() (and stop() if it's a long-running thread), and run it using run_thread().
Changes related to thread API:
- WebSocket was refactored (mostly into the worker)
- HttpServer was refactored a bit
- CheckMediaWorker was refactored a bit
- Version check refactored
- SongSelect search refactored
- New _wait_for_threads() method in MainWindow
- Tidied up closeEvent in MainWindow a bit
Bugs fixed:
- Logs have returned to the cache dir when XDG is around
- Flipped the --no-web-server flag (now False is off, not on)
- Fixed a call to reload_bibles()
Other things done:
- Removed the --style option (it never worked)
- Renamed "url_get_file() to download_file()
- Standardised a callback object for download_file()
Add this to your merge proposal:
-------
lp:~raoul-snyman/openlp/better-threading (revision 2803)
https:/
https:/
https:/
https:/
https:/
https:/
https:/
https:/
Stopping after failure
Failed builds:
- Branch-
Phill (phill-ridout) wrote : | # |
Just a few minor things
Raoul Snyman (raoul-snyman) wrote : | # |
Replied to Tim's questions.
Raoul Snyman (raoul-snyman) : | # |
Raoul Snyman (raoul-snyman) : | # |
- 2804. By Raoul Snyman
-
Fix some issues highlighted by Tim and Phill, and added a file that was erroneously removed
- 2805. By Raoul Snyman
-
Fix the tests I now added back in
Unmerged revisions
Preview Diff
1 | === modified file 'openlp/core/api/deploy.py' |
2 | --- openlp/core/api/deploy.py 2017-12-29 09:15:48 +0000 |
3 | +++ openlp/core/api/deploy.py 2018-01-07 18:07:40 +0000 |
4 | @@ -25,7 +25,7 @@ |
5 | from zipfile import ZipFile |
6 | |
7 | from openlp.core.common.applocation import AppLocation |
8 | -from openlp.core.common.httputils import url_get_file, get_web_page, get_url_file_size |
9 | +from openlp.core.common.httputils import download_file, get_web_page, get_url_file_size |
10 | from openlp.core.common.registry import Registry |
11 | |
12 | |
13 | @@ -65,7 +65,7 @@ |
14 | sha256, version = download_sha256() |
15 | file_size = get_url_file_size('https://get.openlp.org/webclient/site.zip') |
16 | callback.setRange(0, file_size) |
17 | - if url_get_file(callback, 'https://get.openlp.org/webclient/site.zip', |
18 | - AppLocation.get_section_data_path('remotes') / 'site.zip', |
19 | - sha256=sha256): |
20 | + if download_file(callback, 'https://get.openlp.org/webclient/site.zip', |
21 | + AppLocation.get_section_data_path('remotes') / 'site.zip', |
22 | + sha256=sha256): |
23 | deploy_zipfile(AppLocation.get_section_data_path('remotes'), 'site.zip') |
24 | |
25 | === modified file 'openlp/core/api/http/server.py' |
26 | --- openlp/core/api/http/server.py 2017-12-29 09:15:48 +0000 |
27 | +++ openlp/core/api/http/server.py 2018-01-07 18:07:40 +0000 |
28 | @@ -27,7 +27,7 @@ |
29 | import time |
30 | |
31 | from PyQt5 import QtCore, QtWidgets |
32 | -from waitress import serve |
33 | +from waitress.server import create_server |
34 | |
35 | from openlp.core.api.deploy import download_and_check, download_sha256 |
36 | from openlp.core.api.endpoint.controller import controller_endpoint, api_controller_endpoint |
37 | @@ -44,23 +44,16 @@ |
38 | from openlp.core.common.path import create_paths |
39 | from openlp.core.common.registry import Registry, RegistryBase |
40 | from openlp.core.common.settings import Settings |
41 | +from openlp.core.threading import ThreadWorker, run_thread |
42 | |
43 | log = logging.getLogger(__name__) |
44 | |
45 | |
46 | -class HttpWorker(QtCore.QObject): |
47 | +class HttpWorker(ThreadWorker): |
48 | """ |
49 | A special Qt thread class to allow the HTTP server to run at the same time as the UI. |
50 | """ |
51 | - def __init__(self): |
52 | - """ |
53 | - Constructor for the thread class. |
54 | - |
55 | - :param server: The http server class. |
56 | - """ |
57 | - super(HttpWorker, self).__init__() |
58 | - |
59 | - def run(self): |
60 | + def start(self): |
61 | """ |
62 | Run the thread. |
63 | """ |
64 | @@ -68,12 +61,21 @@ |
65 | port = Settings().value('api/port') |
66 | Registry().execute('get_website_version') |
67 | try: |
68 | - serve(application, host=address, port=port) |
69 | + self.server = create_server(application, host=address, port=port) |
70 | + self.server.run() |
71 | except OSError: |
72 | log.exception('An error occurred when serving the application.') |
73 | + self.quit.emit() |
74 | |
75 | def stop(self): |
76 | - pass |
77 | + """ |
78 | + A method to stop the worker |
79 | + """ |
80 | + if hasattr(self, 'server'): |
81 | + # Loop through all the channels and close them to stop the server |
82 | + for channel in self.server._map.values(): |
83 | + if hasattr(channel, 'close'): |
84 | + channel.close() |
85 | |
86 | |
87 | class HttpServer(RegistryBase, RegistryProperties, LogMixin): |
88 | @@ -85,12 +87,9 @@ |
89 | Initialise the http server, and start the http server |
90 | """ |
91 | super(HttpServer, self).__init__(parent) |
92 | - if Registry().get_flag('no_web_server'): |
93 | - self.worker = HttpWorker() |
94 | - self.thread = QtCore.QThread() |
95 | - self.worker.moveToThread(self.thread) |
96 | - self.thread.started.connect(self.worker.run) |
97 | - self.thread.start() |
98 | + if not Registry().get_flag('no_web_server'): |
99 | + worker = HttpWorker() |
100 | + run_thread(worker, 'http_server') |
101 | Registry().register_function('download_website', self.first_time) |
102 | Registry().register_function('get_website_version', self.website_version) |
103 | Registry().set_flag('website_version', '0.0') |
104 | @@ -167,7 +166,7 @@ |
105 | self.was_cancelled = False |
106 | self.previous_size = 0 |
107 | |
108 | - def _download_progress(self, count, block_size): |
109 | + def update_progress(self, count, block_size): |
110 | """ |
111 | Calculate and display the download progress. |
112 | """ |
113 | |
114 | === modified file 'openlp/core/api/websockets.py' |
115 | --- openlp/core/api/websockets.py 2017-12-29 09:15:48 +0000 |
116 | +++ openlp/core/api/websockets.py 2018-01-07 18:07:40 +0000 |
117 | @@ -28,37 +28,88 @@ |
118 | import logging |
119 | import time |
120 | |
121 | -import websockets |
122 | -from PyQt5 import QtCore |
123 | +from websockets import serve |
124 | |
125 | from openlp.core.common.mixins import LogMixin, RegistryProperties |
126 | from openlp.core.common.registry import Registry |
127 | from openlp.core.common.settings import Settings |
128 | +from openlp.core.threading import ThreadWorker, run_thread |
129 | |
130 | log = logging.getLogger(__name__) |
131 | |
132 | |
133 | -class WebSocketWorker(QtCore.QObject): |
134 | +async def handle_websocket(request, path): |
135 | + """ |
136 | + Handle web socket requests and return the poll information |
137 | + |
138 | + Check every 0.2 seconds to get the latest position and send if it changed. This only gets triggered when the first |
139 | + client connects. |
140 | + |
141 | + :param request: request from client |
142 | + :param path: determines the endpoints supported |
143 | + """ |
144 | + log.debug('WebSocket handler registered with client') |
145 | + previous_poll = None |
146 | + previous_main_poll = None |
147 | + poller = Registry().get('poller') |
148 | + if path == '/state': |
149 | + while True: |
150 | + current_poll = poller.poll() |
151 | + if current_poll != previous_poll: |
152 | + await request.send(json.dumps(current_poll).encode()) |
153 | + previous_poll = current_poll |
154 | + await asyncio.sleep(0.2) |
155 | + elif path == '/live_changed': |
156 | + while True: |
157 | + main_poll = poller.main_poll() |
158 | + if main_poll != previous_main_poll: |
159 | + await request.send(main_poll) |
160 | + previous_main_poll = main_poll |
161 | + await asyncio.sleep(0.2) |
162 | + |
163 | + |
164 | +class WebSocketWorker(ThreadWorker, RegistryProperties, LogMixin): |
165 | """ |
166 | A special Qt thread class to allow the WebSockets server to run at the same time as the UI. |
167 | """ |
168 | - def __init__(self, server): |
169 | - """ |
170 | - Constructor for the thread class. |
171 | - |
172 | - :param server: The http server class. |
173 | - """ |
174 | - self.ws_server = server |
175 | - super(WebSocketWorker, self).__init__() |
176 | - |
177 | - def run(self): |
178 | - """ |
179 | - Run the thread. |
180 | - """ |
181 | - self.ws_server.start_server() |
182 | + def start(self): |
183 | + """ |
184 | + Run the worker. |
185 | + """ |
186 | + address = Settings().value('api/ip address') |
187 | + port = Settings().value('api/websocket port') |
188 | + # Start the event loop |
189 | + self.event_loop = asyncio.new_event_loop() |
190 | + asyncio.set_event_loop(self.event_loop) |
191 | + # Create the websocker server |
192 | + loop = 1 |
193 | + self.server = None |
194 | + while not self.server: |
195 | + try: |
196 | + self.server = serve(handle_websocket, address, port) |
197 | + log.debug('WebSocket server started on {addr}:{port}'.format(addr=address, port=port)) |
198 | + except Exception: |
199 | + log.exception('Failed to start WebSocket server') |
200 | + loop += 1 |
201 | + time.sleep(0.1) |
202 | + if not self.server and loop > 3: |
203 | + log.error('Unable to start WebSocket server {addr}:{port}, giving up'.format(addr=address, port=port)) |
204 | + if self.server: |
205 | + # If the websocket server exists, start listening |
206 | + self.event_loop.run_until_complete(self.server) |
207 | + self.event_loop.run_forever() |
208 | + self.quit.emit() |
209 | |
210 | def stop(self): |
211 | - self.ws_server.stop = True |
212 | + """ |
213 | + Stop the websocket server |
214 | + """ |
215 | + if hasattr(self.server, 'ws_server'): |
216 | + self.server.ws_server.close() |
217 | + elif hasattr(self.server, 'server'): |
218 | + self.server.server.close() |
219 | + self.event_loop.stop() |
220 | + self.event_loop.close() |
221 | |
222 | |
223 | class WebSocketServer(RegistryProperties, LogMixin): |
224 | @@ -70,74 +121,6 @@ |
225 | Initialise and start the WebSockets server |
226 | """ |
227 | super(WebSocketServer, self).__init__() |
228 | - if Registry().get_flag('no_web_server'): |
229 | - self.settings_section = 'api' |
230 | - self.worker = WebSocketWorker(self) |
231 | - self.thread = QtCore.QThread() |
232 | - self.worker.moveToThread(self.thread) |
233 | - self.thread.started.connect(self.worker.run) |
234 | - self.thread.start() |
235 | - |
236 | - def start_server(self): |
237 | - """ |
238 | - Start the correct server and save the handler |
239 | - """ |
240 | - address = Settings().value(self.settings_section + '/ip address') |
241 | - port = Settings().value(self.settings_section + '/websocket port') |
242 | - self.start_websocket_instance(address, port) |
243 | - # If web socket server start listening |
244 | - if hasattr(self, 'ws_server') and self.ws_server: |
245 | - event_loop = asyncio.new_event_loop() |
246 | - asyncio.set_event_loop(event_loop) |
247 | - event_loop.run_until_complete(self.ws_server) |
248 | - event_loop.run_forever() |
249 | - else: |
250 | - log.debug('Failed to start ws server on port {port}'.format(port=port)) |
251 | - |
252 | - def start_websocket_instance(self, address, port): |
253 | - """ |
254 | - Start the server |
255 | - |
256 | - :param address: The server address |
257 | - :param port: The run port |
258 | - """ |
259 | - loop = 1 |
260 | - while loop < 4: |
261 | - try: |
262 | - self.ws_server = websockets.serve(self.handle_websocket, address, port) |
263 | - log.debug("Web Socket Server started for class {address} {port}".format(address=address, port=port)) |
264 | - break |
265 | - except Exception as e: |
266 | - log.error('Failed to start ws server {why}'.format(why=e)) |
267 | - loop += 1 |
268 | - time.sleep(0.1) |
269 | - |
270 | - @staticmethod |
271 | - async def handle_websocket(request, path): |
272 | - """ |
273 | - Handle web socket requests and return the poll information. |
274 | - Check ever 0.2 seconds to get the latest position and send if changed. |
275 | - Only gets triggered when 1st client attaches |
276 | - |
277 | - :param request: request from client |
278 | - :param path: determines the endpoints supported |
279 | - :return: |
280 | - """ |
281 | - log.debug("web socket handler registered with client") |
282 | - previous_poll = None |
283 | - previous_main_poll = None |
284 | - poller = Registry().get('poller') |
285 | - if path == '/state': |
286 | - while True: |
287 | - current_poll = poller.poll() |
288 | - if current_poll != previous_poll: |
289 | - await request.send(json.dumps(current_poll).encode()) |
290 | - previous_poll = current_poll |
291 | - await asyncio.sleep(0.2) |
292 | - elif path == '/live_changed': |
293 | - while True: |
294 | - main_poll = poller.main_poll() |
295 | - if main_poll != previous_main_poll: |
296 | - await request.send(main_poll) |
297 | - previous_main_poll = main_poll |
298 | - await asyncio.sleep(0.2) |
299 | + if not Registry().get_flag('no_web_server'): |
300 | + worker = WebSocketWorker() |
301 | + run_thread(worker, 'websocket_server') |
302 | |
303 | === modified file 'openlp/core/app.py' |
304 | --- openlp/core/app.py 2017-12-29 09:15:48 +0000 |
305 | +++ openlp/core/app.py 2018-01-07 18:07:40 +0000 |
306 | @@ -304,8 +304,7 @@ |
307 | 'off a USB flash drive (not implemented).') |
308 | parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true', |
309 | help='Ignore the version file and pull the version directly from Bazaar') |
310 | - parser.add_argument('-s', '--style', dest='style', help='Set the Qt5 style (passed directly to Qt5).') |
311 | - parser.add_argument('-w', '--no-web-server', dest='no_web_server', action='store_false', |
312 | + parser.add_argument('-w', '--no-web-server', dest='no_web_server', action='store_true', |
313 | help='Turn off the Web and Socket Server ') |
314 | parser.add_argument('rargs', nargs='?', default=[]) |
315 | # Parse command line options and deal with them. Use args supplied pragmatically if possible. |
316 | @@ -343,8 +342,6 @@ |
317 | log.setLevel(logging.WARNING) |
318 | else: |
319 | log.setLevel(logging.INFO) |
320 | - if args and args.style: |
321 | - qt_args.extend(['-style', args.style]) |
322 | # Throw the rest of the arguments at Qt, just in case. |
323 | qt_args.extend(args.rargs) |
324 | # Bug #1018855: Set the WM_CLASS property in X11 |
325 | @@ -358,7 +355,7 @@ |
326 | application.setOrganizationDomain('openlp.org') |
327 | application.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) |
328 | application.setAttribute(QtCore.Qt.AA_DontCreateNativeWidgetSiblings, True) |
329 | - if args and args.portable: |
330 | + if args.portable: |
331 | application.setApplicationName('OpenLPPortable') |
332 | Settings.setDefaultFormat(Settings.IniFormat) |
333 | # Get location OpenLPPortable.ini |
334 | |
335 | === modified file 'openlp/core/common/applocation.py' |
336 | --- openlp/core/common/applocation.py 2017-12-29 09:15:48 +0000 |
337 | +++ openlp/core/common/applocation.py 2018-01-07 18:07:40 +0000 |
338 | @@ -157,7 +157,7 @@ |
339 | return directory |
340 | return Path('/usr', 'share', 'openlp') |
341 | if XDG_BASE_AVAILABLE: |
342 | - if dir_type == AppLocation.DataDir or dir_type == AppLocation.CacheDir: |
343 | + if dir_type == AppLocation.DataDir: |
344 | return Path(BaseDirectory.xdg_data_home, 'openlp') |
345 | elif dir_type == AppLocation.CacheDir: |
346 | return Path(BaseDirectory.xdg_cache_home, 'openlp') |
347 | |
348 | === modified file 'openlp/core/common/httputils.py' |
349 | --- openlp/core/common/httputils.py 2017-12-29 09:15:48 +0000 |
350 | +++ openlp/core/common/httputils.py 2018-01-07 18:07:40 +0000 |
351 | @@ -20,7 +20,7 @@ |
352 | # Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
353 | ############################################################################### |
354 | """ |
355 | -The :mod:`openlp.core.utils` module provides the utility libraries for OpenLP. |
356 | +The :mod:`openlp.core.common.httputils` module provides the utility methods for downloading stuff. |
357 | """ |
358 | import hashlib |
359 | import logging |
360 | @@ -104,7 +104,7 @@ |
361 | if retries >= CONNECTION_RETRIES: |
362 | raise ConnectionError('Unable to connect to {url}, see log for details'.format(url=url)) |
363 | retries += 1 |
364 | - except: |
365 | + except: # noqa |
366 | # Don't know what's happening, so reraise the original |
367 | log.exception('Unknown error when trying to connect to {url}'.format(url=url)) |
368 | raise |
369 | @@ -136,12 +136,12 @@ |
370 | continue |
371 | |
372 | |
373 | -def url_get_file(callback, url, file_path, sha256=None): |
374 | +def download_file(update_object, url, file_path, sha256=None): |
375 | """" |
376 | Download a file given a URL. The file is retrieved in chunks, giving the ability to cancel the download at any |
377 | point. Returns False on download error. |
378 | |
379 | - :param callback: the class which needs to be updated |
380 | + :param update_object: the object which needs to be updated |
381 | :param url: URL to download |
382 | :param file_path: Destination file |
383 | :param sha256: The check sum value to be checked against the download value |
384 | @@ -158,13 +158,14 @@ |
385 | hasher = hashlib.sha256() |
386 | # Download until finished or canceled. |
387 | for chunk in response.iter_content(chunk_size=block_size): |
388 | - if callback.was_cancelled: |
389 | + if hasattr(update_object, 'was_cancelled') and update_object.was_cancelled: |
390 | break |
391 | saved_file.write(chunk) |
392 | if sha256: |
393 | hasher.update(chunk) |
394 | block_count += 1 |
395 | - callback._download_progress(block_count, block_size) |
396 | + if hasattr(update_object, 'update_progress'): |
397 | + update_object.update_progress(block_count, block_size) |
398 | response.close() |
399 | if sha256 and hasher.hexdigest() != sha256: |
400 | log.error('sha256 sums did not match for file %s, got %s, expected %s', file_path, hasher.hexdigest(), |
401 | @@ -183,7 +184,7 @@ |
402 | retries += 1 |
403 | time.sleep(0.1) |
404 | continue |
405 | - if callback.was_cancelled and file_path.exists(): |
406 | + if hasattr(update_object, 'was_cancelled') and update_object.was_cancelled and file_path.exists(): |
407 | file_path.unlink() |
408 | return True |
409 | |
410 | |
411 | === modified file 'openlp/core/lib/imagemanager.py' |
412 | --- openlp/core/lib/imagemanager.py 2017-12-29 09:15:48 +0000 |
413 | +++ openlp/core/lib/imagemanager.py 2018-01-07 18:07:40 +0000 |
414 | @@ -35,13 +35,14 @@ |
415 | from openlp.core.common.settings import Settings |
416 | from openlp.core.display.screens import ScreenList |
417 | from openlp.core.lib import resize_image, image_to_byte |
418 | +from openlp.core.threading import ThreadWorker, run_thread |
419 | |
420 | log = logging.getLogger(__name__) |
421 | |
422 | |
423 | -class ImageThread(QtCore.QThread): |
424 | +class ImageWorker(ThreadWorker): |
425 | """ |
426 | - A special Qt thread class to speed up the display of images. This is threaded so it loads the frames and generates |
427 | + A thread worker class to speed up the display of images. This is threaded so it loads the frames and generates |
428 | byte stream in background. |
429 | """ |
430 | def __init__(self, manager): |
431 | @@ -51,14 +52,21 @@ |
432 | ``manager`` |
433 | The image manager. |
434 | """ |
435 | - super(ImageThread, self).__init__(None) |
436 | + super().__init__() |
437 | self.image_manager = manager |
438 | |
439 | - def run(self): |
440 | + def start(self): |
441 | """ |
442 | - Run the thread. |
443 | + Start the worker |
444 | """ |
445 | self.image_manager.process() |
446 | + self.quit.emit() |
447 | + |
448 | + def stop(self): |
449 | + """ |
450 | + Stop the worker |
451 | + """ |
452 | + self.image_manager.stop_manager = True |
453 | |
454 | |
455 | class Priority(object): |
456 | @@ -130,7 +138,7 @@ |
457 | |
458 | class PriorityQueue(queue.PriorityQueue): |
459 | """ |
460 | - Customised ``Queue.PriorityQueue``. |
461 | + Customised ``queue.PriorityQueue``. |
462 | |
463 | Each item in the queue must be a tuple with three values. The first value is the :class:`Image`'s ``priority`` |
464 | attribute, the second value the :class:`Image`'s ``secondary_priority`` attribute. The last value the :class:`Image` |
465 | @@ -179,7 +187,6 @@ |
466 | self.width = current_screen['size'].width() |
467 | self.height = current_screen['size'].height() |
468 | self._cache = {} |
469 | - self.image_thread = ImageThread(self) |
470 | self._conversion_queue = PriorityQueue() |
471 | self.stop_manager = False |
472 | Registry().register_function('images_regenerate', self.process_updates) |
473 | @@ -230,9 +237,13 @@ |
474 | """ |
475 | Flush the queue to updated any data to update |
476 | """ |
477 | - # We want only one thread. |
478 | - if not self.image_thread.isRunning(): |
479 | - self.image_thread.start() |
480 | + try: |
481 | + worker = ImageWorker(self) |
482 | + run_thread(worker, 'image_manager') |
483 | + except KeyError: |
484 | + # run_thread() will throw a KeyError if this thread already exists, so ignore it so that we don't |
485 | + # try to start another thread when one is already running |
486 | + pass |
487 | |
488 | def get_image(self, path, source, width=-1, height=-1): |
489 | """ |
490 | @@ -305,9 +316,7 @@ |
491 | if image.path == path and image.timestamp != os.stat(path).st_mtime: |
492 | image.timestamp = os.stat(path).st_mtime |
493 | self._reset_image(image) |
494 | - # We want only one thread. |
495 | - if not self.image_thread.isRunning(): |
496 | - self.image_thread.start() |
497 | + self.process_updates() |
498 | |
499 | def process(self): |
500 | """ |
501 | |
502 | === modified file 'openlp/core/projectors/manager.py' |
503 | --- openlp/core/projectors/manager.py 2018-01-03 00:35:14 +0000 |
504 | +++ openlp/core/projectors/manager.py 2018-01-07 18:07:40 +0000 |
505 | @@ -308,8 +308,7 @@ |
506 | self.settings_section = 'projector' |
507 | self.projectordb = projectordb |
508 | self.projector_list = [] |
509 | - self.pjlink_udp = PJLinkUDP() |
510 | - self.pjlink_udp.projector_list = self.projector_list |
511 | + self.pjlink_udp = PJLinkUDP(self.projector_list) |
512 | self.source_select_form = None |
513 | |
514 | def bootstrap_initialise(self): |
515 | |
516 | === modified file 'openlp/core/projectors/pjlink.py' |
517 | --- openlp/core/projectors/pjlink.py 2018-01-03 00:35:14 +0000 |
518 | +++ openlp/core/projectors/pjlink.py 2018-01-07 18:07:40 +0000 |
519 | @@ -89,11 +89,11 @@ |
520 | 'SRCH' # Class 2 (reply is ACKN) |
521 | ] |
522 | |
523 | - def __init__(self, port=PJLINK_PORT): |
524 | + def __init__(self, projector_list, port=PJLINK_PORT): |
525 | """ |
526 | Initialize socket |
527 | """ |
528 | - |
529 | + self.projector_list = projector_list |
530 | self.port = port |
531 | |
532 | |
533 | |
534 | === modified file 'openlp/core/threading.py' |
535 | --- openlp/core/threading.py 2017-12-29 09:15:48 +0000 |
536 | +++ openlp/core/threading.py 2018-01-07 18:07:40 +0000 |
537 | @@ -24,26 +24,41 @@ |
538 | """ |
539 | from PyQt5 import QtCore |
540 | |
541 | - |
542 | -def run_thread(parent, worker, prefix='', auto_start=True): |
543 | +from openlp.core.common.registry import Registry |
544 | + |
545 | + |
546 | +class ThreadWorker(QtCore.QObject): |
547 | + """ |
548 | + The :class:`~openlp.core.threading.ThreadWorker` class provides a base class for all worker objects |
549 | + """ |
550 | + quit = QtCore.pyqtSignal() |
551 | + |
552 | + def start(self): |
553 | + """ |
554 | + The start method is how the worker runs. Basically, put your code here. |
555 | + """ |
556 | + raise NotImplementedError('Your base class needs to override this method and run self.quit.emit() at the end.') |
557 | + |
558 | + |
559 | +def run_thread(worker, thread_name, can_start=True): |
560 | """ |
561 | Create a thread and assign a worker to it. This removes a lot of boilerplate code from the codebase. |
562 | |
563 | - :param object parent: The parent object so that the thread and worker are not orphaned. |
564 | :param QObject worker: A QObject-based worker object which does the actual work. |
565 | - :param str prefix: A prefix to be applied to the attribute names. |
566 | - :param bool auto_start: Automatically start the thread. Defaults to True. |
567 | + :param str thread_name: The name of the thread, used to keep track of the thread. |
568 | + :param bool can_start: Start the thread. Defaults to True. |
569 | """ |
570 | - # Set up attribute names |
571 | - thread_name = 'thread' |
572 | - worker_name = 'worker' |
573 | - if prefix: |
574 | - thread_name = '_'.join([prefix, thread_name]) |
575 | - worker_name = '_'.join([prefix, worker_name]) |
576 | + if not thread_name: |
577 | + raise ValueError('A thread_name is required when calling the "run_thread" function') |
578 | + main_window = Registry().get('main_window') |
579 | + if thread_name in main_window.threads: |
580 | + raise KeyError('A thread with the name "{}" has already been created, please use another'.format(thread_name)) |
581 | # Create the thread and add the thread and the worker to the parent |
582 | thread = QtCore.QThread() |
583 | - setattr(parent, thread_name, thread) |
584 | - setattr(parent, worker_name, worker) |
585 | + main_window.threads[thread_name] = { |
586 | + 'thread': thread, |
587 | + 'worker': worker |
588 | + } |
589 | # Move the worker into the thread's context |
590 | worker.moveToThread(thread) |
591 | # Connect slots and signals |
592 | @@ -51,5 +66,46 @@ |
593 | worker.quit.connect(thread.quit) |
594 | worker.quit.connect(worker.deleteLater) |
595 | thread.finished.connect(thread.deleteLater) |
596 | - if auto_start: |
597 | + thread.finished.connect(make_remove_thread(thread_name)) |
598 | + if can_start: |
599 | thread.start() |
600 | + |
601 | + |
602 | +def get_thread_worker(thread_name): |
603 | + """ |
604 | + Get the worker by the thread name |
605 | + |
606 | + :param str thread_name: The name of the thread |
607 | + :returns: The worker for this thread name |
608 | + """ |
609 | + return Registry().get('main_window').threads.get(thread_name) |
610 | + |
611 | + |
612 | +def is_thread_finished(thread_name): |
613 | + """ |
614 | + Check if a thread is finished running. |
615 | + |
616 | + :param str thread_name: The name of the thread |
617 | + :returns: True if the thread is finished, False if it is still running |
618 | + """ |
619 | + main_window = Registry().get('main_window') |
620 | + return thread_name not in main_window.threads or main_window.threads[thread_name]['thread'].isFinished() |
621 | + |
622 | + |
623 | +def make_remove_thread(thread_name): |
624 | + """ |
625 | + Create a function to remove the thread once the thread is finished. |
626 | + |
627 | + :param str thread_name: The name of the thread which should be removed from the thread registry. |
628 | + :returns: A function which will remove the thread from the thread registry. |
629 | + """ |
630 | + def remove_thread(): |
631 | + """ |
632 | + Stop and remove a registered thread |
633 | + |
634 | + :param str thread_name: The name of the thread to stop and remove |
635 | + """ |
636 | + main_window = Registry().get('main_window') |
637 | + if thread_name in main_window.threads: |
638 | + del main_window.threads[thread_name] |
639 | + return remove_thread |
640 | |
641 | === modified file 'openlp/core/ui/firsttimeform.py' |
642 | --- openlp/core/ui/firsttimeform.py 2017-12-29 09:15:48 +0000 |
643 | +++ openlp/core/ui/firsttimeform.py 2018-01-07 18:07:40 +0000 |
644 | @@ -23,8 +23,6 @@ |
645 | This module contains the first time wizard. |
646 | """ |
647 | import logging |
648 | -import os |
649 | -import socket |
650 | import time |
651 | import urllib.error |
652 | import urllib.parse |
653 | @@ -36,7 +34,7 @@ |
654 | |
655 | from openlp.core.common import clean_button_text, trace_error_handler |
656 | from openlp.core.common.applocation import AppLocation |
657 | -from openlp.core.common.httputils import get_web_page, get_url_file_size, url_get_file, CONNECTION_TIMEOUT |
658 | +from openlp.core.common.httputils import get_web_page, get_url_file_size, download_file |
659 | from openlp.core.common.i18n import translate |
660 | from openlp.core.common.mixins import RegistryProperties |
661 | from openlp.core.common.path import Path, create_paths |
662 | @@ -44,46 +42,47 @@ |
663 | from openlp.core.common.settings import Settings |
664 | from openlp.core.lib import PluginStatus, build_icon |
665 | from openlp.core.lib.ui import critical_error_message_box |
666 | -from .firsttimewizard import UiFirstTimeWizard, FirstTimePage |
667 | +from openlp.core.threading import ThreadWorker, run_thread, get_thread_worker, is_thread_finished |
668 | +from openlp.core.ui.firsttimewizard import UiFirstTimeWizard, FirstTimePage |
669 | |
670 | log = logging.getLogger(__name__) |
671 | |
672 | |
673 | -class ThemeScreenshotWorker(QtCore.QObject): |
674 | +class ThemeScreenshotWorker(ThreadWorker): |
675 | """ |
676 | This thread downloads a theme's screenshot |
677 | """ |
678 | screenshot_downloaded = QtCore.pyqtSignal(str, str, str) |
679 | - finished = QtCore.pyqtSignal() |
680 | |
681 | def __init__(self, themes_url, title, filename, sha256, screenshot): |
682 | """ |
683 | Set up the worker object |
684 | """ |
685 | - self.was_download_cancelled = False |
686 | + self.was_cancelled = False |
687 | self.themes_url = themes_url |
688 | self.title = title |
689 | self.filename = filename |
690 | self.sha256 = sha256 |
691 | self.screenshot = screenshot |
692 | - socket.setdefaulttimeout(CONNECTION_TIMEOUT) |
693 | - super(ThemeScreenshotWorker, self).__init__() |
694 | + super().__init__() |
695 | |
696 | - def run(self): |
697 | - """ |
698 | - Overridden method to run the thread. |
699 | - """ |
700 | - if self.was_download_cancelled: |
701 | + def start(self): |
702 | + """ |
703 | + Run the worker |
704 | + """ |
705 | + if self.was_cancelled: |
706 | return |
707 | try: |
708 | - urllib.request.urlretrieve('{host}{name}'.format(host=self.themes_url, name=self.screenshot), |
709 | - os.path.join(gettempdir(), 'openlp', self.screenshot)) |
710 | - # Signal that the screenshot has been downloaded |
711 | - self.screenshot_downloaded.emit(self.title, self.filename, self.sha256) |
712 | - except: |
713 | + download_path = Path(gettempdir()) / 'openlp' / self.screenshot |
714 | + is_success = download_file(self, '{host}{name}'.format(host=self.themes_url, name=self.screenshot), |
715 | + download_path) |
716 | + if is_success and not self.was_cancelled: |
717 | + # Signal that the screenshot has been downloaded |
718 | + self.screenshot_downloaded.emit(self.title, self.filename, self.sha256) |
719 | + except: # noqa |
720 | log.exception('Unable to download screenshot') |
721 | finally: |
722 | - self.finished.emit() |
723 | + self.quit.emit() |
724 | |
725 | @QtCore.pyqtSlot(bool) |
726 | def set_download_canceled(self, toggle): |
727 | @@ -145,12 +144,13 @@ |
728 | return FirstTimePage.Progress |
729 | elif self.currentId() == FirstTimePage.Themes: |
730 | self.application.set_busy_cursor() |
731 | - while not all([thread.isFinished() for thread in self.theme_screenshot_threads]): |
732 | + while not all([is_thread_finished(thread_name) for thread_name in self.theme_screenshot_threads]): |
733 | time.sleep(0.1) |
734 | self.application.process_events() |
735 | # Build the screenshot icons, as this can not be done in the thread. |
736 | self._build_theme_screenshots() |
737 | self.application.set_normal_cursor() |
738 | + self.theme_screenshot_threads = [] |
739 | return FirstTimePage.Defaults |
740 | else: |
741 | return self.get_next_page_id() |
742 | @@ -171,7 +171,6 @@ |
743 | self.screens = screens |
744 | self.was_cancelled = False |
745 | self.theme_screenshot_threads = [] |
746 | - self.theme_screenshot_workers = [] |
747 | self.has_run_wizard = False |
748 | |
749 | def _download_index(self): |
750 | @@ -256,14 +255,10 @@ |
751 | sha256 = self.config.get('theme_{theme}'.format(theme=theme), 'sha256', fallback='') |
752 | screenshot = self.config.get('theme_{theme}'.format(theme=theme), 'screenshot') |
753 | worker = ThemeScreenshotWorker(self.themes_url, title, filename, sha256, screenshot) |
754 | - self.theme_screenshot_workers.append(worker) |
755 | worker.screenshot_downloaded.connect(self.on_screenshot_downloaded) |
756 | - thread = QtCore.QThread(self) |
757 | - self.theme_screenshot_threads.append(thread) |
758 | - thread.started.connect(worker.run) |
759 | - worker.finished.connect(thread.quit) |
760 | - worker.moveToThread(thread) |
761 | - thread.start() |
762 | + thread_name = 'theme_screenshot_{title}'.format(title=title) |
763 | + run_thread(worker, thread_name) |
764 | + self.theme_screenshot_threads.append(thread_name) |
765 | self.application.process_events() |
766 | |
767 | def set_defaults(self): |
768 | @@ -353,12 +348,14 @@ |
769 | Process the triggering of the cancel button. |
770 | """ |
771 | self.was_cancelled = True |
772 | - if self.theme_screenshot_workers: |
773 | - for worker in self.theme_screenshot_workers: |
774 | - worker.set_download_canceled(True) |
775 | + if self.theme_screenshot_threads: |
776 | + for thread_name in self.theme_screenshot_threads: |
777 | + worker = get_thread_worker(thread_name) |
778 | + if worker: |
779 | + worker.set_download_canceled(True) |
780 | # Was the thread created. |
781 | if self.theme_screenshot_threads: |
782 | - while any([thread.isRunning() for thread in self.theme_screenshot_threads]): |
783 | + while any([not is_thread_finished(thread_name) for thread_name in self.theme_screenshot_threads]): |
784 | time.sleep(0.1) |
785 | self.application.set_normal_cursor() |
786 | |
787 | @@ -562,8 +559,8 @@ |
788 | self._increment_progress_bar(self.downloading.format(name=filename), 0) |
789 | self.previous_size = 0 |
790 | destination = songs_destination_path / str(filename) |
791 | - if not url_get_file(self, '{path}{name}'.format(path=self.songs_url, name=filename), |
792 | - destination, sha256): |
793 | + if not download_file(self, '{path}{name}'.format(path=self.songs_url, name=filename), |
794 | + destination, sha256): |
795 | missed_files.append('Song: {name}'.format(name=filename)) |
796 | # Download Bibles |
797 | bibles_iterator = QtWidgets.QTreeWidgetItemIterator(self.bibles_tree_widget) |
798 | @@ -573,8 +570,8 @@ |
799 | bible, sha256 = item.data(0, QtCore.Qt.UserRole) |
800 | self._increment_progress_bar(self.downloading.format(name=bible), 0) |
801 | self.previous_size = 0 |
802 | - if not url_get_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible), |
803 | - bibles_destination_path / bible, sha256): |
804 | + if not download_file(self, '{path}{name}'.format(path=self.bibles_url, name=bible), |
805 | + bibles_destination_path / bible, sha256): |
806 | missed_files.append('Bible: {name}'.format(name=bible)) |
807 | bibles_iterator += 1 |
808 | # Download themes |
809 | @@ -584,8 +581,8 @@ |
810 | theme, sha256 = item.data(QtCore.Qt.UserRole) |
811 | self._increment_progress_bar(self.downloading.format(name=theme), 0) |
812 | self.previous_size = 0 |
813 | - if not url_get_file(self, '{path}{name}'.format(path=self.themes_url, name=theme), |
814 | - themes_destination_path / theme, sha256): |
815 | + if not download_file(self, '{path}{name}'.format(path=self.themes_url, name=theme), |
816 | + themes_destination_path / theme, sha256): |
817 | missed_files.append('Theme: {name}'.format(name=theme)) |
818 | if missed_files: |
819 | file_list = '' |
820 | |
821 | === modified file 'openlp/core/ui/mainwindow.py' |
822 | --- openlp/core/ui/mainwindow.py 2017-12-29 09:15:48 +0000 |
823 | +++ openlp/core/ui/mainwindow.py 2018-01-07 18:07:40 +0000 |
824 | @@ -24,7 +24,6 @@ |
825 | """ |
826 | import logging |
827 | import sys |
828 | -import time |
829 | from datetime import datetime |
830 | from distutils import dir_util |
831 | from distutils.errors import DistutilsFileError |
832 | @@ -478,8 +477,7 @@ |
833 | """ |
834 | super(MainWindow, self).__init__() |
835 | Registry().register('main_window', self) |
836 | - self.version_thread = None |
837 | - self.version_worker = None |
838 | + self.threads = {} |
839 | self.clipboard = self.application.clipboard() |
840 | self.arguments = ''.join(self.application.args) |
841 | # Set up settings sections for the main application (not for use by plugins). |
842 | @@ -501,8 +499,8 @@ |
843 | Settings().set_up_default_values() |
844 | self.about_form = AboutForm(self) |
845 | MediaController() |
846 | - websockets.WebSocketServer() |
847 | - server.HttpServer() |
848 | + self.ws_server = websockets.WebSocketServer() |
849 | + self.http_server = server.HttpServer(self) |
850 | SettingsForm(self) |
851 | self.formatting_tag_form = FormattingTagForm(self) |
852 | self.shortcut_form = ShortcutListForm(self) |
853 | @@ -549,6 +547,41 @@ |
854 | # Reset the cursor |
855 | self.application.set_normal_cursor() |
856 | |
857 | + def _wait_for_threads(self): |
858 | + """ |
859 | + Wait for the threads |
860 | + """ |
861 | + # Sometimes the threads haven't finished, let's wait for them |
862 | + wait_dialog = QtWidgets.QProgressDialog('Waiting for some things to finish...', '', 0, 0, self) |
863 | + wait_dialog.setWindowModality(QtCore.Qt.WindowModal) |
864 | + wait_dialog.setAutoClose(False) |
865 | + wait_dialog.setCancelButton(None) |
866 | + wait_dialog.show() |
867 | + for thread_name in self.threads.keys(): |
868 | + log.debug('Waiting for thread %s', thread_name) |
869 | + self.application.processEvents() |
870 | + thread = self.threads[thread_name]['thread'] |
871 | + worker = self.threads[thread_name]['worker'] |
872 | + try: |
873 | + if worker and hasattr(worker, 'stop'): |
874 | + # If the worker has a stop method, run it |
875 | + worker.stop() |
876 | + if thread and thread.isRunning(): |
877 | + # If the thread is running, let's wait 5 seconds for it |
878 | + retry = 0 |
879 | + while thread.isRunning() and retry < 50: |
880 | + # Make the GUI responsive while we wait |
881 | + self.application.processEvents() |
882 | + thread.wait(100) |
883 | + retry += 1 |
884 | + if thread.isRunning(): |
885 | + # If the thread is still running after 5 seconds, kill it |
886 | + thread.terminate() |
887 | + except RuntimeError: |
888 | + # Ignore the RuntimeError that is thrown when Qt has already deleted the C++ thread object |
889 | + pass |
890 | + wait_dialog.close() |
891 | + |
892 | def bootstrap_post_set_up(self): |
893 | """ |
894 | process the bootstrap post setup request |
895 | @@ -695,7 +728,7 @@ |
896 | # Update the theme widget |
897 | self.theme_manager_contents.load_themes() |
898 | # Check if any Bibles downloaded. If there are, they will be processed. |
899 | - Registry().execute('bibles_load_list', True) |
900 | + Registry().execute('bibles_load_list') |
901 | self.application.set_normal_cursor() |
902 | |
903 | def is_display_blank(self): |
904 | @@ -1000,39 +1033,14 @@ |
905 | if not self.application.is_event_loop_active: |
906 | event.ignore() |
907 | return |
908 | - # Sometimes the version thread hasn't finished, let's wait for it |
909 | - try: |
910 | - if self.version_thread and self.version_thread.isRunning(): |
911 | - wait_dialog = QtWidgets.QProgressDialog('Waiting for some things to finish...', '', 0, 0, self) |
912 | - wait_dialog.setWindowModality(QtCore.Qt.WindowModal) |
913 | - wait_dialog.setAutoClose(False) |
914 | - wait_dialog.setCancelButton(None) |
915 | - wait_dialog.show() |
916 | - retry = 0 |
917 | - while self.version_thread.isRunning() and retry < 50: |
918 | - self.application.processEvents() |
919 | - self.version_thread.wait(100) |
920 | - retry += 1 |
921 | - if self.version_thread.isRunning(): |
922 | - self.version_thread.terminate() |
923 | - wait_dialog.close() |
924 | - except RuntimeError: |
925 | - # Ignore the RuntimeError that is thrown when Qt has already deleted the C++ thread object |
926 | - pass |
927 | - # If we just did a settings import, close without saving changes. |
928 | - if self.settings_imported: |
929 | - self.clean_up(False) |
930 | - event.accept() |
931 | if self.service_manager_contents.is_modified(): |
932 | ret = self.service_manager_contents.save_modified_service() |
933 | if ret == QtWidgets.QMessageBox.Save: |
934 | if self.service_manager_contents.decide_save_method(): |
935 | - self.clean_up() |
936 | event.accept() |
937 | else: |
938 | event.ignore() |
939 | elif ret == QtWidgets.QMessageBox.Discard: |
940 | - self.clean_up() |
941 | event.accept() |
942 | else: |
943 | event.ignore() |
944 | @@ -1048,13 +1056,16 @@ |
945 | close_button.setText(translate('OpenLP.MainWindow', '&Exit OpenLP')) |
946 | msg_box.setDefaultButton(QtWidgets.QMessageBox.Close) |
947 | if msg_box.exec() == QtWidgets.QMessageBox.Close: |
948 | - self.clean_up() |
949 | event.accept() |
950 | else: |
951 | event.ignore() |
952 | else: |
953 | - self.clean_up() |
954 | event.accept() |
955 | + if event.isAccepted(): |
956 | + # Wait for all the threads to complete |
957 | + self._wait_for_threads() |
958 | + # If we just did a settings import, close without saving changes. |
959 | + self.clean_up(save_settings=not self.settings_imported) |
960 | |
961 | def clean_up(self, save_settings=True): |
962 | """ |
963 | @@ -1062,9 +1073,6 @@ |
964 | |
965 | :param save_settings: Switch to prevent saving settings. Defaults to **True**. |
966 | """ |
967 | - self.image_manager.stop_manager = True |
968 | - while self.image_manager.image_thread.isRunning(): |
969 | - time.sleep(0.1) |
970 | if save_settings: |
971 | if Settings().value('advanced/save current plugin'): |
972 | Settings().setValue('advanced/current media plugin', self.media_tool_box.currentIndex()) |
973 | |
974 | === modified file 'openlp/core/ui/media/systemplayer.py' |
975 | --- openlp/core/ui/media/systemplayer.py 2017-12-29 09:15:48 +0000 |
976 | +++ openlp/core/ui/media/systemplayer.py 2018-01-07 18:07:40 +0000 |
977 | @@ -31,6 +31,7 @@ |
978 | from openlp.core.common.i18n import translate |
979 | from openlp.core.ui.media import MediaState |
980 | from openlp.core.ui.media.mediaplayer import MediaPlayer |
981 | +from openlp.core.threading import ThreadWorker, run_thread, is_thread_finished |
982 | |
983 | log = logging.getLogger(__name__) |
984 | |
985 | @@ -293,39 +294,38 @@ |
986 | :param path: Path to file to be checked |
987 | :return: True if file can be played otherwise False |
988 | """ |
989 | - thread = QtCore.QThread() |
990 | check_media_worker = CheckMediaWorker(path) |
991 | check_media_worker.setVolume(0) |
992 | - check_media_worker.moveToThread(thread) |
993 | - check_media_worker.finished.connect(thread.quit) |
994 | - thread.started.connect(check_media_worker.play) |
995 | - thread.start() |
996 | - while thread.isRunning(): |
997 | + run_thread(check_media_worker, 'check_media') |
998 | + while not is_thread_finished('check_media'): |
999 | self.application.processEvents() |
1000 | return check_media_worker.result |
1001 | |
1002 | |
1003 | -class CheckMediaWorker(QtMultimedia.QMediaPlayer): |
1004 | +class CheckMediaWorker(QtMultimedia.QMediaPlayer, ThreadWorker): |
1005 | """ |
1006 | Class used to check if a media file is playable |
1007 | """ |
1008 | - finished = QtCore.pyqtSignal() |
1009 | - |
1010 | def __init__(self, path): |
1011 | super(CheckMediaWorker, self).__init__(None, QtMultimedia.QMediaPlayer.VideoSurface) |
1012 | + self.path = path |
1013 | + |
1014 | + def start(self): |
1015 | + """ |
1016 | + Start the thread worker |
1017 | + """ |
1018 | self.result = None |
1019 | - |
1020 | self.error.connect(functools.partial(self.signals, 'error')) |
1021 | self.mediaStatusChanged.connect(functools.partial(self.signals, 'media')) |
1022 | - |
1023 | - self.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(path))) |
1024 | + self.setMedia(QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(self.path))) |
1025 | + self.play() |
1026 | |
1027 | def signals(self, origin, status): |
1028 | if origin == 'media' and status == self.BufferedMedia: |
1029 | self.result = True |
1030 | self.stop() |
1031 | - self.finished.emit() |
1032 | + self.quit.emit() |
1033 | elif origin == 'error' and status != self.NoError: |
1034 | self.result = False |
1035 | self.stop() |
1036 | - self.finished.emit() |
1037 | + self.quit.emit() |
1038 | |
1039 | === modified file 'openlp/core/version.py' |
1040 | --- openlp/core/version.py 2018-01-02 21:00:54 +0000 |
1041 | +++ openlp/core/version.py 2018-01-07 18:07:40 +0000 |
1042 | @@ -35,7 +35,7 @@ |
1043 | |
1044 | from openlp.core.common.applocation import AppLocation |
1045 | from openlp.core.common.settings import Settings |
1046 | -from openlp.core.threading import run_thread |
1047 | +from openlp.core.threading import ThreadWorker, run_thread |
1048 | |
1049 | log = logging.getLogger(__name__) |
1050 | |
1051 | @@ -44,14 +44,13 @@ |
1052 | CONNECTION_RETRIES = 2 |
1053 | |
1054 | |
1055 | -class VersionWorker(QtCore.QObject): |
1056 | +class VersionWorker(ThreadWorker): |
1057 | """ |
1058 | A worker class to fetch the version of OpenLP from the website. This is run from within a thread so that it |
1059 | doesn't affect the loading time of OpenLP. |
1060 | """ |
1061 | new_version = QtCore.pyqtSignal(dict) |
1062 | no_internet = QtCore.pyqtSignal() |
1063 | - quit = QtCore.pyqtSignal() |
1064 | |
1065 | def __init__(self, last_check_date, current_version): |
1066 | """ |
1067 | @@ -110,22 +109,22 @@ |
1068 | Settings().setValue('core/last version test', date.today().strftime('%Y-%m-%d')) |
1069 | |
1070 | |
1071 | -def check_for_update(parent): |
1072 | +def check_for_update(main_window): |
1073 | """ |
1074 | Run a thread to download and check the version of OpenLP |
1075 | |
1076 | - :param MainWindow parent: The parent object for the thread. Usually the OpenLP main window. |
1077 | + :param MainWindow main_window: The OpenLP main window. |
1078 | """ |
1079 | last_check_date = Settings().value('core/last version test') |
1080 | if date.today().strftime('%Y-%m-%d') <= last_check_date: |
1081 | log.debug('Version check skipped, last checked today') |
1082 | return |
1083 | worker = VersionWorker(last_check_date, get_version()) |
1084 | - worker.new_version.connect(parent.on_new_version) |
1085 | + worker.new_version.connect(main_window.on_new_version) |
1086 | worker.quit.connect(update_check_date) |
1087 | # TODO: Use this to figure out if there's an Internet connection? |
1088 | # worker.no_internet.connect(parent.on_no_internet) |
1089 | - run_thread(parent, worker, 'version') |
1090 | + run_thread(worker, 'version') |
1091 | |
1092 | |
1093 | def get_version(): |
1094 | |
1095 | === modified file 'openlp/plugins/songs/forms/songselectform.py' |
1096 | --- openlp/plugins/songs/forms/songselectform.py 2017-12-29 09:15:48 +0000 |
1097 | +++ openlp/plugins/songs/forms/songselectform.py 2018-01-07 18:07:40 +0000 |
1098 | @@ -27,24 +27,23 @@ |
1099 | |
1100 | from PyQt5 import QtCore, QtWidgets |
1101 | |
1102 | -from openlp.core.common import is_win |
1103 | from openlp.core.common.i18n import translate |
1104 | -from openlp.core.common.registry import Registry |
1105 | +from openlp.core.common.mixins import RegistryProperties |
1106 | from openlp.core.common.settings import Settings |
1107 | +from openlp.core.threading import ThreadWorker, run_thread |
1108 | from openlp.plugins.songs.forms.songselectdialog import Ui_SongSelectDialog |
1109 | from openlp.plugins.songs.lib.songselect import SongSelectImport |
1110 | |
1111 | log = logging.getLogger(__name__) |
1112 | |
1113 | |
1114 | -class SearchWorker(QtCore.QObject): |
1115 | +class SearchWorker(ThreadWorker): |
1116 | """ |
1117 | Run the actual SongSelect search, and notify the GUI when we find each song. |
1118 | """ |
1119 | show_info = QtCore.pyqtSignal(str, str) |
1120 | found_song = QtCore.pyqtSignal(dict) |
1121 | finished = QtCore.pyqtSignal() |
1122 | - quit = QtCore.pyqtSignal() |
1123 | |
1124 | def __init__(self, importer, search_text): |
1125 | super().__init__() |
1126 | @@ -74,7 +73,7 @@ |
1127 | self.found_song.emit(song) |
1128 | |
1129 | |
1130 | -class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog): |
1131 | +class SongSelectForm(QtWidgets.QDialog, Ui_SongSelectDialog, RegistryProperties): |
1132 | """ |
1133 | The :class:`SongSelectForm` class is the SongSelect dialog. |
1134 | """ |
1135 | @@ -90,8 +89,6 @@ |
1136 | """ |
1137 | Initialise the SongSelectForm |
1138 | """ |
1139 | - self.thread = None |
1140 | - self.worker = None |
1141 | self.song_count = 0 |
1142 | self.song = None |
1143 | self.set_progress_visible(False) |
1144 | @@ -311,17 +308,11 @@ |
1145 | search_history = self.search_combobox.getItems() |
1146 | Settings().setValue(self.plugin.settings_section + '/songselect searches', '|'.join(search_history)) |
1147 | # Create thread and run search |
1148 | - self.thread = QtCore.QThread() |
1149 | - self.worker = SearchWorker(self.song_select_importer, self.search_combobox.currentText()) |
1150 | - self.worker.moveToThread(self.thread) |
1151 | - self.thread.started.connect(self.worker.start) |
1152 | - self.worker.show_info.connect(self.on_search_show_info) |
1153 | - self.worker.found_song.connect(self.on_search_found_song) |
1154 | - self.worker.finished.connect(self.on_search_finished) |
1155 | - self.worker.quit.connect(self.thread.quit) |
1156 | - self.worker.quit.connect(self.worker.deleteLater) |
1157 | - self.thread.finished.connect(self.thread.deleteLater) |
1158 | - self.thread.start() |
1159 | + worker = SearchWorker(self.song_select_importer, self.search_combobox.currentText()) |
1160 | + worker.show_info.connect(self.on_search_show_info) |
1161 | + worker.found_song.connect(self.on_search_found_song) |
1162 | + worker.finished.connect(self.on_search_finished) |
1163 | + run_thread(worker, 'songselect') |
1164 | |
1165 | def on_stop_button_clicked(self): |
1166 | """ |
1167 | @@ -408,16 +399,3 @@ |
1168 | """ |
1169 | self.search_progress_bar.setVisible(is_visible) |
1170 | self.stop_button.setVisible(is_visible) |
1171 | - |
1172 | - @property |
1173 | - def application(self): |
1174 | - """ |
1175 | - Adds the openlp to the class dynamically. |
1176 | - Windows needs to access the application in a dynamic manner. |
1177 | - """ |
1178 | - if is_win(): |
1179 | - return Registry().get('application') |
1180 | - else: |
1181 | - if not hasattr(self, '_application'): |
1182 | - self._application = Registry().get('application') |
1183 | - return self._application |
1184 | |
1185 | === modified file 'tests/functional/openlp_core/api/http/test_http.py' |
1186 | --- tests/functional/openlp_core/api/http/test_http.py 2017-12-29 09:15:48 +0000 |
1187 | +++ tests/functional/openlp_core/api/http/test_http.py 2018-01-07 18:07:40 +0000 |
1188 | @@ -42,8 +42,23 @@ |
1189 | Registry().register('service_list', MagicMock()) |
1190 | |
1191 | @patch('openlp.core.api.http.server.HttpWorker') |
1192 | - @patch('openlp.core.api.http.server.QtCore.QThread') |
1193 | - def test_server_start(self, mock_qthread, mock_thread): |
1194 | + @patch('openlp.core.api.http.server.run_thread') |
1195 | + def test_server_start(self, mocked_run_thread, MockHttpWorker): |
1196 | + """ |
1197 | + Test the starting of the Waitress Server with the disable flag set off |
1198 | + """ |
1199 | + # GIVEN: A new httpserver |
1200 | + # WHEN: I start the server |
1201 | + Registry().set_flag('no_web_server', False) |
1202 | + HttpServer() |
1203 | + |
1204 | + # THEN: the api environment should have been created |
1205 | + assert mocked_run_thread.call_count == 1, 'The qthread should have been called once' |
1206 | + assert MockHttpWorker.call_count == 1, 'The http thread should have been called once' |
1207 | + |
1208 | + @patch('openlp.core.api.http.server.HttpWorker') |
1209 | + @patch('openlp.core.api.http.server.run_thread') |
1210 | + def test_server_start_not_required(self, mocked_run_thread, MockHttpWorker): |
1211 | """ |
1212 | Test the starting of the Waitress Server with the disable flag set off |
1213 | """ |
1214 | @@ -53,20 +68,5 @@ |
1215 | HttpServer() |
1216 | |
1217 | # THEN: the api environment should have been created |
1218 | - assert mock_qthread.call_count == 1, 'The qthread should have been called once' |
1219 | - assert mock_thread.call_count == 1, 'The http thread should have been called once' |
1220 | - |
1221 | - @patch('openlp.core.api.http.server.HttpWorker') |
1222 | - @patch('openlp.core.api.http.server.QtCore.QThread') |
1223 | - def test_server_start_not_required(self, mock_qthread, mock_thread): |
1224 | - """ |
1225 | - Test the starting of the Waitress Server with the disable flag set off |
1226 | - """ |
1227 | - # GIVEN: A new httpserver |
1228 | - # WHEN: I start the server |
1229 | - Registry().set_flag('no_web_server', False) |
1230 | - HttpServer() |
1231 | - |
1232 | - # THEN: the api environment should have been created |
1233 | - assert mock_qthread.call_count == 0, 'The qthread should not have have been called' |
1234 | - assert mock_thread.call_count == 0, 'The http thread should not have been called' |
1235 | + assert mocked_run_thread.call_count == 0, 'The qthread should not have have been called' |
1236 | + assert MockHttpWorker.call_count == 0, 'The http thread should not have been called' |
1237 | |
1238 | === modified file 'tests/functional/openlp_core/api/test_websockets.py' |
1239 | --- tests/functional/openlp_core/api/test_websockets.py 2017-12-29 09:15:48 +0000 |
1240 | +++ tests/functional/openlp_core/api/test_websockets.py 2018-01-07 18:07:40 +0000 |
1241 | @@ -63,34 +63,34 @@ |
1242 | self.destroy_settings() |
1243 | |
1244 | @patch('openlp.core.api.websockets.WebSocketWorker') |
1245 | - @patch('openlp.core.api.websockets.QtCore.QThread') |
1246 | - def test_serverstart(self, mock_qthread, mock_worker): |
1247 | + @patch('openlp.core.api.websockets.run_thread') |
1248 | + def test_serverstart(self, mocked_run_thread, MockWebSocketWorker): |
1249 | """ |
1250 | Test the starting of the WebSockets Server with the disabled flag set on |
1251 | """ |
1252 | # GIVEN: A new httpserver |
1253 | # WHEN: I start the server |
1254 | - Registry().set_flag('no_web_server', True) |
1255 | + Registry().set_flag('no_web_server', False) |
1256 | WebSocketServer() |
1257 | |
1258 | # THEN: the api environment should have been created |
1259 | - assert mock_qthread.call_count == 1, 'The qthread should have been called once' |
1260 | - assert mock_worker.call_count == 1, 'The http thread should have been called once' |
1261 | + assert mocked_run_thread.call_count == 1, 'The qthread should have been called once' |
1262 | + assert MockWebSocketWorker.call_count == 1, 'The http thread should have been called once' |
1263 | |
1264 | @patch('openlp.core.api.websockets.WebSocketWorker') |
1265 | - @patch('openlp.core.api.websockets.QtCore.QThread') |
1266 | - def test_serverstart_not_required(self, mock_qthread, mock_worker): |
1267 | + @patch('openlp.core.api.websockets.run_thread') |
1268 | + def test_serverstart_not_required(self, mocked_run_thread, MockWebSocketWorker): |
1269 | """ |
1270 | Test the starting of the WebSockets Server with the disabled flag set off |
1271 | """ |
1272 | # GIVEN: A new httpserver and the server is not required |
1273 | # WHEN: I start the server |
1274 | - Registry().set_flag('no_web_server', False) |
1275 | + Registry().set_flag('no_web_server', True) |
1276 | WebSocketServer() |
1277 | |
1278 | # THEN: the api environment should have been created |
1279 | - assert mock_qthread.call_count == 0, 'The qthread should not have been called' |
1280 | - assert mock_worker.call_count == 0, 'The http thread should not have been called' |
1281 | + assert mocked_run_thread.call_count == 0, 'The qthread should not have been called' |
1282 | + assert MockWebSocketWorker.call_count == 0, 'The http thread should not have been called' |
1283 | |
1284 | def test_main_poll(self): |
1285 | """ |
1286 | |
1287 | === modified file 'tests/functional/openlp_core/common/test_httputils.py' |
1288 | --- tests/functional/openlp_core/common/test_httputils.py 2017-12-29 09:15:48 +0000 |
1289 | +++ tests/functional/openlp_core/common/test_httputils.py 2018-01-07 18:07:40 +0000 |
1290 | @@ -27,7 +27,7 @@ |
1291 | from unittest import TestCase |
1292 | from unittest.mock import MagicMock, patch |
1293 | |
1294 | -from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, url_get_file |
1295 | +from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, download_file |
1296 | from openlp.core.common.path import Path |
1297 | from tests.helpers.testmixin import TestMixin |
1298 | |
1299 | @@ -235,7 +235,7 @@ |
1300 | mocked_requests.get.side_effect = OSError |
1301 | |
1302 | # WHEN: Attempt to retrieve a file |
1303 | - url_get_file(MagicMock(), url='http://localhost/test', file_path=Path(self.tempfile)) |
1304 | + download_file(MagicMock(), url='http://localhost/test', file_path=Path(self.tempfile)) |
1305 | |
1306 | # THEN: socket.timeout should have been caught |
1307 | # NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files |
1308 | |
1309 | === modified file 'tests/functional/openlp_core/lib/test_image_manager.py' |
1310 | --- tests/functional/openlp_core/lib/test_image_manager.py 2017-12-28 08:22:55 +0000 |
1311 | +++ tests/functional/openlp_core/lib/test_image_manager.py 2018-01-07 18:07:40 +0000 |
1312 | @@ -25,20 +25,113 @@ |
1313 | import os |
1314 | import time |
1315 | from threading import Lock |
1316 | -from unittest import TestCase |
1317 | -from unittest.mock import patch |
1318 | +from unittest import TestCase, skip |
1319 | +from unittest.mock import MagicMock, patch |
1320 | |
1321 | from PyQt5 import QtGui |
1322 | |
1323 | from openlp.core.common.registry import Registry |
1324 | from openlp.core.display.screens import ScreenList |
1325 | -from openlp.core.lib.imagemanager import ImageManager, Priority |
1326 | +from openlp.core.lib.imagemanager import ImageWorker, ImageManager, Priority, PriorityQueue |
1327 | from tests.helpers.testmixin import TestMixin |
1328 | from tests.utils.constants import RESOURCE_PATH |
1329 | |
1330 | TEST_PATH = str(RESOURCE_PATH) |
1331 | |
1332 | |
1333 | +class TestImageWorker(TestCase, TestMixin): |
1334 | + """ |
1335 | + Test all the methods in the ImageWorker class |
1336 | + """ |
1337 | + def test_init(self): |
1338 | + """ |
1339 | + Test the constructor of the ImageWorker |
1340 | + """ |
1341 | + # GIVEN: An ImageWorker class and a mocked ImageManager |
1342 | + mocked_image_manager = MagicMock() |
1343 | + |
1344 | + # WHEN: Creating the ImageWorker |
1345 | + worker = ImageWorker(mocked_image_manager) |
1346 | + |
1347 | + # THEN: The image_manager attribute should be set correctly |
1348 | + assert worker.image_manager is mocked_image_manager, \ |
1349 | + 'worker.image_manager should have been the mocked_image_manager' |
1350 | + |
1351 | + @patch('openlp.core.lib.imagemanager.ThreadWorker.quit') |
1352 | + def test_start(self, mocked_quit): |
1353 | + """ |
1354 | + Test that the start() method of the image worker calls the process method and then emits quit. |
1355 | + """ |
1356 | + # GIVEN: A mocked image_manager and a new image worker |
1357 | + mocked_image_manager = MagicMock() |
1358 | + worker = ImageWorker(mocked_image_manager) |
1359 | + |
1360 | + # WHEN: start() is called |
1361 | + worker.start() |
1362 | + |
1363 | + # THEN: process() should have been called and quit should have been emitted |
1364 | + mocked_image_manager.process.assert_called_once_with() |
1365 | + mocked_quit.emit.assert_called_once_with() |
1366 | + |
1367 | + def test_stop(self): |
1368 | + """ |
1369 | + Test that the stop method does the right thing |
1370 | + """ |
1371 | + # GIVEN: A mocked image_manager and a worker |
1372 | + mocked_image_manager = MagicMock() |
1373 | + worker = ImageWorker(mocked_image_manager) |
1374 | + |
1375 | + # WHEN: The stop() method is called |
1376 | + worker.stop() |
1377 | + |
1378 | + # THEN: The stop_manager attrivute should have been set to True |
1379 | + assert mocked_image_manager.stop_manager is True, 'mocked_image_manager.stop_manager should have been True' |
1380 | + |
1381 | + |
1382 | +class TestPriorityQueue(TestCase, TestMixin): |
1383 | + """ |
1384 | + Test the PriorityQueue class |
1385 | + """ |
1386 | + @patch('openlp.core.lib.imagemanager.PriorityQueue.remove') |
1387 | + @patch('openlp.core.lib.imagemanager.PriorityQueue.put') |
1388 | + def test_modify_priority(self, mocked_put, mocked_remove): |
1389 | + """ |
1390 | + Test the modify_priority() method of PriorityQueue |
1391 | + """ |
1392 | + # GIVEN: An instance of a PriorityQueue and a mocked image |
1393 | + mocked_image = MagicMock() |
1394 | + mocked_image.priority = Priority.Normal |
1395 | + mocked_image.secondary_priority = Priority.Low |
1396 | + queue = PriorityQueue() |
1397 | + |
1398 | + # WHEN: modify_priority is called with a mocked image and a new priority |
1399 | + queue.modify_priority(mocked_image, Priority.High) |
1400 | + |
1401 | + # THEN: The remove() method should have been called, image priority updated and put() called |
1402 | + mocked_remove.assert_called_once_with(mocked_image) |
1403 | + assert mocked_image.priority == Priority.High, 'The priority should have been Priority.High' |
1404 | + mocked_put.assert_called_once_with((Priority.High, Priority.Low, mocked_image)) |
1405 | + |
1406 | + def test_remove(self): |
1407 | + """ |
1408 | + Test the remove() method of PriorityQueue |
1409 | + """ |
1410 | + # GIVEN: A PriorityQueue instance with a mocked image and queue |
1411 | + mocked_image = MagicMock() |
1412 | + mocked_image.priority = Priority.High |
1413 | + mocked_image.secondary_priority = Priority.Normal |
1414 | + queue = PriorityQueue() |
1415 | + |
1416 | + # WHEN: An image is removed |
1417 | + with patch.object(queue, 'queue') as mocked_queue: |
1418 | + mocked_queue.__contains__.return_value = True |
1419 | + queue.remove(mocked_image) |
1420 | + |
1421 | + # THEN: The mocked queue.remove() method should have been called |
1422 | + mocked_queue.remove.assert_called_once_with((Priority.High, Priority.Normal, mocked_image)) |
1423 | + |
1424 | + |
1425 | +@skip('Probably not going to use ImageManager in WebEngine/Reveal.js') |
1426 | class TestImageManager(TestCase, TestMixin): |
1427 | |
1428 | def setUp(self): |
1429 | @@ -57,10 +150,10 @@ |
1430 | Delete all the C++ objects at the end so that we don't have a segfault |
1431 | """ |
1432 | self.image_manager.stop_manager = True |
1433 | - self.image_manager.image_thread.wait() |
1434 | del self.app |
1435 | |
1436 | - def test_basic_image_manager(self): |
1437 | + @patch('openlp.core.lib.imagemanager.run_thread') |
1438 | + def test_basic_image_manager(self, mocked_run_thread): |
1439 | """ |
1440 | Test the Image Manager setup basic functionality |
1441 | """ |
1442 | @@ -86,7 +179,8 @@ |
1443 | self.image_manager.get_image(TEST_PATH, 'church1.jpg') |
1444 | assert context.exception is not '', 'KeyError exception should have been thrown for missing image' |
1445 | |
1446 | - def test_different_dimension_image(self): |
1447 | + @patch('openlp.core.lib.imagemanager.run_thread') |
1448 | + def test_different_dimension_image(self, mocked_run_thread): |
1449 | """ |
1450 | Test the Image Manager with dimensions |
1451 | """ |
1452 | @@ -118,57 +212,58 @@ |
1453 | self.image_manager.get_image(full_path, 'church.jpg', 120, 120) |
1454 | assert context.exception is not '', 'KeyError exception should have been thrown for missing dimension' |
1455 | |
1456 | - def test_process_cache(self): |
1457 | + @patch('openlp.core.lib.imagemanager.resize_image') |
1458 | + @patch('openlp.core.lib.imagemanager.image_to_byte') |
1459 | + @patch('openlp.core.lib.imagemanager.run_thread') |
1460 | + def test_process_cache(self, mocked_run_thread, mocked_image_to_byte, mocked_resize_image): |
1461 | """ |
1462 | Test the process_cache method |
1463 | """ |
1464 | - with patch('openlp.core.lib.imagemanager.resize_image') as mocked_resize_image, \ |
1465 | - patch('openlp.core.lib.imagemanager.image_to_byte') as mocked_image_to_byte: |
1466 | - # GIVEN: Mocked functions |
1467 | - mocked_resize_image.side_effect = self.mocked_resize_image |
1468 | - mocked_image_to_byte.side_effect = self.mocked_image_to_byte |
1469 | - image1 = 'church.jpg' |
1470 | - image2 = 'church2.jpg' |
1471 | - image3 = 'church3.jpg' |
1472 | - image4 = 'church4.jpg' |
1473 | - |
1474 | - # WHEN: Add the images. Then get the lock (=queue can not be processed). |
1475 | - self.lock.acquire() |
1476 | - self.image_manager.add_image(TEST_PATH, image1, None) |
1477 | - self.image_manager.add_image(TEST_PATH, image2, None) |
1478 | - |
1479 | - # THEN: All images have been added to the queue, and only the first image is not be in the list anymore, but |
1480 | - # is being processed (see mocked methods/functions). |
1481 | - # Note: Priority.Normal means, that the resize_image() was not completed yet (because afterwards the # |
1482 | - # priority is adjusted to Priority.Lowest). |
1483 | - assert self.get_image_priority(image1) == Priority.Normal, "image1's priority should be 'Priority.Normal'" |
1484 | - assert self.get_image_priority(image2) == Priority.Normal, "image2's priority should be 'Priority.Normal'" |
1485 | - |
1486 | - # WHEN: Add more images. |
1487 | - self.image_manager.add_image(TEST_PATH, image3, None) |
1488 | - self.image_manager.add_image(TEST_PATH, image4, None) |
1489 | - # Allow the queue to process. |
1490 | - self.lock.release() |
1491 | - # Request some "data". |
1492 | - self.image_manager.get_image_bytes(TEST_PATH, image4) |
1493 | - self.image_manager.get_image(TEST_PATH, image3) |
1494 | - # Now the mocked methods/functions do not have to sleep anymore. |
1495 | - self.sleep_time = 0 |
1496 | - # Wait for the queue to finish. |
1497 | - while not self.image_manager._conversion_queue.empty(): |
1498 | - time.sleep(0.1) |
1499 | - # Because empty() is not reliable, wait a litte; just to make sure. |
1500 | + # GIVEN: Mocked functions |
1501 | + mocked_resize_image.side_effect = self.mocked_resize_image |
1502 | + mocked_image_to_byte.side_effect = self.mocked_image_to_byte |
1503 | + image1 = 'church.jpg' |
1504 | + image2 = 'church2.jpg' |
1505 | + image3 = 'church3.jpg' |
1506 | + image4 = 'church4.jpg' |
1507 | + |
1508 | + # WHEN: Add the images. Then get the lock (=queue can not be processed). |
1509 | + self.lock.acquire() |
1510 | + self.image_manager.add_image(TEST_PATH, image1, None) |
1511 | + self.image_manager.add_image(TEST_PATH, image2, None) |
1512 | + |
1513 | + # THEN: All images have been added to the queue, and only the first image is not be in the list anymore, but |
1514 | + # is being processed (see mocked methods/functions). |
1515 | + # Note: Priority.Normal means, that the resize_image() was not completed yet (because afterwards the # |
1516 | + # priority is adjusted to Priority.Lowest). |
1517 | + assert self.get_image_priority(image1) == Priority.Normal, "image1's priority should be 'Priority.Normal'" |
1518 | + assert self.get_image_priority(image2) == Priority.Normal, "image2's priority should be 'Priority.Normal'" |
1519 | + |
1520 | + # WHEN: Add more images. |
1521 | + self.image_manager.add_image(TEST_PATH, image3, None) |
1522 | + self.image_manager.add_image(TEST_PATH, image4, None) |
1523 | + # Allow the queue to process. |
1524 | + self.lock.release() |
1525 | + # Request some "data". |
1526 | + self.image_manager.get_image_bytes(TEST_PATH, image4) |
1527 | + self.image_manager.get_image(TEST_PATH, image3) |
1528 | + # Now the mocked methods/functions do not have to sleep anymore. |
1529 | + self.sleep_time = 0 |
1530 | + # Wait for the queue to finish. |
1531 | + while not self.image_manager._conversion_queue.empty(): |
1532 | time.sleep(0.1) |
1533 | - # THEN: The images' priority reflect how they were processed. |
1534 | - assert self.image_manager._conversion_queue.qsize() == 0, "The queue should be empty." |
1535 | - assert self.get_image_priority(image1) == Priority.Lowest, \ |
1536 | - "The image should have not been requested (=Lowest)" |
1537 | - assert self.get_image_priority(image2) == Priority.Lowest, \ |
1538 | - "The image should have not been requested (=Lowest)" |
1539 | - assert self.get_image_priority(image3) == Priority.Low, \ |
1540 | - "Only the QImage should have been requested (=Low)." |
1541 | - assert self.get_image_priority(image4) == Priority.Urgent, \ |
1542 | - "The image bytes should have been requested (=Urgent)." |
1543 | + # Because empty() is not reliable, wait a litte; just to make sure. |
1544 | + time.sleep(0.1) |
1545 | + # THEN: The images' priority reflect how they were processed. |
1546 | + assert self.image_manager._conversion_queue.qsize() == 0, "The queue should be empty." |
1547 | + assert self.get_image_priority(image1) == Priority.Lowest, \ |
1548 | + "The image should have not been requested (=Lowest)" |
1549 | + assert self.get_image_priority(image2) == Priority.Lowest, \ |
1550 | + "The image should have not been requested (=Lowest)" |
1551 | + assert self.get_image_priority(image3) == Priority.Low, \ |
1552 | + "Only the QImage should have been requested (=Low)." |
1553 | + assert self.get_image_priority(image4) == Priority.Urgent, \ |
1554 | + "The image bytes should have been requested (=Urgent)." |
1555 | |
1556 | def get_image_priority(self, image): |
1557 | """ |
1558 | |
1559 | === modified file 'tests/functional/openlp_core/test_app.py' |
1560 | --- tests/functional/openlp_core/test_app.py 2017-12-29 09:15:48 +0000 |
1561 | +++ tests/functional/openlp_core/test_app.py 2018-01-07 18:07:40 +0000 |
1562 | @@ -36,14 +36,15 @@ |
1563 | """ |
1564 | # GIVEN: a a set of system arguments. |
1565 | sys.argv[1:] = [] |
1566 | + |
1567 | # WHEN: We we parse them to expand to options |
1568 | - args = parse_options(None) |
1569 | + args = parse_options() |
1570 | + |
1571 | # THEN: the following fields will have been extracted. |
1572 | assert args.dev_version is False, 'The dev_version flag should be False' |
1573 | assert args.loglevel == 'warning', 'The log level should be set to warning' |
1574 | assert args.no_error_form is False, 'The no_error_form should be set to False' |
1575 | assert args.portable is False, 'The portable flag should be set to false' |
1576 | - assert args.style is None, 'There are no style flags to be processed' |
1577 | assert args.rargs == [], 'The service file should be blank' |
1578 | |
1579 | |
1580 | @@ -53,14 +54,15 @@ |
1581 | """ |
1582 | # GIVEN: a a set of system arguments. |
1583 | sys.argv[1:] = ['-l debug'] |
1584 | + |
1585 | # WHEN: We we parse them to expand to options |
1586 | - args = parse_options(None) |
1587 | + args = parse_options() |
1588 | + |
1589 | # THEN: the following fields will have been extracted. |
1590 | assert args.dev_version is False, 'The dev_version flag should be False' |
1591 | assert args.loglevel == ' debug', 'The log level should be set to debug' |
1592 | assert args.no_error_form is False, 'The no_error_form should be set to False' |
1593 | assert args.portable is False, 'The portable flag should be set to false' |
1594 | - assert args.style is None, 'There are no style flags to be processed' |
1595 | assert args.rargs == [], 'The service file should be blank' |
1596 | |
1597 | |
1598 | @@ -70,14 +72,15 @@ |
1599 | """ |
1600 | # GIVEN: a a set of system arguments. |
1601 | sys.argv[1:] = ['--portable'] |
1602 | + |
1603 | # WHEN: We we parse them to expand to options |
1604 | - args = parse_options(None) |
1605 | + args = parse_options() |
1606 | + |
1607 | # THEN: the following fields will have been extracted. |
1608 | assert args.dev_version is False, 'The dev_version flag should be False' |
1609 | assert args.loglevel == 'warning', 'The log level should be set to warning' |
1610 | assert args.no_error_form is False, 'The no_error_form should be set to False' |
1611 | assert args.portable is True, 'The portable flag should be set to true' |
1612 | - assert args.style is None, 'There are no style flags to be processed' |
1613 | assert args.rargs == [], 'The service file should be blank' |
1614 | |
1615 | |
1616 | @@ -87,14 +90,15 @@ |
1617 | """ |
1618 | # GIVEN: a a set of system arguments. |
1619 | sys.argv[1:] = ['-l debug', '-d'] |
1620 | + |
1621 | # WHEN: We we parse them to expand to options |
1622 | - args = parse_options(None) |
1623 | + args = parse_options() |
1624 | + |
1625 | # THEN: the following fields will have been extracted. |
1626 | assert args.dev_version is True, 'The dev_version flag should be True' |
1627 | assert args.loglevel == ' debug', 'The log level should be set to debug' |
1628 | assert args.no_error_form is False, 'The no_error_form should be set to False' |
1629 | assert args.portable is False, 'The portable flag should be set to false' |
1630 | - assert args.style is None, 'There are no style flags to be processed' |
1631 | assert args.rargs == [], 'The service file should be blank' |
1632 | |
1633 | |
1634 | @@ -104,14 +108,15 @@ |
1635 | """ |
1636 | # GIVEN: a a set of system arguments. |
1637 | sys.argv[1:] = ['dummy_temp'] |
1638 | + |
1639 | # WHEN: We we parse them to expand to options |
1640 | - args = parse_options(None) |
1641 | + args = parse_options() |
1642 | + |
1643 | # THEN: the following fields will have been extracted. |
1644 | assert args.dev_version is False, 'The dev_version flag should be False' |
1645 | assert args.loglevel == 'warning', 'The log level should be set to warning' |
1646 | assert args.no_error_form is False, 'The no_error_form should be set to False' |
1647 | assert args.portable is False, 'The portable flag should be set to false' |
1648 | - assert args.style is None, 'There are no style flags to be processed' |
1649 | assert args.rargs == 'dummy_temp', 'The service file should not be blank' |
1650 | |
1651 | |
1652 | @@ -121,14 +126,15 @@ |
1653 | """ |
1654 | # GIVEN: a a set of system arguments. |
1655 | sys.argv[1:] = ['-l debug', 'dummy_temp'] |
1656 | + |
1657 | # WHEN: We we parse them to expand to options |
1658 | - args = parse_options(None) |
1659 | + args = parse_options() |
1660 | + |
1661 | # THEN: the following fields will have been extracted. |
1662 | assert args.dev_version is False, 'The dev_version flag should be False' |
1663 | assert args.loglevel == ' debug', 'The log level should be set to debug' |
1664 | assert args.no_error_form is False, 'The no_error_form should be set to False' |
1665 | assert args.portable is False, 'The portable flag should be set to false' |
1666 | - assert args.style is None, 'There are no style flags to be processed' |
1667 | assert args.rargs == 'dummy_temp', 'The service file should not be blank' |
1668 | |
1669 | |
1670 | |
1671 | === added file 'tests/functional/openlp_core/test_threading.py' |
1672 | --- tests/functional/openlp_core/test_threading.py 1970-01-01 00:00:00 +0000 |
1673 | +++ tests/functional/openlp_core/test_threading.py 2018-01-07 18:07:40 +0000 |
1674 | @@ -0,0 +1,89 @@ |
1675 | +# -*- coding: utf-8 -*- |
1676 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1677 | + |
1678 | +############################################################################### |
1679 | +# OpenLP - Open Source Lyrics Projection # |
1680 | +# --------------------------------------------------------------------------- # |
1681 | +# Copyright (c) 2008-2018 OpenLP Developers # |
1682 | +# --------------------------------------------------------------------------- # |
1683 | +# This program is free software; you can redistribute it and/or modify it # |
1684 | +# under the terms of the GNU General Public License as published by the Free # |
1685 | +# Software Foundation; version 2 of the License. # |
1686 | +# # |
1687 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1688 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1689 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1690 | +# more details. # |
1691 | +# # |
1692 | +# You should have received a copy of the GNU General Public License along # |
1693 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1694 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1695 | +############################################################################### |
1696 | +""" |
1697 | +Package to test the openlp.core.threading package. |
1698 | +""" |
1699 | +from unittest.mock import MagicMock, call, patch |
1700 | + |
1701 | +from openlp.core.version import run_thread |
1702 | + |
1703 | + |
1704 | +def test_run_thread_no_name(): |
1705 | + """ |
1706 | + Test that trying to run a thread without a name results in an exception being thrown |
1707 | + """ |
1708 | + # GIVEN: A fake worker |
1709 | + # WHEN: run_thread() is called without a name |
1710 | + try: |
1711 | + run_thread(MagicMock(), '') |
1712 | + assert False, 'A ValueError should have been thrown to prevent blank names' |
1713 | + except ValueError: |
1714 | + # THEN: A ValueError should have been thrown |
1715 | + assert True, 'A ValueError was correctly thrown' |
1716 | + |
1717 | + |
1718 | +@patch('openlp.core.threading.Registry') |
1719 | +def test_run_thread_exists(MockRegistry): |
1720 | + """ |
1721 | + Test that trying to run a thread with a name that already exists will throw a KeyError |
1722 | + """ |
1723 | + # GIVEN: A mocked registry with a main window object |
1724 | + mocked_main_window = MagicMock() |
1725 | + mocked_main_window.threads = {'test_thread': MagicMock()} |
1726 | + MockRegistry.return_value.get.return_value = mocked_main_window |
1727 | + |
1728 | + # WHEN: run_thread() is called |
1729 | + try: |
1730 | + run_thread(MagicMock(), 'test_thread') |
1731 | + assert False, 'A KeyError should have been thrown to show that a thread with this name already exists' |
1732 | + except KeyError: |
1733 | + assert True, 'A KeyError was correctly thrown' |
1734 | + |
1735 | + |
1736 | +@patch('openlp.core.threading.QtCore.QThread') |
1737 | +@patch('openlp.core.threading.Registry') |
1738 | +def test_run_thread(MockRegistry, MockQThread): |
1739 | + """ |
1740 | + Test that running a thread works correctly |
1741 | + """ |
1742 | + # GIVEN: A mocked registry with a main window object |
1743 | + mocked_main_window = MagicMock() |
1744 | + mocked_main_window.threads = {} |
1745 | + MockRegistry.return_value.get.return_value = mocked_main_window |
1746 | + |
1747 | + # WHEN: run_thread() is called |
1748 | + run_thread(MagicMock(), 'test_thread') |
1749 | + |
1750 | + # THEN: The thread should be in the threads list and the correct methods should have been called |
1751 | + assert len(mocked_main_window.threads.keys()) == 1, 'There should be 1 item in the list of threads' |
1752 | + assert list(mocked_main_window.threads.keys()) == ['test_thread'], 'The test_thread item should be in the list' |
1753 | + mocked_worker = mocked_main_window.threads['test_thread']['worker'] |
1754 | + mocked_thread = mocked_main_window.threads['test_thread']['thread'] |
1755 | + mocked_worker.moveToThread.assert_called_once_with(mocked_thread) |
1756 | + mocked_thread.started.connect.assert_called_once_with(mocked_worker.start) |
1757 | + expected_quit_calls = [call(mocked_thread.quit), call(mocked_worker.deleteLater)] |
1758 | + assert mocked_worker.quit.connect.call_args_list == expected_quit_calls, \ |
1759 | + 'The workers quit signal should be connected twice' |
1760 | + assert mocked_thread.finished.connect.call_args_list[0] == call(mocked_thread.deleteLater), \ |
1761 | + 'The threads finished signal should be connected to its deleteLater slot' |
1762 | + assert mocked_thread.finished.connect.call_count == 2, 'The signal should have been connected twice' |
1763 | + mocked_thread.start.assert_called_once_with() |
1764 | |
1765 | === added file 'tests/functional/openlp_core/ui/media/test_systemplayer.py' |
1766 | --- tests/functional/openlp_core/ui/media/test_systemplayer.py 1970-01-01 00:00:00 +0000 |
1767 | +++ tests/functional/openlp_core/ui/media/test_systemplayer.py 2018-01-07 18:07:40 +0000 |
1768 | @@ -0,0 +1,543 @@ |
1769 | +# -*- coding: utf-8 -*- |
1770 | +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
1771 | + |
1772 | +############################################################################### |
1773 | +# OpenLP - Open Source Lyrics Projection # |
1774 | +# --------------------------------------------------------------------------- # |
1775 | +# Copyright (c) 2008-2018 OpenLP Developers # |
1776 | +# --------------------------------------------------------------------------- # |
1777 | +# This program is free software; you can redistribute it and/or modify it # |
1778 | +# under the terms of the GNU General Public License as published by the Free # |
1779 | +# Software Foundation; version 2 of the License. # |
1780 | +# # |
1781 | +# This program is distributed in the hope that it will be useful, but WITHOUT # |
1782 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
1783 | +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
1784 | +# more details. # |
1785 | +# # |
1786 | +# You should have received a copy of the GNU General Public License along # |
1787 | +# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
1788 | +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
1789 | +############################################################################### |
1790 | +""" |
1791 | +Package to test the openlp.core.ui.media.systemplayer package. |
1792 | +""" |
1793 | +from unittest import TestCase |
1794 | +from unittest.mock import MagicMock, call, patch |
1795 | + |
1796 | +from PyQt5 import QtCore, QtMultimedia |
1797 | + |
1798 | +from openlp.core.common.registry import Registry |
1799 | +from openlp.core.ui.media import MediaState |
1800 | +from openlp.core.ui.media.systemplayer import SystemPlayer, CheckMediaWorker, ADDITIONAL_EXT |
1801 | + |
1802 | + |
1803 | +class TestSystemPlayer(TestCase): |
1804 | + """ |
1805 | + Test the system media player |
1806 | + """ |
1807 | + @patch('openlp.core.ui.media.systemplayer.mimetypes') |
1808 | + @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer') |
1809 | + def test_constructor(self, MockQMediaPlayer, mocked_mimetypes): |
1810 | + """ |
1811 | + Test the SystemPlayer constructor |
1812 | + """ |
1813 | + # GIVEN: The SystemPlayer class and a mockedQMediaPlayer |
1814 | + mocked_media_player = MagicMock() |
1815 | + mocked_media_player.supportedMimeTypes.return_value = [ |
1816 | + 'application/postscript', |
1817 | + 'audio/aiff', |
1818 | + 'audio/x-aiff', |
1819 | + 'text/html', |
1820 | + 'video/animaflex', |
1821 | + 'video/x-ms-asf' |
1822 | + ] |
1823 | + mocked_mimetypes.guess_all_extensions.side_effect = [ |
1824 | + ['.aiff'], |
1825 | + ['.aiff'], |
1826 | + ['.afl'], |
1827 | + ['.asf'] |
1828 | + ] |
1829 | + MockQMediaPlayer.return_value = mocked_media_player |
1830 | + |
1831 | + # WHEN: An object is created from it |
1832 | + player = SystemPlayer(self) |
1833 | + |
1834 | + # THEN: The correct initial values should be set up |
1835 | + assert 'system' == player.name |
1836 | + assert 'System' == player.original_name |
1837 | + assert '&System' == player.display_name |
1838 | + assert self == player.parent |
1839 | + assert ADDITIONAL_EXT == player.additional_extensions |
1840 | + MockQMediaPlayer.assert_called_once_with(None, QtMultimedia.QMediaPlayer.VideoSurface) |
1841 | + mocked_mimetypes.init.assert_called_once_with() |
1842 | + mocked_media_player.service.assert_called_once_with() |
1843 | + mocked_media_player.supportedMimeTypes.assert_called_once_with() |
1844 | + assert ['*.aiff'] == player.audio_extensions_list |
1845 | + assert ['*.afl', '*.asf'] == player.video_extensions_list |
1846 | + |
1847 | + @patch('openlp.core.ui.media.systemplayer.QtMultimediaWidgets.QVideoWidget') |
1848 | + @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer') |
1849 | + def test_setup(self, MockQMediaPlayer, MockQVideoWidget): |
1850 | + """ |
1851 | + Test the setup() method of SystemPlayer |
1852 | + """ |
1853 | + # GIVEN: A SystemPlayer instance and a mock display |
1854 | + player = SystemPlayer(self) |
1855 | + mocked_display = MagicMock() |
1856 | + mocked_display.size.return_value = [1, 2, 3, 4] |
1857 | + mocked_video_widget = MagicMock() |
1858 | + mocked_media_player = MagicMock() |
1859 | + MockQVideoWidget.return_value = mocked_video_widget |
1860 | + MockQMediaPlayer.return_value = mocked_media_player |
1861 | + |
1862 | + # WHEN: setup() is run |
1863 | + player.setup(mocked_display) |
1864 | + |
1865 | + # THEN: The player should have a display widget |
1866 | + MockQVideoWidget.assert_called_once_with(mocked_display) |
1867 | + assert mocked_video_widget == mocked_display.video_widget |
1868 | + mocked_display.size.assert_called_once_with() |
1869 | + mocked_video_widget.resize.assert_called_once_with([1, 2, 3, 4]) |
1870 | + MockQMediaPlayer.assert_called_with(mocked_display) |
1871 | + assert mocked_media_player == mocked_display.media_player |
1872 | + mocked_media_player.setVideoOutput.assert_called_once_with(mocked_video_widget) |
1873 | + mocked_video_widget.raise_.assert_called_once_with() |
1874 | + mocked_video_widget.hide.assert_called_once_with() |
1875 | + assert player.has_own_widget is True |
1876 | + |
1877 | + def test_disconnect_slots(self): |
1878 | + """ |
1879 | + Test that we the disconnect slots method catches the TypeError |
1880 | + """ |
1881 | + # GIVEN: A SystemPlayer class and a signal that throws a TypeError |
1882 | + player = SystemPlayer(self) |
1883 | + mocked_signal = MagicMock() |
1884 | + mocked_signal.disconnect.side_effect = \ |
1885 | + TypeError('disconnect() failed between \'durationChanged\' and all its connections') |
1886 | + |
1887 | + # WHEN: disconnect_slots() is called |
1888 | + player.disconnect_slots(mocked_signal) |
1889 | + |
1890 | + # THEN: disconnect should have been called and the exception should have been ignored |
1891 | + mocked_signal.disconnect.assert_called_once_with() |
1892 | + |
1893 | + def test_check_available(self): |
1894 | + """ |
1895 | + Test the check_available() method on SystemPlayer |
1896 | + """ |
1897 | + # GIVEN: A SystemPlayer instance |
1898 | + player = SystemPlayer(self) |
1899 | + |
1900 | + # WHEN: check_available is run |
1901 | + result = player.check_available() |
1902 | + |
1903 | + # THEN: it should be available |
1904 | + assert result is True |
1905 | + |
1906 | + def test_load_valid_media(self): |
1907 | + """ |
1908 | + Test the load() method of SystemPlayer with a valid media file |
1909 | + """ |
1910 | + # GIVEN: A SystemPlayer instance and a mocked display |
1911 | + player = SystemPlayer(self) |
1912 | + mocked_display = MagicMock() |
1913 | + mocked_display.controller.media_info.volume = 1 |
1914 | + mocked_display.controller.media_info.file_info.absoluteFilePath.return_value = '/path/to/file' |
1915 | + |
1916 | + # WHEN: The load() method is run |
1917 | + with patch.object(player, 'check_media') as mocked_check_media, \ |
1918 | + patch.object(player, 'volume') as mocked_volume: |
1919 | + mocked_check_media.return_value = True |
1920 | + result = player.load(mocked_display) |
1921 | + |
1922 | + # THEN: the file is sent to the video widget |
1923 | + mocked_display.controller.media_info.file_info.absoluteFilePath.assert_called_once_with() |
1924 | + mocked_check_media.assert_called_once_with('/path/to/file') |
1925 | + mocked_display.media_player.setMedia.assert_called_once_with( |
1926 | + QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile('/path/to/file'))) |
1927 | + mocked_volume.assert_called_once_with(mocked_display, 1) |
1928 | + assert result is True |
1929 | + |
1930 | + def test_load_invalid_media(self): |
1931 | + """ |
1932 | + Test the load() method of SystemPlayer with an invalid media file |
1933 | + """ |
1934 | + # GIVEN: A SystemPlayer instance and a mocked display |
1935 | + player = SystemPlayer(self) |
1936 | + mocked_display = MagicMock() |
1937 | + mocked_display.controller.media_info.volume = 1 |
1938 | + mocked_display.controller.media_info.file_info.absoluteFilePath.return_value = '/path/to/file' |
1939 | + |
1940 | + # WHEN: The load() method is run |
1941 | + with patch.object(player, 'check_media') as mocked_check_media, \ |
1942 | + patch.object(player, 'volume'): |
1943 | + mocked_check_media.return_value = False |
1944 | + result = player.load(mocked_display) |
1945 | + |
1946 | + # THEN: stuff |
1947 | + mocked_display.controller.media_info.file_info.absoluteFilePath.assert_called_once_with() |
1948 | + mocked_check_media.assert_called_once_with('/path/to/file') |
1949 | + assert result is False |
1950 | + |
1951 | + def test_resize(self): |
1952 | + """ |
1953 | + Test the resize() method of the SystemPlayer |
1954 | + """ |
1955 | + # GIVEN: A SystemPlayer instance and a mocked display |
1956 | + player = SystemPlayer(self) |
1957 | + mocked_display = MagicMock() |
1958 | + mocked_display.size.return_value = [1, 2, 3, 4] |
1959 | + |
1960 | + # WHEN: The resize() method is called |
1961 | + player.resize(mocked_display) |
1962 | + |
1963 | + # THEN: The player is resized |
1964 | + mocked_display.size.assert_called_once_with() |
1965 | + mocked_display.video_widget.resize.assert_called_once_with([1, 2, 3, 4]) |
1966 | + |
1967 | + @patch('openlp.core.ui.media.systemplayer.functools') |
1968 | + def test_play_is_live(self, mocked_functools): |
1969 | + """ |
1970 | + Test the play() method of the SystemPlayer on the live display |
1971 | + """ |
1972 | + # GIVEN: A SystemPlayer instance and a mocked display |
1973 | + mocked_functools.partial.return_value = 'function' |
1974 | + player = SystemPlayer(self) |
1975 | + mocked_display = MagicMock() |
1976 | + mocked_display.controller.is_live = True |
1977 | + mocked_display.controller.media_info.start_time = 1 |
1978 | + mocked_display.controller.media_info.volume = 1 |
1979 | + |
1980 | + # WHEN: play() is called |
1981 | + with patch.object(player, 'get_live_state') as mocked_get_live_state, \ |
1982 | + patch.object(player, 'seek') as mocked_seek, \ |
1983 | + patch.object(player, 'volume') as mocked_volume, \ |
1984 | + patch.object(player, 'set_state') as mocked_set_state, \ |
1985 | + patch.object(player, 'disconnect_slots') as mocked_disconnect_slots: |
1986 | + mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PlayingState |
1987 | + result = player.play(mocked_display) |
1988 | + |
1989 | + # THEN: the media file is played |
1990 | + mocked_get_live_state.assert_called_once_with() |
1991 | + mocked_display.media_player.play.assert_called_once_with() |
1992 | + mocked_seek.assert_called_once_with(mocked_display, 1000) |
1993 | + mocked_volume.assert_called_once_with(mocked_display, 1) |
1994 | + mocked_disconnect_slots.assert_called_once_with(mocked_display.media_player.durationChanged) |
1995 | + mocked_display.media_player.durationChanged.connect.assert_called_once_with('function') |
1996 | + mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display) |
1997 | + mocked_display.video_widget.raise_.assert_called_once_with() |
1998 | + assert result is True |
1999 | + |
2000 | + @patch('openlp.core.ui.media.systemplayer.functools') |
2001 | + def test_play_is_preview(self, mocked_functools): |
2002 | + """ |
2003 | + Test the play() method of the SystemPlayer on the preview display |
2004 | + """ |
2005 | + # GIVEN: A SystemPlayer instance and a mocked display |
2006 | + mocked_functools.partial.return_value = 'function' |
2007 | + player = SystemPlayer(self) |
2008 | + mocked_display = MagicMock() |
2009 | + mocked_display.controller.is_live = False |
2010 | + mocked_display.controller.media_info.start_time = 1 |
2011 | + mocked_display.controller.media_info.volume = 1 |
2012 | + |
2013 | + # WHEN: play() is called |
2014 | + with patch.object(player, 'get_preview_state') as mocked_get_preview_state, \ |
2015 | + patch.object(player, 'seek') as mocked_seek, \ |
2016 | + patch.object(player, 'volume') as mocked_volume, \ |
2017 | + patch.object(player, 'set_state') as mocked_set_state: |
2018 | + mocked_get_preview_state.return_value = QtMultimedia.QMediaPlayer.PlayingState |
2019 | + result = player.play(mocked_display) |
2020 | + |
2021 | + # THEN: the media file is played |
2022 | + mocked_get_preview_state.assert_called_once_with() |
2023 | + mocked_display.media_player.play.assert_called_once_with() |
2024 | + mocked_seek.assert_called_once_with(mocked_display, 1000) |
2025 | + mocked_volume.assert_called_once_with(mocked_display, 1) |
2026 | + mocked_display.media_player.durationChanged.connect.assert_called_once_with('function') |
2027 | + mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display) |
2028 | + mocked_display.video_widget.raise_.assert_called_once_with() |
2029 | + assert result is True |
2030 | + |
2031 | + def test_pause_is_live(self): |
2032 | + """ |
2033 | + Test the pause() method of the SystemPlayer on the live display |
2034 | + """ |
2035 | + # GIVEN: A SystemPlayer instance |
2036 | + player = SystemPlayer(self) |
2037 | + mocked_display = MagicMock() |
2038 | + mocked_display.controller.is_live = True |
2039 | + |
2040 | + # WHEN: The pause method is called |
2041 | + with patch.object(player, 'get_live_state') as mocked_get_live_state, \ |
2042 | + patch.object(player, 'set_state') as mocked_set_state: |
2043 | + mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PausedState |
2044 | + player.pause(mocked_display) |
2045 | + |
2046 | + # THEN: The video is paused |
2047 | + mocked_display.media_player.pause.assert_called_once_with() |
2048 | + mocked_get_live_state.assert_called_once_with() |
2049 | + mocked_set_state.assert_called_once_with(MediaState.Paused, mocked_display) |
2050 | + |
2051 | + def test_pause_is_preview(self): |
2052 | + """ |
2053 | + Test the pause() method of the SystemPlayer on the preview display |
2054 | + """ |
2055 | + # GIVEN: A SystemPlayer instance |
2056 | + player = SystemPlayer(self) |
2057 | + mocked_display = MagicMock() |
2058 | + mocked_display.controller.is_live = False |
2059 | + |
2060 | + # WHEN: The pause method is called |
2061 | + with patch.object(player, 'get_preview_state') as mocked_get_preview_state, \ |
2062 | + patch.object(player, 'set_state') as mocked_set_state: |
2063 | + mocked_get_preview_state.return_value = QtMultimedia.QMediaPlayer.PausedState |
2064 | + player.pause(mocked_display) |
2065 | + |
2066 | + # THEN: The video is paused |
2067 | + mocked_display.media_player.pause.assert_called_once_with() |
2068 | + mocked_get_preview_state.assert_called_once_with() |
2069 | + mocked_set_state.assert_called_once_with(MediaState.Paused, mocked_display) |
2070 | + |
2071 | + def test_stop(self): |
2072 | + """ |
2073 | + Test the stop() method of the SystemPlayer |
2074 | + """ |
2075 | + # GIVEN: A SystemPlayer instance |
2076 | + player = SystemPlayer(self) |
2077 | + mocked_display = MagicMock() |
2078 | + |
2079 | + # WHEN: The stop method is called |
2080 | + with patch.object(player, 'set_visible') as mocked_set_visible, \ |
2081 | + patch.object(player, 'set_state') as mocked_set_state: |
2082 | + player.stop(mocked_display) |
2083 | + |
2084 | + # THEN: The video is stopped |
2085 | + mocked_display.media_player.stop.assert_called_once_with() |
2086 | + mocked_set_visible.assert_called_once_with(mocked_display, False) |
2087 | + mocked_set_state.assert_called_once_with(MediaState.Stopped, mocked_display) |
2088 | + |
2089 | + def test_volume(self): |
2090 | + """ |
2091 | + Test the volume() method of the SystemPlayer |
2092 | + """ |
2093 | + # GIVEN: A SystemPlayer instance |
2094 | + player = SystemPlayer(self) |
2095 | + mocked_display = MagicMock() |
2096 | + mocked_display.has_audio = True |
2097 | + |
2098 | + # WHEN: The stop method is called |
2099 | + player.volume(mocked_display, 2) |
2100 | + |
2101 | + # THEN: The video is stopped |
2102 | + mocked_display.media_player.setVolume.assert_called_once_with(2) |
2103 | + |
2104 | + def test_seek(self): |
2105 | + """ |
2106 | + Test the seek() method of the SystemPlayer |
2107 | + """ |
2108 | + # GIVEN: A SystemPlayer instance |
2109 | + player = SystemPlayer(self) |
2110 | + mocked_display = MagicMock() |
2111 | + |
2112 | + # WHEN: The stop method is called |
2113 | + player.seek(mocked_display, 2) |
2114 | + |
2115 | + # THEN: The video is stopped |
2116 | + mocked_display.media_player.setPosition.assert_called_once_with(2) |
2117 | + |
2118 | + def test_reset(self): |
2119 | + """ |
2120 | + Test the reset() method of the SystemPlayer |
2121 | + """ |
2122 | + # GIVEN: A SystemPlayer instance |
2123 | + player = SystemPlayer(self) |
2124 | + mocked_display = MagicMock() |
2125 | + |
2126 | + # WHEN: reset() is called |
2127 | + with patch.object(player, 'set_state') as mocked_set_state, \ |
2128 | + patch.object(player, 'set_visible') as mocked_set_visible: |
2129 | + player.reset(mocked_display) |
2130 | + |
2131 | + # THEN: The media player is reset |
2132 | + mocked_display.media_player.stop() |
2133 | + mocked_display.media_player.setMedia.assert_called_once_with(QtMultimedia.QMediaContent()) |
2134 | + mocked_set_visible.assert_called_once_with(mocked_display, False) |
2135 | + mocked_display.video_widget.setVisible.assert_called_once_with(False) |
2136 | + mocked_set_state.assert_called_once_with(MediaState.Off, mocked_display) |
2137 | + |
2138 | + def test_set_visible(self): |
2139 | + """ |
2140 | + Test the set_visible() method on the SystemPlayer |
2141 | + """ |
2142 | + # GIVEN: A SystemPlayer instance and a mocked display |
2143 | + player = SystemPlayer(self) |
2144 | + player.has_own_widget = True |
2145 | + mocked_display = MagicMock() |
2146 | + |
2147 | + # WHEN: set_visible() is called |
2148 | + player.set_visible(mocked_display, True) |
2149 | + |
2150 | + # THEN: The widget should be visible |
2151 | + mocked_display.video_widget.setVisible.assert_called_once_with(True) |
2152 | + |
2153 | + def test_set_duration(self): |
2154 | + """ |
2155 | + Test the set_duration() method of the SystemPlayer |
2156 | + """ |
2157 | + # GIVEN: a mocked controller |
2158 | + mocked_controller = MagicMock() |
2159 | + mocked_controller.media_info.length = 5 |
2160 | + |
2161 | + # WHEN: The set_duration() is called. NB: the 10 here is ignored by the code |
2162 | + SystemPlayer.set_duration(mocked_controller, 10) |
2163 | + |
2164 | + # THEN: The maximum length of the slider should be set |
2165 | + mocked_controller.seek_slider.setMaximum.assert_called_once_with(5) |
2166 | + |
2167 | + def test_update_ui(self): |
2168 | + """ |
2169 | + Test the update_ui() method on the SystemPlayer |
2170 | + """ |
2171 | + # GIVEN: A SystemPlayer instance |
2172 | + player = SystemPlayer(self) |
2173 | + player.state = [MediaState.Playing, MediaState.Playing] |
2174 | + mocked_display = MagicMock() |
2175 | + mocked_display.media_player.state.return_value = QtMultimedia.QMediaPlayer.PausedState |
2176 | + mocked_display.controller.media_info.end_time = 1 |
2177 | + mocked_display.media_player.position.return_value = 2 |
2178 | + mocked_display.controller.seek_slider.isSliderDown.return_value = False |
2179 | + |
2180 | + # WHEN: update_ui() is called |
2181 | + with patch.object(player, 'stop') as mocked_stop, \ |
2182 | + patch.object(player, 'set_visible') as mocked_set_visible: |
2183 | + player.update_ui(mocked_display) |
2184 | + |
2185 | + # THEN: The UI is updated |
2186 | + expected_stop_calls = [call(mocked_display)] |
2187 | + expected_position_calls = [call(), call()] |
2188 | + expected_block_signals_calls = [call(True), call(False)] |
2189 | + mocked_display.media_player.state.assert_called_once_with() |
2190 | + assert 1 == mocked_stop.call_count |
2191 | + assert expected_stop_calls == mocked_stop.call_args_list |
2192 | + assert 2 == mocked_display.media_player.position.call_count |
2193 | + assert expected_position_calls == mocked_display.media_player.position.call_args_list |
2194 | + mocked_set_visible.assert_called_once_with(mocked_display, False) |
2195 | + mocked_display.controller.seek_slider.isSliderDown.assert_called_once_with() |
2196 | + assert expected_block_signals_calls == mocked_display.controller.seek_slider.blockSignals.call_args_list |
2197 | + mocked_display.controller.seek_slider.setSliderPosition.assert_called_once_with(2) |
2198 | + |
2199 | + def test_get_media_display_css(self): |
2200 | + """ |
2201 | + Test the get_media_display_css() method of the SystemPlayer |
2202 | + """ |
2203 | + # GIVEN: A SystemPlayer instance |
2204 | + player = SystemPlayer(self) |
2205 | + |
2206 | + # WHEN: get_media_display_css() is called |
2207 | + result = player.get_media_display_css() |
2208 | + |
2209 | + # THEN: The css should be empty |
2210 | + assert '' == result |
2211 | + |
2212 | + @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer') |
2213 | + def test_get_info(self, MockQMediaPlayer): |
2214 | + """ |
2215 | + Test the get_info() method of the SystemPlayer |
2216 | + """ |
2217 | + # GIVEN: A SystemPlayer instance |
2218 | + mocked_media_player = MagicMock() |
2219 | + mocked_media_player.supportedMimeTypes.return_value = [] |
2220 | + MockQMediaPlayer.return_value = mocked_media_player |
2221 | + player = SystemPlayer(self) |
2222 | + |
2223 | + # WHEN: get_info() is called |
2224 | + result = player.get_info() |
2225 | + |
2226 | + # THEN: The info should be correct |
2227 | + expected_info = 'This media player uses your operating system to provide media capabilities.<br/> ' \ |
2228 | + '<strong>Audio</strong><br/>[]<br/><strong>Video</strong><br/>[]<br/>' |
2229 | + assert expected_info == result |
2230 | + |
2231 | + @patch('openlp.core.ui.media.systemplayer.CheckMediaWorker') |
2232 | + @patch('openlp.core.ui.media.systemplayer.run_thread') |
2233 | + @patch('openlp.core.ui.media.systemplayer.is_thread_finished') |
2234 | + def test_check_media(self, mocked_is_thread_finished, mocked_run_thread, MockCheckMediaWorker): |
2235 | + """ |
2236 | + Test the check_media() method of the SystemPlayer |
2237 | + """ |
2238 | + # GIVEN: A SystemPlayer instance and a mocked thread |
2239 | + valid_file = '/path/to/video.ogv' |
2240 | + mocked_application = MagicMock() |
2241 | + Registry().create() |
2242 | + Registry().register('application', mocked_application) |
2243 | + player = SystemPlayer(self) |
2244 | + mocked_is_thread_finished.side_effect = [False, True] |
2245 | + mocked_check_media_worker = MagicMock() |
2246 | + mocked_check_media_worker.result = True |
2247 | + MockCheckMediaWorker.return_value = mocked_check_media_worker |
2248 | + |
2249 | + # WHEN: check_media() is called with a valid media file |
2250 | + result = player.check_media(valid_file) |
2251 | + |
2252 | + # THEN: It should return True |
2253 | + MockCheckMediaWorker.assert_called_once_with(valid_file) |
2254 | + mocked_check_media_worker.setVolume.assert_called_once_with(0) |
2255 | + mocked_run_thread.assert_called_once_with(mocked_check_media_worker, 'check_media') |
2256 | + mocked_is_thread_finished.assert_called_with('check_media') |
2257 | + assert mocked_is_thread_finished.call_count == 2, 'is_thread_finished() should have been called twice' |
2258 | + mocked_application.processEvents.assert_called_once_with() |
2259 | + assert result is True |
2260 | + |
2261 | + |
2262 | +class TestCheckMediaWorker(TestCase): |
2263 | + """ |
2264 | + Test the CheckMediaWorker class |
2265 | + """ |
2266 | + def test_constructor(self): |
2267 | + """ |
2268 | + Test the constructor of the CheckMediaWorker class |
2269 | + """ |
2270 | + # GIVEN: A file path |
2271 | + path = 'file.ogv' |
2272 | + |
2273 | + # WHEN: The CheckMediaWorker object is instantiated |
2274 | + worker = CheckMediaWorker(path) |
2275 | + |
2276 | + # THEN: The correct values should be set up |
2277 | + assert worker is not None |
2278 | + |
2279 | + def test_signals_media(self): |
2280 | + """ |
2281 | + Test the signals() signal of the CheckMediaWorker class with a "media" origin |
2282 | + """ |
2283 | + # GIVEN: A CheckMediaWorker instance |
2284 | + worker = CheckMediaWorker('file.ogv') |
2285 | + |
2286 | + # WHEN: signals() is called with media and BufferedMedia |
2287 | + with patch.object(worker, 'stop') as mocked_stop, \ |
2288 | + patch.object(worker, 'quit') as mocked_quit: |
2289 | + worker.signals('media', worker.BufferedMedia) |
2290 | + |
2291 | + # THEN: The worker should exit and the result should be True |
2292 | + mocked_stop.assert_called_once_with() |
2293 | + mocked_quit.emit.assert_called_once_with() |
2294 | + assert worker.result is True |
2295 | + |
2296 | + def test_signals_error(self): |
2297 | + """ |
2298 | + Test the signals() signal of the CheckMediaWorker class with a "error" origin |
2299 | + """ |
2300 | + # GIVEN: A CheckMediaWorker instance |
2301 | + worker = CheckMediaWorker('file.ogv') |
2302 | + |
2303 | + # WHEN: signals() is called with error and BufferedMedia |
2304 | + with patch.object(worker, 'stop') as mocked_stop, \ |
2305 | + patch.object(worker, 'quit') as mocked_quit: |
2306 | + worker.signals('error', None) |
2307 | + |
2308 | + # THEN: The worker should exit and the result should be True |
2309 | + mocked_stop.assert_called_once_with() |
2310 | + mocked_quit.emit.assert_called_once_with() |
2311 | + assert worker.result is False |
2312 | |
2313 | === removed file 'tests/functional/openlp_core/ui/media/test_systemplayer.py' |
2314 | --- tests/functional/openlp_core/ui/media/test_systemplayer.py 2017-12-29 09:15:48 +0000 |
2315 | +++ tests/functional/openlp_core/ui/media/test_systemplayer.py 1970-01-01 00:00:00 +0000 |
2316 | @@ -1,549 +0,0 @@ |
2317 | -# -*- coding: utf-8 -*- |
2318 | -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 |
2319 | - |
2320 | -############################################################################### |
2321 | -# OpenLP - Open Source Lyrics Projection # |
2322 | -# --------------------------------------------------------------------------- # |
2323 | -# Copyright (c) 2008-2018 OpenLP Developers # |
2324 | -# --------------------------------------------------------------------------- # |
2325 | -# This program is free software; you can redistribute it and/or modify it # |
2326 | -# under the terms of the GNU General Public License as published by the Free # |
2327 | -# Software Foundation; version 2 of the License. # |
2328 | -# # |
2329 | -# This program is distributed in the hope that it will be useful, but WITHOUT # |
2330 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # |
2331 | -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # |
2332 | -# more details. # |
2333 | -# # |
2334 | -# You should have received a copy of the GNU General Public License along # |
2335 | -# with this program; if not, write to the Free Software Foundation, Inc., 59 # |
2336 | -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # |
2337 | -############################################################################### |
2338 | -""" |
2339 | -Package to test the openlp.core.ui.media.systemplayer package. |
2340 | -""" |
2341 | -from unittest import TestCase |
2342 | -from unittest.mock import MagicMock, call, patch |
2343 | - |
2344 | -from PyQt5 import QtCore, QtMultimedia |
2345 | - |
2346 | -from openlp.core.common.registry import Registry |
2347 | -from openlp.core.ui.media import MediaState |
2348 | -from openlp.core.ui.media.systemplayer import SystemPlayer, CheckMediaWorker, ADDITIONAL_EXT |
2349 | - |
2350 | - |
2351 | -class TestSystemPlayer(TestCase): |
2352 | - """ |
2353 | - Test the system media player |
2354 | - """ |
2355 | - @patch('openlp.core.ui.media.systemplayer.mimetypes') |
2356 | - @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer') |
2357 | - def test_constructor(self, MockQMediaPlayer, mocked_mimetypes): |
2358 | - """ |
2359 | - Test the SystemPlayer constructor |
2360 | - """ |
2361 | - # GIVEN: The SystemPlayer class and a mockedQMediaPlayer |
2362 | - mocked_media_player = MagicMock() |
2363 | - mocked_media_player.supportedMimeTypes.return_value = [ |
2364 | - 'application/postscript', |
2365 | - 'audio/aiff', |
2366 | - 'audio/x-aiff', |
2367 | - 'text/html', |
2368 | - 'video/animaflex', |
2369 | - 'video/x-ms-asf' |
2370 | - ] |
2371 | - mocked_mimetypes.guess_all_extensions.side_effect = [ |
2372 | - ['.aiff'], |
2373 | - ['.aiff'], |
2374 | - ['.afl'], |
2375 | - ['.asf'] |
2376 | - ] |
2377 | - MockQMediaPlayer.return_value = mocked_media_player |
2378 | - |
2379 | - # WHEN: An object is created from it |
2380 | - player = SystemPlayer(self) |
2381 | - |
2382 | - # THEN: The correct initial values should be set up |
2383 | - assert 'system' == player.name |
2384 | - assert 'System' == player.original_name |
2385 | - assert '&System' == player.display_name |
2386 | - assert self == player.parent |
2387 | - assert ADDITIONAL_EXT == player.additional_extensions |
2388 | - MockQMediaPlayer.assert_called_once_with(None, QtMultimedia.QMediaPlayer.VideoSurface) |
2389 | - mocked_mimetypes.init.assert_called_once_with() |
2390 | - mocked_media_player.service.assert_called_once_with() |
2391 | - mocked_media_player.supportedMimeTypes.assert_called_once_with() |
2392 | - assert ['*.aiff'] == player.audio_extensions_list |
2393 | - assert ['*.afl', '*.asf'] == player.video_extensions_list |
2394 | - |
2395 | - @patch('openlp.core.ui.media.systemplayer.QtMultimediaWidgets.QVideoWidget') |
2396 | - @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer') |
2397 | - def test_setup(self, MockQMediaPlayer, MockQVideoWidget): |
2398 | - """ |
2399 | - Test the setup() method of SystemPlayer |
2400 | - """ |
2401 | - # GIVEN: A SystemPlayer instance and a mock display |
2402 | - player = SystemPlayer(self) |
2403 | - mocked_display = MagicMock() |
2404 | - mocked_display.size.return_value = [1, 2, 3, 4] |
2405 | - mocked_video_widget = MagicMock() |
2406 | - mocked_media_player = MagicMock() |
2407 | - MockQVideoWidget.return_value = mocked_video_widget |
2408 | - MockQMediaPlayer.return_value = mocked_media_player |
2409 | - |
2410 | - # WHEN: setup() is run |
2411 | - player.setup(mocked_display) |
2412 | - |
2413 | - # THEN: The player should have a display widget |
2414 | - MockQVideoWidget.assert_called_once_with(mocked_display) |
2415 | - assert mocked_video_widget == mocked_display.video_widget |
2416 | - mocked_display.size.assert_called_once_with() |
2417 | - mocked_video_widget.resize.assert_called_once_with([1, 2, 3, 4]) |
2418 | - MockQMediaPlayer.assert_called_with(mocked_display) |
2419 | - assert mocked_media_player == mocked_display.media_player |
2420 | - mocked_media_player.setVideoOutput.assert_called_once_with(mocked_video_widget) |
2421 | - mocked_video_widget.raise_.assert_called_once_with() |
2422 | - mocked_video_widget.hide.assert_called_once_with() |
2423 | - assert player.has_own_widget is True |
2424 | - |
2425 | - def test_disconnect_slots(self): |
2426 | - """ |
2427 | - Test that we the disconnect slots method catches the TypeError |
2428 | - """ |
2429 | - # GIVEN: A SystemPlayer class and a signal that throws a TypeError |
2430 | - player = SystemPlayer(self) |
2431 | - mocked_signal = MagicMock() |
2432 | - mocked_signal.disconnect.side_effect = \ |
2433 | - TypeError('disconnect() failed between \'durationChanged\' and all its connections') |
2434 | - |
2435 | - # WHEN: disconnect_slots() is called |
2436 | - player.disconnect_slots(mocked_signal) |
2437 | - |
2438 | - # THEN: disconnect should have been called and the exception should have been ignored |
2439 | - mocked_signal.disconnect.assert_called_once_with() |
2440 | - |
2441 | - def test_check_available(self): |
2442 | - """ |
2443 | - Test the check_available() method on SystemPlayer |
2444 | - """ |
2445 | - # GIVEN: A SystemPlayer instance |
2446 | - player = SystemPlayer(self) |
2447 | - |
2448 | - # WHEN: check_available is run |
2449 | - result = player.check_available() |
2450 | - |
2451 | - # THEN: it should be available |
2452 | - assert result is True |
2453 | - |
2454 | - def test_load_valid_media(self): |
2455 | - """ |
2456 | - Test the load() method of SystemPlayer with a valid media file |
2457 | - """ |
2458 | - # GIVEN: A SystemPlayer instance and a mocked display |
2459 | - player = SystemPlayer(self) |
2460 | - mocked_display = MagicMock() |
2461 | - mocked_display.controller.media_info.volume = 1 |
2462 | - mocked_display.controller.media_info.file_info.absoluteFilePath.return_value = '/path/to/file' |
2463 | - |
2464 | - # WHEN: The load() method is run |
2465 | - with patch.object(player, 'check_media') as mocked_check_media, \ |
2466 | - patch.object(player, 'volume') as mocked_volume: |
2467 | - mocked_check_media.return_value = True |
2468 | - result = player.load(mocked_display) |
2469 | - |
2470 | - # THEN: the file is sent to the video widget |
2471 | - mocked_display.controller.media_info.file_info.absoluteFilePath.assert_called_once_with() |
2472 | - mocked_check_media.assert_called_once_with('/path/to/file') |
2473 | - mocked_display.media_player.setMedia.assert_called_once_with( |
2474 | - QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile('/path/to/file'))) |
2475 | - mocked_volume.assert_called_once_with(mocked_display, 1) |
2476 | - assert result is True |
2477 | - |
2478 | - def test_load_invalid_media(self): |
2479 | - """ |
2480 | - Test the load() method of SystemPlayer with an invalid media file |
2481 | - """ |
2482 | - # GIVEN: A SystemPlayer instance and a mocked display |
2483 | - player = SystemPlayer(self) |
2484 | - mocked_display = MagicMock() |
2485 | - mocked_display.controller.media_info.volume = 1 |
2486 | - mocked_display.controller.media_info.file_info.absoluteFilePath.return_value = '/path/to/file' |
2487 | - |
2488 | - # WHEN: The load() method is run |
2489 | - with patch.object(player, 'check_media') as mocked_check_media, \ |
2490 | - patch.object(player, 'volume') as mocked_volume: |
2491 | - mocked_check_media.return_value = False |
2492 | - result = player.load(mocked_display) |
2493 | - |
2494 | - # THEN: stuff |
2495 | - mocked_display.controller.media_info.file_info.absoluteFilePath.assert_called_once_with() |
2496 | - mocked_check_media.assert_called_once_with('/path/to/file') |
2497 | - assert result is False |
2498 | - |
2499 | - def test_resize(self): |
2500 | - """ |
2501 | - Test the resize() method of the SystemPlayer |
2502 | - """ |
2503 | - # GIVEN: A SystemPlayer instance and a mocked display |
2504 | - player = SystemPlayer(self) |
2505 | - mocked_display = MagicMock() |
2506 | - mocked_display.size.return_value = [1, 2, 3, 4] |
2507 | - |
2508 | - # WHEN: The resize() method is called |
2509 | - player.resize(mocked_display) |
2510 | - |
2511 | - # THEN: The player is resized |
2512 | - mocked_display.size.assert_called_once_with() |
2513 | - mocked_display.video_widget.resize.assert_called_once_with([1, 2, 3, 4]) |
2514 | - |
2515 | - @patch('openlp.core.ui.media.systemplayer.functools') |
2516 | - def test_play_is_live(self, mocked_functools): |
2517 | - """ |
2518 | - Test the play() method of the SystemPlayer on the live display |
2519 | - """ |
2520 | - # GIVEN: A SystemPlayer instance and a mocked display |
2521 | - mocked_functools.partial.return_value = 'function' |
2522 | - player = SystemPlayer(self) |
2523 | - mocked_display = MagicMock() |
2524 | - mocked_display.controller.is_live = True |
2525 | - mocked_display.controller.media_info.start_time = 1 |
2526 | - mocked_display.controller.media_info.volume = 1 |
2527 | - |
2528 | - # WHEN: play() is called |
2529 | - with patch.object(player, 'get_live_state') as mocked_get_live_state, \ |
2530 | - patch.object(player, 'seek') as mocked_seek, \ |
2531 | - patch.object(player, 'volume') as mocked_volume, \ |
2532 | - patch.object(player, 'set_state') as mocked_set_state, \ |
2533 | - patch.object(player, 'disconnect_slots') as mocked_disconnect_slots: |
2534 | - mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PlayingState |
2535 | - result = player.play(mocked_display) |
2536 | - |
2537 | - # THEN: the media file is played |
2538 | - mocked_get_live_state.assert_called_once_with() |
2539 | - mocked_display.media_player.play.assert_called_once_with() |
2540 | - mocked_seek.assert_called_once_with(mocked_display, 1000) |
2541 | - mocked_volume.assert_called_once_with(mocked_display, 1) |
2542 | - mocked_disconnect_slots.assert_called_once_with(mocked_display.media_player.durationChanged) |
2543 | - mocked_display.media_player.durationChanged.connect.assert_called_once_with('function') |
2544 | - mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display) |
2545 | - mocked_display.video_widget.raise_.assert_called_once_with() |
2546 | - assert result is True |
2547 | - |
2548 | - @patch('openlp.core.ui.media.systemplayer.functools') |
2549 | - def test_play_is_preview(self, mocked_functools): |
2550 | - """ |
2551 | - Test the play() method of the SystemPlayer on the preview display |
2552 | - """ |
2553 | - # GIVEN: A SystemPlayer instance and a mocked display |
2554 | - mocked_functools.partial.return_value = 'function' |
2555 | - player = SystemPlayer(self) |
2556 | - mocked_display = MagicMock() |
2557 | - mocked_display.controller.is_live = False |
2558 | - mocked_display.controller.media_info.start_time = 1 |
2559 | - mocked_display.controller.media_info.volume = 1 |
2560 | - |
2561 | - # WHEN: play() is called |
2562 | - with patch.object(player, 'get_preview_state') as mocked_get_preview_state, \ |
2563 | - patch.object(player, 'seek') as mocked_seek, \ |
2564 | - patch.object(player, 'volume') as mocked_volume, \ |
2565 | - patch.object(player, 'set_state') as mocked_set_state: |
2566 | - mocked_get_preview_state.return_value = QtMultimedia.QMediaPlayer.PlayingState |
2567 | - result = player.play(mocked_display) |
2568 | - |
2569 | - # THEN: the media file is played |
2570 | - mocked_get_preview_state.assert_called_once_with() |
2571 | - mocked_display.media_player.play.assert_called_once_with() |
2572 | - mocked_seek.assert_called_once_with(mocked_display, 1000) |
2573 | - mocked_volume.assert_called_once_with(mocked_display, 1) |
2574 | - mocked_display.media_player.durationChanged.connect.assert_called_once_with('function') |
2575 | - mocked_set_state.assert_called_once_with(MediaState.Playing, mocked_display) |
2576 | - mocked_display.video_widget.raise_.assert_called_once_with() |
2577 | - assert result is True |
2578 | - |
2579 | - def test_pause_is_live(self): |
2580 | - """ |
2581 | - Test the pause() method of the SystemPlayer on the live display |
2582 | - """ |
2583 | - # GIVEN: A SystemPlayer instance |
2584 | - player = SystemPlayer(self) |
2585 | - mocked_display = MagicMock() |
2586 | - mocked_display.controller.is_live = True |
2587 | - |
2588 | - # WHEN: The pause method is called |
2589 | - with patch.object(player, 'get_live_state') as mocked_get_live_state, \ |
2590 | - patch.object(player, 'set_state') as mocked_set_state: |
2591 | - mocked_get_live_state.return_value = QtMultimedia.QMediaPlayer.PausedState |
2592 | - player.pause(mocked_display) |
2593 | - |
2594 | - # THEN: The video is paused |
2595 | - mocked_display.media_player.pause.assert_called_once_with() |
2596 | - mocked_get_live_state.assert_called_once_with() |
2597 | - mocked_set_state.assert_called_once_with(MediaState.Paused, mocked_display) |
2598 | - |
2599 | - def test_pause_is_preview(self): |
2600 | - """ |
2601 | - Test the pause() method of the SystemPlayer on the preview display |
2602 | - """ |
2603 | - # GIVEN: A SystemPlayer instance |
2604 | - player = SystemPlayer(self) |
2605 | - mocked_display = MagicMock() |
2606 | - mocked_display.controller.is_live = False |
2607 | - |
2608 | - # WHEN: The pause method is called |
2609 | - with patch.object(player, 'get_preview_state') as mocked_get_preview_state, \ |
2610 | - patch.object(player, 'set_state') as mocked_set_state: |
2611 | - mocked_get_preview_state.return_value = QtMultimedia.QMediaPlayer.PausedState |
2612 | - player.pause(mocked_display) |
2613 | - |
2614 | - # THEN: The video is paused |
2615 | - mocked_display.media_player.pause.assert_called_once_with() |
2616 | - mocked_get_preview_state.assert_called_once_with() |
2617 | - mocked_set_state.assert_called_once_with(MediaState.Paused, mocked_display) |
2618 | - |
2619 | - def test_stop(self): |
2620 | - """ |
2621 | - Test the stop() method of the SystemPlayer |
2622 | - """ |
2623 | - # GIVEN: A SystemPlayer instance |
2624 | - player = SystemPlayer(self) |
2625 | - mocked_display = MagicMock() |
2626 | - |
2627 | - # WHEN: The stop method is called |
2628 | - with patch.object(player, 'set_visible') as mocked_set_visible, \ |
2629 | - patch.object(player, 'set_state') as mocked_set_state: |
2630 | - player.stop(mocked_display) |
2631 | - |
2632 | - # THEN: The video is stopped |
2633 | - mocked_display.media_player.stop.assert_called_once_with() |
2634 | - mocked_set_visible.assert_called_once_with(mocked_display, False) |
2635 | - mocked_set_state.assert_called_once_with(MediaState.Stopped, mocked_display) |
2636 | - |
2637 | - def test_volume(self): |
2638 | - """ |
2639 | - Test the volume() method of the SystemPlayer |
2640 | - """ |
2641 | - # GIVEN: A SystemPlayer instance |
2642 | - player = SystemPlayer(self) |
2643 | - mocked_display = MagicMock() |
2644 | - mocked_display.has_audio = True |
2645 | - |
2646 | - # WHEN: The stop method is called |
2647 | - player.volume(mocked_display, 2) |
2648 | - |
2649 | - # THEN: The video is stopped |
2650 | - mocked_display.media_player.setVolume.assert_called_once_with(2) |
2651 | - |
2652 | - def test_seek(self): |
2653 | - """ |
2654 | - Test the seek() method of the SystemPlayer |
2655 | - """ |
2656 | - # GIVEN: A SystemPlayer instance |
2657 | - player = SystemPlayer(self) |
2658 | - mocked_display = MagicMock() |
2659 | - |
2660 | - # WHEN: The stop method is called |
2661 | - player.seek(mocked_display, 2) |
2662 | - |
2663 | - # THEN: The video is stopped |
2664 | - mocked_display.media_player.setPosition.assert_called_once_with(2) |
2665 | - |
2666 | - def test_reset(self): |
2667 | - """ |
2668 | - Test the reset() method of the SystemPlayer |
2669 | - """ |
2670 | - # GIVEN: A SystemPlayer instance |
2671 | - player = SystemPlayer(self) |
2672 | - mocked_display = MagicMock() |
2673 | - |
2674 | - # WHEN: reset() is called |
2675 | - with patch.object(player, 'set_state') as mocked_set_state, \ |
2676 | - patch.object(player, 'set_visible') as mocked_set_visible: |
2677 | - player.reset(mocked_display) |
2678 | - |
2679 | - # THEN: The media player is reset |
2680 | - mocked_display.media_player.stop() |
2681 | - mocked_display.media_player.setMedia.assert_called_once_with(QtMultimedia.QMediaContent()) |
2682 | - mocked_set_visible.assert_called_once_with(mocked_display, False) |
2683 | - mocked_display.video_widget.setVisible.assert_called_once_with(False) |
2684 | - mocked_set_state.assert_called_once_with(MediaState.Off, mocked_display) |
2685 | - |
2686 | - def test_set_visible(self): |
2687 | - """ |
2688 | - Test the set_visible() method on the SystemPlayer |
2689 | - """ |
2690 | - # GIVEN: A SystemPlayer instance and a mocked display |
2691 | - player = SystemPlayer(self) |
2692 | - player.has_own_widget = True |
2693 | - mocked_display = MagicMock() |
2694 | - |
2695 | - # WHEN: set_visible() is called |
2696 | - player.set_visible(mocked_display, True) |
2697 | - |
2698 | - # THEN: The widget should be visible |
2699 | - mocked_display.video_widget.setVisible.assert_called_once_with(True) |
2700 | - |
2701 | - def test_set_duration(self): |
2702 | - """ |
2703 | - Test the set_duration() method of the SystemPlayer |
2704 | - """ |
2705 | - # GIVEN: a mocked controller |
2706 | - mocked_controller = MagicMock() |
2707 | - mocked_controller.media_info.length = 5 |
2708 | - |
2709 | - # WHEN: The set_duration() is called. NB: the 10 here is ignored by the code |
2710 | - SystemPlayer.set_duration(mocked_controller, 10) |
2711 | - |
2712 | - # THEN: The maximum length of the slider should be set |
2713 | - mocked_controller.seek_slider.setMaximum.assert_called_once_with(5) |
2714 | - |
2715 | - def test_update_ui(self): |
2716 | - """ |
2717 | - Test the update_ui() method on the SystemPlayer |
2718 | - """ |
2719 | - # GIVEN: A SystemPlayer instance |
2720 | - player = SystemPlayer(self) |
2721 | - player.state = [MediaState.Playing, MediaState.Playing] |
2722 | - mocked_display = MagicMock() |
2723 | - mocked_display.media_player.state.return_value = QtMultimedia.QMediaPlayer.PausedState |
2724 | - mocked_display.controller.media_info.end_time = 1 |
2725 | - mocked_display.media_player.position.return_value = 2 |
2726 | - mocked_display.controller.seek_slider.isSliderDown.return_value = False |
2727 | - |
2728 | - # WHEN: update_ui() is called |
2729 | - with patch.object(player, 'stop') as mocked_stop, \ |
2730 | - patch.object(player, 'set_visible') as mocked_set_visible: |
2731 | - player.update_ui(mocked_display) |
2732 | - |
2733 | - # THEN: The UI is updated |
2734 | - expected_stop_calls = [call(mocked_display)] |
2735 | - expected_position_calls = [call(), call()] |
2736 | - expected_block_signals_calls = [call(True), call(False)] |
2737 | - mocked_display.media_player.state.assert_called_once_with() |
2738 | - assert 1 == mocked_stop.call_count |
2739 | - assert expected_stop_calls == mocked_stop.call_args_list |
2740 | - assert 2 == mocked_display.media_player.position.call_count |
2741 | - assert expected_position_calls == mocked_display.media_player.position.call_args_list |
2742 | - mocked_set_visible.assert_called_once_with(mocked_display, False) |
2743 | - mocked_display.controller.seek_slider.isSliderDown.assert_called_once_with() |
2744 | - assert expected_block_signals_calls == mocked_display.controller.seek_slider.blockSignals.call_args_list |
2745 | - mocked_display.controller.seek_slider.setSliderPosition.assert_called_once_with(2) |
2746 | - |
2747 | - def test_get_media_display_css(self): |
2748 | - """ |
2749 | - Test the get_media_display_css() method of the SystemPlayer |
2750 | - """ |
2751 | - # GIVEN: A SystemPlayer instance |
2752 | - player = SystemPlayer(self) |
2753 | - |
2754 | - # WHEN: get_media_display_css() is called |
2755 | - result = player.get_media_display_css() |
2756 | - |
2757 | - # THEN: The css should be empty |
2758 | - assert '' == result |
2759 | - |
2760 | - @patch('openlp.core.ui.media.systemplayer.QtMultimedia.QMediaPlayer') |
2761 | - def test_get_info(self, MockQMediaPlayer): |
2762 | - """ |
2763 | - Test the get_info() method of the SystemPlayer |
2764 | - """ |
2765 | - # GIVEN: A SystemPlayer instance |
2766 | - mocked_media_player = MagicMock() |
2767 | - mocked_media_player.supportedMimeTypes.return_value = [] |
2768 | - MockQMediaPlayer.return_value = mocked_media_player |
2769 | - player = SystemPlayer(self) |
2770 | - |
2771 | - # WHEN: get_info() is called |
2772 | - result = player.get_info() |
2773 | - |
2774 | - # THEN: The info should be correct |
2775 | - expected_info = 'This media player uses your operating system to provide media capabilities.<br/> ' \ |
2776 | - '<strong>Audio</strong><br/>[]<br/><strong>Video</strong><br/>[]<br/>' |
2777 | - assert expected_info == result |
2778 | - |
2779 | - @patch('openlp.core.ui.media.systemplayer.CheckMediaWorker') |
2780 | - @patch('openlp.core.ui.media.systemplayer.QtCore.QThread') |
2781 | - def test_check_media(self, MockQThread, MockCheckMediaWorker): |
2782 | - """ |
2783 | - Test the check_media() method of the SystemPlayer |
2784 | - """ |
2785 | - # GIVEN: A SystemPlayer instance and a mocked thread |
2786 | - valid_file = '/path/to/video.ogv' |
2787 | - mocked_application = MagicMock() |
2788 | - Registry().create() |
2789 | - Registry().register('application', mocked_application) |
2790 | - player = SystemPlayer(self) |
2791 | - mocked_thread = MagicMock() |
2792 | - mocked_thread.isRunning.side_effect = [True, False] |
2793 | - mocked_thread.quit = 'quit' # actually supposed to be a slot, but it's all mocked out anyway |
2794 | - MockQThread.return_value = mocked_thread |
2795 | - mocked_check_media_worker = MagicMock() |
2796 | - mocked_check_media_worker.play = 'play' |
2797 | - mocked_check_media_worker.result = True |
2798 | - MockCheckMediaWorker.return_value = mocked_check_media_worker |
2799 | - |
2800 | - # WHEN: check_media() is called with a valid media file |
2801 | - result = player.check_media(valid_file) |
2802 | - |
2803 | - # THEN: It should return True |
2804 | - MockQThread.assert_called_once_with() |
2805 | - MockCheckMediaWorker.assert_called_once_with(valid_file) |
2806 | - mocked_check_media_worker.setVolume.assert_called_once_with(0) |
2807 | - mocked_check_media_worker.moveToThread.assert_called_once_with(mocked_thread) |
2808 | - mocked_check_media_worker.finished.connect.assert_called_once_with('quit') |
2809 | - mocked_thread.started.connect.assert_called_once_with('play') |
2810 | - mocked_thread.start.assert_called_once_with() |
2811 | - assert 2 == mocked_thread.isRunning.call_count |
2812 | - mocked_application.processEvents.assert_called_once_with() |
2813 | - assert result is True |
2814 | - |
2815 | - |
2816 | -class TestCheckMediaWorker(TestCase): |
2817 | - """ |
2818 | - Test the CheckMediaWorker class |
2819 | - """ |
2820 | - def test_constructor(self): |
2821 | - """ |
2822 | - Test the constructor of the CheckMediaWorker class |
2823 | - """ |
2824 | - # GIVEN: A file path |
2825 | - path = 'file.ogv' |
2826 | - |
2827 | - # WHEN: The CheckMediaWorker object is instantiated |
2828 | - worker = CheckMediaWorker(path) |
2829 | - |
2830 | - # THEN: The correct values should be set up |
2831 | - assert worker is not None |
2832 | - |
2833 | - def test_signals_media(self): |
2834 | - """ |
2835 | - Test the signals() signal of the CheckMediaWorker class with a "media" origin |
2836 | - """ |
2837 | - # GIVEN: A CheckMediaWorker instance |
2838 | - worker = CheckMediaWorker('file.ogv') |
2839 | - |
2840 | - # WHEN: signals() is called with media and BufferedMedia |
2841 | - with patch.object(worker, 'stop') as mocked_stop, \ |
2842 | - patch.object(worker, 'finished') as mocked_finished: |
2843 | - worker.signals('media', worker.BufferedMedia) |
2844 | - |
2845 | - # THEN: The worker should exit and the result should be True |
2846 | - mocked_stop.assert_called_once_with() |
2847 | - mocked_finished.emit.assert_called_once_with() |
2848 | - assert worker.result is True |
2849 | - |
2850 | - def test_signals_error(self): |
2851 | - """ |
2852 | - Test the signals() signal of the CheckMediaWorker class with a "error" origin |
2853 | - """ |
2854 | - # GIVEN: A CheckMediaWorker instance |
2855 | - worker = CheckMediaWorker('file.ogv') |
2856 | - |
2857 | - # WHEN: signals() is called with error and BufferedMedia |
2858 | - with patch.object(worker, 'stop') as mocked_stop, \ |
2859 | - patch.object(worker, 'finished') as mocked_finished: |
2860 | - worker.signals('error', None) |
2861 | - |
2862 | - # THEN: The worker should exit and the result should be True |
2863 | - mocked_stop.assert_called_once_with() |
2864 | - mocked_finished.emit.assert_called_once_with() |
2865 | - assert worker.result is False |
2866 | |
2867 | === modified file 'tests/functional/openlp_core/ui/test_firsttimeform.py' |
2868 | --- tests/functional/openlp_core/ui/test_firsttimeform.py 2017-12-28 08:22:55 +0000 |
2869 | +++ tests/functional/openlp_core/ui/test_firsttimeform.py 2018-01-07 18:07:40 +0000 |
2870 | @@ -92,7 +92,6 @@ |
2871 | assert frw.web_access is True, 'The default value of self.web_access should be True' |
2872 | assert frw.was_cancelled is False, 'The default value of self.was_cancelled should be False' |
2873 | assert [] == frw.theme_screenshot_threads, 'The list of threads should be empty' |
2874 | - assert [] == frw.theme_screenshot_workers, 'The list of workers should be empty' |
2875 | assert frw.has_run_wizard is False, 'has_run_wizard should be False' |
2876 | |
2877 | def test_set_defaults(self): |
2878 | @@ -155,32 +154,33 @@ |
2879 | mocked_display_combo_box.count.assert_called_with() |
2880 | mocked_display_combo_box.setCurrentIndex.assert_called_with(1) |
2881 | |
2882 | - def test_on_cancel_button_clicked(self): |
2883 | + @patch('openlp.core.ui.firsttimeform.time') |
2884 | + @patch('openlp.core.ui.firsttimeform.get_thread_worker') |
2885 | + @patch('openlp.core.ui.firsttimeform.is_thread_finished') |
2886 | + def test_on_cancel_button_clicked(self, mocked_is_thread_finished, mocked_get_thread_worker, mocked_time): |
2887 | """ |
2888 | Test that the cancel button click slot shuts down the threads correctly |
2889 | """ |
2890 | # GIVEN: A FRW, some mocked threads and workers (that isn't quite done) and other mocked stuff |
2891 | - frw = FirstTimeForm(None) |
2892 | - frw.initialize(MagicMock()) |
2893 | mocked_worker = MagicMock() |
2894 | - mocked_thread = MagicMock() |
2895 | - mocked_thread.isRunning.side_effect = [True, False] |
2896 | - frw.theme_screenshot_workers.append(mocked_worker) |
2897 | - frw.theme_screenshot_threads.append(mocked_thread) |
2898 | - with patch('openlp.core.ui.firsttimeform.time') as mocked_time, \ |
2899 | - patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor: |
2900 | + mocked_get_thread_worker.return_value = mocked_worker |
2901 | + mocked_is_thread_finished.side_effect = [False, True] |
2902 | + frw = FirstTimeForm(None) |
2903 | + frw.initialize(MagicMock()) |
2904 | + frw.theme_screenshot_threads = ['test_thread'] |
2905 | + with patch.object(frw.application, 'set_normal_cursor') as mocked_set_normal_cursor: |
2906 | |
2907 | # WHEN: on_cancel_button_clicked() is called |
2908 | frw.on_cancel_button_clicked() |
2909 | |
2910 | # THEN: The right things should be called in the right order |
2911 | assert frw.was_cancelled is True, 'The was_cancelled property should have been set to True' |
2912 | + mocked_get_thread_worker.assert_called_once_with('test_thread') |
2913 | mocked_worker.set_download_canceled.assert_called_with(True) |
2914 | - mocked_thread.isRunning.assert_called_with() |
2915 | - assert 2 == mocked_thread.isRunning.call_count, 'isRunning() should have been called twice' |
2916 | - mocked_time.sleep.assert_called_with(0.1) |
2917 | - assert 1 == mocked_time.sleep.call_count, 'sleep() should have only been called once' |
2918 | - mocked_set_normal_cursor.assert_called_with() |
2919 | + mocked_is_thread_finished.assert_called_with('test_thread') |
2920 | + assert mocked_is_thread_finished.call_count == 2, 'isRunning() should have been called twice' |
2921 | + mocked_time.sleep.assert_called_once_with(0.1) |
2922 | + mocked_set_normal_cursor.assert_called_once_with() |
2923 | |
2924 | def test_broken_config(self): |
2925 | """ |
2926 | |
2927 | === modified file 'tests/functional/openlp_core/ui/test_mainwindow.py' |
2928 | --- tests/functional/openlp_core/ui/test_mainwindow.py 2017-12-29 09:15:48 +0000 |
2929 | +++ tests/functional/openlp_core/ui/test_mainwindow.py 2018-01-07 18:07:40 +0000 |
2930 | @@ -60,9 +60,10 @@ |
2931 | # Mock cursor busy/normal methods. |
2932 | self.app.set_busy_cursor = MagicMock() |
2933 | self.app.set_normal_cursor = MagicMock() |
2934 | + self.app.process_events = MagicMock() |
2935 | self.app.args = [] |
2936 | Registry().register('application', self.app) |
2937 | - Registry().set_flag('no_web_server', False) |
2938 | + Registry().set_flag('no_web_server', True) |
2939 | self.add_toolbar_action_patcher = patch('openlp.core.ui.mainwindow.create_action') |
2940 | self.mocked_add_toolbar_action = self.add_toolbar_action_patcher.start() |
2941 | self.mocked_add_toolbar_action.side_effect = self._create_mock_action |
2942 | @@ -74,8 +75,8 @@ |
2943 | """ |
2944 | Delete all the C++ objects and stop all the patchers |
2945 | """ |
2946 | + del self.main_window |
2947 | self.add_toolbar_action_patcher.stop() |
2948 | - del self.main_window |
2949 | |
2950 | def test_cmd_line_file(self): |
2951 | """ |
2952 | @@ -92,20 +93,20 @@ |
2953 | # THEN the service from the arguments is loaded |
2954 | mocked_load_file.assert_called_with(service) |
2955 | |
2956 | - def test_cmd_line_arg(self): |
2957 | + @patch('openlp.core.ui.servicemanager.ServiceManager.load_file') |
2958 | + def test_cmd_line_arg(self, mocked_load_file): |
2959 | """ |
2960 | Test that passing a non service file does nothing. |
2961 | """ |
2962 | # GIVEN a non service file as an argument to openlp |
2963 | service = os.path.join('openlp.py') |
2964 | self.main_window.arguments = [service] |
2965 | - with patch('openlp.core.ui.servicemanager.ServiceManager.load_file') as mocked_load_file: |
2966 | - |
2967 | - # WHEN the argument is processed |
2968 | - self.main_window.open_cmd_line_files("") |
2969 | - |
2970 | - # THEN the file should not be opened |
2971 | - assert mocked_load_file.called is False, 'load_file should not have been called' |
2972 | + |
2973 | + # WHEN the argument is processed |
2974 | + self.main_window.open_cmd_line_files(service) |
2975 | + |
2976 | + # THEN the file should not be opened |
2977 | + assert mocked_load_file.called is False, 'load_file should not have been called' |
2978 | |
2979 | def test_main_window_title(self): |
2980 | """ |
2981 | |
2982 | === modified file 'tests/functional/openlp_plugins/songs/test_songselect.py' |
2983 | --- tests/functional/openlp_plugins/songs/test_songselect.py 2017-12-29 10:19:33 +0000 |
2984 | +++ tests/functional/openlp_plugins/songs/test_songselect.py 2018-01-07 18:07:40 +0000 |
2985 | @@ -765,9 +765,9 @@ |
2986 | assert ssform.search_combobox.isEnabled() is True |
2987 | |
2988 | @patch('openlp.plugins.songs.forms.songselectform.Settings') |
2989 | - @patch('openlp.plugins.songs.forms.songselectform.QtCore.QThread') |
2990 | + @patch('openlp.plugins.songs.forms.songselectform.run_thread') |
2991 | @patch('openlp.plugins.songs.forms.songselectform.SearchWorker') |
2992 | - def test_on_search_button_clicked(self, MockedSearchWorker, MockedQtThread, MockedSettings): |
2993 | + def test_on_search_button_clicked(self, MockedSearchWorker, mocked_run_thread, MockedSettings): |
2994 | """ |
2995 | Test that search fields are disabled when search button is clicked. |
2996 | """ |
2997 | |
2998 | === modified file 'tests/interfaces/openlp_core/ui/test_mainwindow.py' |
2999 | --- tests/interfaces/openlp_core/ui/test_mainwindow.py 2017-12-29 09:15:48 +0000 |
3000 | +++ tests/interfaces/openlp_core/ui/test_mainwindow.py 2018-01-07 18:07:40 +0000 |
3001 | @@ -44,21 +44,21 @@ |
3002 | self.app.set_normal_cursor = MagicMock() |
3003 | self.app.args = [] |
3004 | Registry().register('application', self.app) |
3005 | - Registry().set_flag('no_web_server', False) |
3006 | + Registry().set_flag('no_web_server', True) |
3007 | # Mock classes and methods used by mainwindow. |
3008 | - with patch('openlp.core.ui.mainwindow.SettingsForm') as mocked_settings_form, \ |
3009 | - patch('openlp.core.ui.mainwindow.ImageManager') as mocked_image_manager, \ |
3010 | - patch('openlp.core.ui.mainwindow.LiveController') as mocked_live_controller, \ |
3011 | - patch('openlp.core.ui.mainwindow.PreviewController') as mocked_preview_controller, \ |
3012 | - patch('openlp.core.ui.mainwindow.OpenLPDockWidget') as mocked_dock_widget, \ |
3013 | - patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox') as mocked_q_tool_box_class, \ |
3014 | - patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget') as mocked_add_dock_method, \ |
3015 | - patch('openlp.core.ui.mainwindow.ServiceManager') as mocked_service_manager, \ |
3016 | - patch('openlp.core.ui.mainwindow.ThemeManager') as mocked_theme_manager, \ |
3017 | - patch('openlp.core.ui.mainwindow.ProjectorManager') as mocked_projector_manager, \ |
3018 | - patch('openlp.core.ui.mainwindow.Renderer') as mocked_renderer, \ |
3019 | - patch('openlp.core.ui.mainwindow.websockets.WebSocketServer') as mocked_websocketserver, \ |
3020 | - patch('openlp.core.ui.mainwindow.server.HttpServer') as mocked_httpserver: |
3021 | + with patch('openlp.core.ui.mainwindow.SettingsForm'), \ |
3022 | + patch('openlp.core.ui.mainwindow.ImageManager'), \ |
3023 | + patch('openlp.core.ui.mainwindow.LiveController'), \ |
3024 | + patch('openlp.core.ui.mainwindow.PreviewController'), \ |
3025 | + patch('openlp.core.ui.mainwindow.OpenLPDockWidget'), \ |
3026 | + patch('openlp.core.ui.mainwindow.QtWidgets.QToolBox'), \ |
3027 | + patch('openlp.core.ui.mainwindow.QtWidgets.QMainWindow.addDockWidget'), \ |
3028 | + patch('openlp.core.ui.mainwindow.ServiceManager'), \ |
3029 | + patch('openlp.core.ui.mainwindow.ThemeManager'), \ |
3030 | + patch('openlp.core.ui.mainwindow.ProjectorManager'), \ |
3031 | + patch('openlp.core.ui.mainwindow.Renderer'), \ |
3032 | + patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \ |
3033 | + patch('openlp.core.ui.mainwindow.server.HttpServer'): |
3034 | self.main_window = MainWindow() |
3035 | |
3036 | def tearDown(self): |
Very nice.
3 Minor questions.