Merge lp:~raoul-snyman/openlp/bug-1742910 into lp:openlp
- bug-1742910
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Merged at revision: | 2808 | ||||
Proposed branch: | lp:~raoul-snyman/openlp/bug-1742910 | ||||
Merge into: | lp:openlp | ||||
Diff against target: |
755 lines (+333/-194) 10 files modified
openlp/core/app.py (+1/-1) openlp/core/common/path.py (+2/-2) openlp/core/threading.py (+14/-10) openlp/core/ui/mainwindow.py (+3/-4) tests/functional/openlp_core/api/http/test_error.py (+37/-35) tests/functional/openlp_core/api/test_deploy.py (+94/-15) tests/functional/openlp_core/common/test_path.py (+14/-1) tests/functional/openlp_core/lib/test_path.py (+0/-87) tests/functional/openlp_core/lib/test_ui.py (+55/-28) tests/functional/openlp_core/test_threading.py (+113/-11) |
||||
To merge this branch: | bzr merge lp:~raoul-snyman/openlp/bug-1742910 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Bentley | Approve | ||
Review via email: mp+336066@code.launchpad.net |
This proposal supersedes a proposal from 2018-01-13.
Commit message
Description of the change
Fix bug #1742910 by moving the threads to the application object instead of the main window object.
Add this to your merge proposal:
-------
lp:~raoul-snyman/openlp/bug-1742910 (revision 2810)
https:/
https:/
https:/
https:/
https:/
https:/
https:/
https:/
Stopping after failure
Failed builds:
- Branch-
Tim Bentley (trb143) : | # |
Preview Diff
1 | === modified file 'openlp/core/app.py' | |||
2 | --- openlp/core/app.py 2018-01-07 04:40:40 +0000 | |||
3 | +++ openlp/core/app.py 2018-01-13 06:00:49 +0000 | |||
4 | @@ -63,8 +63,8 @@ | |||
5 | 63 | The core application class. This class inherits from Qt's QApplication | 63 | The core application class. This class inherits from Qt's QApplication |
6 | 64 | class in order to provide the core of the application. | 64 | class in order to provide the core of the application. |
7 | 65 | """ | 65 | """ |
8 | 66 | |||
9 | 67 | args = [] | 66 | args = [] |
10 | 67 | worker_threads = {} | ||
11 | 68 | 68 | ||
12 | 69 | def exec(self): | 69 | def exec(self): |
13 | 70 | """ | 70 | """ |
14 | 71 | 71 | ||
15 | === modified file 'openlp/core/common/path.py' | |||
16 | --- openlp/core/common/path.py 2017-12-29 09:15:48 +0000 | |||
17 | +++ openlp/core/common/path.py 2018-01-13 06:00:49 +0000 | |||
18 | @@ -26,9 +26,9 @@ | |||
19 | 26 | from openlp.core.common import is_win | 26 | from openlp.core.common import is_win |
20 | 27 | 27 | ||
21 | 28 | if is_win(): | 28 | if is_win(): |
23 | 29 | from pathlib import WindowsPath as PathVariant | 29 | from pathlib import WindowsPath as PathVariant # pragma: nocover |
24 | 30 | else: | 30 | else: |
26 | 31 | from pathlib import PosixPath as PathVariant | 31 | from pathlib import PosixPath as PathVariant # pragma: nocover |
27 | 32 | 32 | ||
28 | 33 | log = logging.getLogger(__name__) | 33 | log = logging.getLogger(__name__) |
29 | 34 | 34 | ||
30 | 35 | 35 | ||
31 | === modified file 'openlp/core/threading.py' | |||
32 | --- openlp/core/threading.py 2018-01-07 17:50:29 +0000 | |||
33 | +++ openlp/core/threading.py 2018-01-13 06:00:49 +0000 | |||
34 | @@ -50,12 +50,12 @@ | |||
35 | 50 | """ | 50 | """ |
36 | 51 | if not thread_name: | 51 | if not thread_name: |
37 | 52 | raise ValueError('A thread_name is required when calling the "run_thread" function') | 52 | raise ValueError('A thread_name is required when calling the "run_thread" function') |
40 | 53 | main_window = Registry().get('main_window') | 53 | application = Registry().get('application') |
41 | 54 | if thread_name in main_window.threads: | 54 | if thread_name in application.worker_threads: |
42 | 55 | raise KeyError('A thread with the name "{}" has already been created, please use another'.format(thread_name)) | 55 | raise KeyError('A thread with the name "{}" has already been created, please use another'.format(thread_name)) |
43 | 56 | # Create the thread and add the thread and the worker to the parent | 56 | # Create the thread and add the thread and the worker to the parent |
44 | 57 | thread = QtCore.QThread() | 57 | thread = QtCore.QThread() |
46 | 58 | main_window.threads[thread_name] = { | 58 | application.worker_threads[thread_name] = { |
47 | 59 | 'thread': thread, | 59 | 'thread': thread, |
48 | 60 | 'worker': worker | 60 | 'worker': worker |
49 | 61 | } | 61 | } |
50 | @@ -78,7 +78,10 @@ | |||
51 | 78 | :param str thread_name: The name of the thread | 78 | :param str thread_name: The name of the thread |
52 | 79 | :returns: The worker for this thread name | 79 | :returns: The worker for this thread name |
53 | 80 | """ | 80 | """ |
55 | 81 | return Registry().get('main_window').threads.get(thread_name) | 81 | thread_info = Registry().get('application').worker_threads.get(thread_name) |
56 | 82 | if not thread_info: | ||
57 | 83 | raise KeyError('No thread named "{}" exists'.format(thread_name)) | ||
58 | 84 | return thread_info.get('worker') | ||
59 | 82 | 85 | ||
60 | 83 | 86 | ||
61 | 84 | def is_thread_finished(thread_name): | 87 | def is_thread_finished(thread_name): |
62 | @@ -88,8 +91,8 @@ | |||
63 | 88 | :param str thread_name: The name of the thread | 91 | :param str thread_name: The name of the thread |
64 | 89 | :returns: True if the thread is finished, False if it is still running | 92 | :returns: True if the thread is finished, False if it is still running |
65 | 90 | """ | 93 | """ |
68 | 91 | main_window = Registry().get('main_window') | 94 | app = Registry().get('application') |
69 | 92 | return thread_name not in main_window.threads or main_window.threads[thread_name]['thread'].isFinished() | 95 | return thread_name not in app.worker_threads or app.worker_threads[thread_name]['thread'].isFinished() |
70 | 93 | 96 | ||
71 | 94 | 97 | ||
72 | 95 | def make_remove_thread(thread_name): | 98 | def make_remove_thread(thread_name): |
73 | @@ -99,13 +102,14 @@ | |||
74 | 99 | :param str thread_name: The name of the thread which should be removed from the thread registry. | 102 | :param str thread_name: The name of the thread which should be removed from the thread registry. |
75 | 100 | :returns: A function which will remove the thread from the thread registry. | 103 | :returns: A function which will remove the thread from the thread registry. |
76 | 101 | """ | 104 | """ |
78 | 102 | def remove_thread(): | 105 | |
79 | 106 | def remove_thread(): # pragma: nocover | ||
80 | 103 | """ | 107 | """ |
81 | 104 | Stop and remove a registered thread | 108 | Stop and remove a registered thread |
82 | 105 | 109 | ||
83 | 106 | :param str thread_name: The name of the thread to stop and remove | 110 | :param str thread_name: The name of the thread to stop and remove |
84 | 107 | """ | 111 | """ |
88 | 108 | main_window = Registry().get('main_window') | 112 | application = Registry().get('application') |
89 | 109 | if thread_name in main_window.threads: | 113 | if thread_name in application.worker_threads: |
90 | 110 | del main_window.threads[thread_name] | 114 | del application.worker_threads[thread_name] |
91 | 111 | return remove_thread | 115 | return remove_thread |
92 | 112 | 116 | ||
93 | === modified file 'openlp/core/ui/mainwindow.py' | |||
94 | --- openlp/core/ui/mainwindow.py 2018-01-07 17:50:29 +0000 | |||
95 | +++ openlp/core/ui/mainwindow.py 2018-01-13 06:00:49 +0000 | |||
96 | @@ -477,7 +477,6 @@ | |||
97 | 477 | """ | 477 | """ |
98 | 478 | super(MainWindow, self).__init__() | 478 | super(MainWindow, self).__init__() |
99 | 479 | Registry().register('main_window', self) | 479 | Registry().register('main_window', self) |
100 | 480 | self.threads = {} | ||
101 | 481 | self.clipboard = self.application.clipboard() | 480 | self.clipboard = self.application.clipboard() |
102 | 482 | self.arguments = ''.join(self.application.args) | 481 | self.arguments = ''.join(self.application.args) |
103 | 483 | # Set up settings sections for the main application (not for use by plugins). | 482 | # Set up settings sections for the main application (not for use by plugins). |
104 | @@ -557,11 +556,11 @@ | |||
105 | 557 | wait_dialog.setAutoClose(False) | 556 | wait_dialog.setAutoClose(False) |
106 | 558 | wait_dialog.setCancelButton(None) | 557 | wait_dialog.setCancelButton(None) |
107 | 559 | wait_dialog.show() | 558 | wait_dialog.show() |
109 | 560 | for thread_name in self.threads.keys(): | 559 | for thread_name in self.application.worker_threads.keys(): |
110 | 561 | log.debug('Waiting for thread %s', thread_name) | 560 | log.debug('Waiting for thread %s', thread_name) |
111 | 562 | self.application.processEvents() | 561 | self.application.processEvents() |
114 | 563 | thread = self.threads[thread_name]['thread'] | 562 | thread = self.application.worker_threads[thread_name]['thread'] |
115 | 564 | worker = self.threads[thread_name]['worker'] | 563 | worker = self.application.worker_threads[thread_name]['worker'] |
116 | 565 | try: | 564 | try: |
117 | 566 | if worker and hasattr(worker, 'stop'): | 565 | if worker and hasattr(worker, 'stop'): |
118 | 567 | # If the worker has a stop method, run it | 566 | # If the worker has a stop method, run it |
119 | 568 | 567 | ||
120 | === modified file 'tests/functional/openlp_core/api/http/test_error.py' | |||
121 | --- tests/functional/openlp_core/api/http/test_error.py 2017-12-29 09:15:48 +0000 | |||
122 | +++ tests/functional/openlp_core/api/http/test_error.py 2018-01-13 06:00:49 +0000 | |||
123 | @@ -22,38 +22,40 @@ | |||
124 | 22 | """ | 22 | """ |
125 | 23 | Functional tests to test the API Error Class. | 23 | Functional tests to test the API Error Class. |
126 | 24 | """ | 24 | """ |
162 | 25 | 25 | from openlp.core.api.http.errors import HttpError, NotFound, ServerError | |
163 | 26 | from unittest import TestCase | 26 | |
164 | 27 | 27 | ||
165 | 28 | from openlp.core.api.http.errors import NotFound, ServerError | 28 | def test_http_error(): |
166 | 29 | 29 | """ | |
167 | 30 | 30 | Test the HTTPError class | |
168 | 31 | class TestApiError(TestCase): | 31 | """ |
169 | 32 | """ | 32 | # GIVEN: An HTTPError class |
170 | 33 | A test suite to test out the Error in the API code | 33 | # WHEN: An instance is created |
171 | 34 | """ | 34 | error = HttpError(400, 'Access Denied') |
172 | 35 | def test_not_found(self): | 35 | |
173 | 36 | """ | 36 | # THEN: The to_response() method should return the correct information |
174 | 37 | Test the Not Found error displays the correct information | 37 | assert error.to_response() == ('Access Denied', 400), 'to_response() should have returned the correct info' |
175 | 38 | """ | 38 | |
176 | 39 | # GIVEN: | 39 | |
177 | 40 | # WHEN: I raise an exception | 40 | def test_not_found(): |
178 | 41 | with self.assertRaises(Exception) as context: | 41 | """ |
179 | 42 | raise NotFound() | 42 | Test the Not Found error displays the correct information |
180 | 43 | 43 | """ | |
181 | 44 | # THEN: we get an error and a status | 44 | # GIVEN: A NotFound class |
182 | 45 | assert 'Not Found' == context.exception.message, 'A Not Found exception should be thrown' | 45 | # WHEN: An instance is created |
183 | 46 | assert 404 == context.exception.status, 'A 404 status should be thrown' | 46 | error = NotFound() |
184 | 47 | 47 | ||
185 | 48 | def test_server_error(self): | 48 | # THEN: The to_response() method should return the correct information |
186 | 49 | """ | 49 | assert error.to_response() == ('Not Found', 404), 'to_response() should have returned the correct info' |
187 | 50 | Test the server error displays the correct information | 50 | |
188 | 51 | """ | 51 | |
189 | 52 | # GIVEN: | 52 | def test_server_error(): |
190 | 53 | # WHEN: I raise an exception | 53 | """ |
191 | 54 | with self.assertRaises(Exception) as context: | 54 | Test the server error displays the correct information |
192 | 55 | raise ServerError() | 55 | """ |
193 | 56 | 56 | # GIVEN: A ServerError class | |
194 | 57 | # THEN: we get an error and a status | 57 | # WHEN: An instance of the class is created |
195 | 58 | assert'Server Error' == context.exception.message, 'A Not Found exception should be thrown' | 58 | error = ServerError() |
196 | 59 | assert 500 == context.exception.status, 'A 500 status should be thrown' | 59 | |
197 | 60 | # THEN: The to_response() method should return the correct information | ||
198 | 61 | assert error.to_response() == ('Server Error', 500), 'to_response() should have returned the correct info' | ||
199 | 60 | 62 | ||
200 | === modified file 'tests/functional/openlp_core/api/test_deploy.py' | |||
201 | --- tests/functional/openlp_core/api/test_deploy.py 2017-12-29 09:15:48 +0000 | |||
202 | +++ tests/functional/openlp_core/api/test_deploy.py 2018-01-13 06:00:49 +0000 | |||
203 | @@ -21,11 +21,12 @@ | |||
204 | 21 | ############################################################################### | 21 | ############################################################################### |
205 | 22 | from tempfile import mkdtemp | 22 | from tempfile import mkdtemp |
206 | 23 | from unittest import TestCase | 23 | from unittest import TestCase |
212 | 24 | 24 | from unittest.mock import MagicMock, patch | |
213 | 25 | from openlp.core.api.deploy import deploy_zipfile | 25 | |
214 | 26 | from openlp.core.common.path import Path, copyfile | 26 | from openlp.core.api.deploy import deploy_zipfile, download_sha256, download_and_check |
215 | 27 | 27 | from openlp.core.common.path import Path | |
216 | 28 | TEST_PATH = (Path(__file__).parent / '..' / '..' / '..' / 'resources').resolve() | 28 | |
217 | 29 | CONFIG_FILE = '2c266badff1e3d140664c50fd1460a2b332b24d5ad8c267fa62e506b5eb6d894 deploy/site.zip\n2017_06_27' | ||
218 | 29 | 30 | ||
219 | 30 | 31 | ||
220 | 31 | class TestRemoteDeploy(TestCase): | 32 | class TestRemoteDeploy(TestCase): |
221 | @@ -45,17 +46,95 @@ | |||
222 | 45 | """ | 46 | """ |
223 | 46 | self.app_root_path.rmtree() | 47 | self.app_root_path.rmtree() |
224 | 47 | 48 | ||
226 | 48 | def test_deploy_zipfile(self): | 49 | @patch('openlp.core.api.deploy.ZipFile') |
227 | 50 | def test_deploy_zipfile(self, MockZipFile): | ||
228 | 49 | """ | 51 | """ |
229 | 50 | Remote Deploy tests - test the dummy zip file is processed correctly | 52 | Remote Deploy tests - test the dummy zip file is processed correctly |
230 | 51 | """ | 53 | """ |
231 | 52 | # GIVEN: A new downloaded zip file | 54 | # GIVEN: A new downloaded zip file |
241 | 53 | zip_path = TEST_PATH / 'remotes' / 'site.zip' | 55 | mocked_zipfile = MagicMock() |
242 | 54 | app_root_path = self.app_root_path / 'site.zip' | 56 | MockZipFile.return_value = mocked_zipfile |
243 | 55 | copyfile(zip_path, app_root_path) | 57 | root_path = Path('/tmp/remotes') |
244 | 56 | 58 | ||
245 | 57 | # WHEN: I process the zipfile | 59 | # WHEN: deploy_zipfile() is called |
246 | 58 | deploy_zipfile(self.app_root_path, 'site.zip') | 60 | deploy_zipfile(root_path, 'site.zip') |
247 | 59 | 61 | ||
248 | 60 | # THEN: test if www directory has been created | 62 | # THEN: the zip file should have been extracted to the right location |
249 | 61 | assert (self.app_root_path / 'www').is_dir(), 'We should have a www directory' | 63 | MockZipFile.assert_called_once_with('/tmp/remotes/site.zip') |
250 | 64 | mocked_zipfile.extractall.assert_called_once_with('/tmp/remotes') | ||
251 | 65 | |||
252 | 66 | @patch('openlp.core.api.deploy.Registry') | ||
253 | 67 | @patch('openlp.core.api.deploy.get_web_page') | ||
254 | 68 | def test_download_sha256_connection_error(self, mocked_get_web_page, MockRegistry): | ||
255 | 69 | """ | ||
256 | 70 | Test that if a ConnectionError occurs while downloading a sha256 False is returned | ||
257 | 71 | """ | ||
258 | 72 | # GIVEN: A bunch of mocks | ||
259 | 73 | MockRegistry.return_value.get.return_value.applicationVersion.return_value = '1.0' | ||
260 | 74 | mocked_get_web_page.side_effect = ConnectionError() | ||
261 | 75 | |||
262 | 76 | # WHEN: download_sha256() is called | ||
263 | 77 | result = download_sha256() | ||
264 | 78 | |||
265 | 79 | # THEN: The result should be False | ||
266 | 80 | assert result is False, 'download_sha256() should return False when encountering ConnectionError' | ||
267 | 81 | |||
268 | 82 | @patch('openlp.core.api.deploy.Registry') | ||
269 | 83 | @patch('openlp.core.api.deploy.get_web_page') | ||
270 | 84 | def test_download_sha256_no_config(self, mocked_get_web_page, MockRegistry): | ||
271 | 85 | """ | ||
272 | 86 | Test that if there's no config when downloading a sha256 None is returned | ||
273 | 87 | """ | ||
274 | 88 | # GIVEN: A bunch of mocks | ||
275 | 89 | MockRegistry.return_value.get.return_value.applicationVersion.return_value = '1.0' | ||
276 | 90 | mocked_get_web_page.return_value = None | ||
277 | 91 | |||
278 | 92 | # WHEN: download_sha256() is called | ||
279 | 93 | result = download_sha256() | ||
280 | 94 | |||
281 | 95 | # THEN: The result should be Nonw | ||
282 | 96 | assert result is None, 'download_sha256() should return None when there is a problem downloading the page' | ||
283 | 97 | |||
284 | 98 | @patch('openlp.core.api.deploy.Registry') | ||
285 | 99 | @patch('openlp.core.api.deploy.get_web_page') | ||
286 | 100 | def test_download_sha256(self, mocked_get_web_page, MockRegistry): | ||
287 | 101 | """ | ||
288 | 102 | Test that the sha256 and the version are returned | ||
289 | 103 | """ | ||
290 | 104 | # GIVEN: A bunch of mocks | ||
291 | 105 | MockRegistry.return_value.get.return_value.applicationVersion.return_value = '1.0' | ||
292 | 106 | mocked_get_web_page.return_value = CONFIG_FILE | ||
293 | 107 | |||
294 | 108 | # WHEN: download_sha256() is called | ||
295 | 109 | result = download_sha256() | ||
296 | 110 | |||
297 | 111 | # THEN: The result should be Nonw | ||
298 | 112 | assert result == ('2c266badff1e3d140664c50fd1460a2b332b24d5ad8c267fa62e506b5eb6d894', '2017_06_27'), \ | ||
299 | 113 | 'download_sha256() should return a tuple of sha256 and version' | ||
300 | 114 | |||
301 | 115 | @patch('openlp.core.api.deploy.Registry') | ||
302 | 116 | @patch('openlp.core.api.deploy.download_sha256') | ||
303 | 117 | @patch('openlp.core.api.deploy.get_url_file_size') | ||
304 | 118 | @patch('openlp.core.api.deploy.download_file') | ||
305 | 119 | @patch('openlp.core.api.deploy.AppLocation.get_section_data_path') | ||
306 | 120 | @patch('openlp.core.api.deploy.deploy_zipfile') | ||
307 | 121 | def test_download_and_check(self, mocked_deploy_zipfile, mocked_get_data_path, mocked_download_file, | ||
308 | 122 | mocked_get_url_file_size, mocked_download_sha256, MockRegistry): | ||
309 | 123 | # GIVEN: A bunch of mocks | ||
310 | 124 | mocked_get_data_path.return_value = Path('/tmp/remotes') | ||
311 | 125 | mocked_download_file.return_value = True | ||
312 | 126 | mocked_get_url_file_size.return_value = 5 | ||
313 | 127 | mocked_download_sha256.return_value = ('asdfgh', '0.1') | ||
314 | 128 | MockRegistry.return_value.get.return_value.applicationVersion.return_value = '1.0' | ||
315 | 129 | mocked_callback = MagicMock() | ||
316 | 130 | |||
317 | 131 | # WHEN: download_and_check() is called | ||
318 | 132 | download_and_check(mocked_callback) | ||
319 | 133 | |||
320 | 134 | # THEN: The correct things should have been done | ||
321 | 135 | mocked_download_sha256.assert_called_once_with() | ||
322 | 136 | mocked_get_url_file_size.assert_called_once_with('https://get.openlp.org/webclient/site.zip') | ||
323 | 137 | mocked_callback.setRange.assert_called_once_with(0, 5) | ||
324 | 138 | mocked_download_file.assert_called_once_with(mocked_callback, 'https://get.openlp.org/webclient/site.zip', | ||
325 | 139 | Path('/tmp/remotes/site.zip'), sha256='asdfgh') | ||
326 | 140 | mocked_deploy_zipfile.assert_called_once_with(Path('/tmp/remotes'), 'site.zip') | ||
327 | 62 | 141 | ||
328 | === modified file 'tests/functional/openlp_core/common/test_path.py' | |||
329 | --- tests/functional/openlp_core/common/test_path.py 2017-12-29 09:15:48 +0000 | |||
330 | +++ tests/functional/openlp_core/common/test_path.py 2018-01-13 06:00:49 +0000 | |||
331 | @@ -27,7 +27,7 @@ | |||
332 | 27 | from unittest.mock import ANY, MagicMock, patch | 27 | from unittest.mock import ANY, MagicMock, patch |
333 | 28 | 28 | ||
334 | 29 | from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, \ | 29 | from openlp.core.common.path import Path, copy, copyfile, copytree, create_paths, path_to_str, replace_params, \ |
336 | 30 | str_to_path, which | 30 | str_to_path, which, files_to_paths |
337 | 31 | 31 | ||
338 | 32 | 32 | ||
339 | 33 | class TestShutil(TestCase): | 33 | class TestShutil(TestCase): |
340 | @@ -401,3 +401,16 @@ | |||
341 | 401 | except: | 401 | except: |
342 | 402 | # THEN: `create_paths` raises an exception | 402 | # THEN: `create_paths` raises an exception |
343 | 403 | pass | 403 | pass |
344 | 404 | |||
345 | 405 | def test_files_to_paths(self): | ||
346 | 406 | """ | ||
347 | 407 | Test the files_to_paths() method | ||
348 | 408 | """ | ||
349 | 409 | # GIVEN: A list of string filenames | ||
350 | 410 | test_files = ['/tmp/openlp/file1.txt', '/tmp/openlp/file2.txt'] | ||
351 | 411 | |||
352 | 412 | # WHEN: files_to_paths is called | ||
353 | 413 | result = files_to_paths(test_files) | ||
354 | 414 | |||
355 | 415 | # THEN: The result should be a list of Paths | ||
356 | 416 | assert result == [Path('/tmp/openlp/file1.txt'), Path('/tmp/openlp/file2.txt')] | ||
357 | 404 | 417 | ||
358 | === removed file 'tests/functional/openlp_core/lib/test_path.py' | |||
359 | --- tests/functional/openlp_core/lib/test_path.py 2017-12-23 09:09:45 +0000 | |||
360 | +++ tests/functional/openlp_core/lib/test_path.py 1970-01-01 00:00:00 +0000 | |||
361 | @@ -1,87 +0,0 @@ | |||
362 | 1 | # -*- coding: utf-8 -*- | ||
363 | 2 | # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 | ||
364 | 3 | |||
365 | 4 | ############################################################################### | ||
366 | 5 | # OpenLP - Open Source Lyrics Projection # | ||
367 | 6 | # --------------------------------------------------------------------------- # | ||
368 | 7 | # Copyright (c) 2008-2017 OpenLP Developers # | ||
369 | 8 | # --------------------------------------------------------------------------- # | ||
370 | 9 | # This program is free software; you can redistribute it and/or modify it # | ||
371 | 10 | # under the terms of the GNU General Public License as published by the Free # | ||
372 | 11 | # Software Foundation; version 2 of the License. # | ||
373 | 12 | # # | ||
374 | 13 | # This program is distributed in the hope that it will be useful, but WITHOUT # | ||
375 | 14 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # | ||
376 | 15 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # | ||
377 | 16 | # more details. # | ||
378 | 17 | # # | ||
379 | 18 | # You should have received a copy of the GNU General Public License along # | ||
380 | 19 | # with this program; if not, write to the Free Software Foundation, Inc., 59 # | ||
381 | 20 | # Temple Place, Suite 330, Boston, MA 02111-1307 USA # | ||
382 | 21 | ############################################################################### | ||
383 | 22 | """ | ||
384 | 23 | Package to test the openlp.core.lib.path package. | ||
385 | 24 | """ | ||
386 | 25 | import os | ||
387 | 26 | from unittest import TestCase | ||
388 | 27 | |||
389 | 28 | from openlp.core.common.path import Path, path_to_str, str_to_path | ||
390 | 29 | |||
391 | 30 | |||
392 | 31 | class TestPath(TestCase): | ||
393 | 32 | """ | ||
394 | 33 | Tests for the :mod:`openlp.core.lib.path` module | ||
395 | 34 | """ | ||
396 | 35 | |||
397 | 36 | def test_path_to_str_type_error(self): | ||
398 | 37 | """ | ||
399 | 38 | Test that `path_to_str` raises a type error when called with an invalid type | ||
400 | 39 | """ | ||
401 | 40 | # GIVEN: The `path_to_str` function | ||
402 | 41 | # WHEN: Calling `path_to_str` with an invalid Type | ||
403 | 42 | # THEN: A TypeError should have been raised | ||
404 | 43 | with self.assertRaises(TypeError): | ||
405 | 44 | path_to_str(str()) | ||
406 | 45 | |||
407 | 46 | def test_path_to_str_none(self): | ||
408 | 47 | """ | ||
409 | 48 | Test that `path_to_str` correctly converts the path parameter when passed with None | ||
410 | 49 | """ | ||
411 | 50 | # GIVEN: The `path_to_str` function | ||
412 | 51 | # WHEN: Calling the `path_to_str` function with None | ||
413 | 52 | result = path_to_str(None) | ||
414 | 53 | |||
415 | 54 | # THEN: `path_to_str` should return an empty string | ||
416 | 55 | assert result == '' | ||
417 | 56 | |||
418 | 57 | def test_path_to_str_path_object(self): | ||
419 | 58 | """ | ||
420 | 59 | Test that `path_to_str` correctly converts the path parameter when passed a Path object | ||
421 | 60 | """ | ||
422 | 61 | # GIVEN: The `path_to_str` function | ||
423 | 62 | # WHEN: Calling the `path_to_str` function with a Path object | ||
424 | 63 | result = path_to_str(Path('test/path')) | ||
425 | 64 | |||
426 | 65 | # THEN: `path_to_str` should return a string representation of the Path object | ||
427 | 66 | assert result == os.path.join('test', 'path') | ||
428 | 67 | |||
429 | 68 | def test_str_to_path_type_error(self): | ||
430 | 69 | """ | ||
431 | 70 | Test that `str_to_path` raises a type error when called with an invalid type | ||
432 | 71 | """ | ||
433 | 72 | # GIVEN: The `str_to_path` function | ||
434 | 73 | # WHEN: Calling `str_to_path` with an invalid Type | ||
435 | 74 | # THEN: A TypeError should have been raised | ||
436 | 75 | with self.assertRaises(TypeError): | ||
437 | 76 | str_to_path(Path()) | ||
438 | 77 | |||
439 | 78 | def test_str_to_path_empty_str(self): | ||
440 | 79 | """ | ||
441 | 80 | Test that `str_to_path` correctly converts the string parameter when passed with and empty string | ||
442 | 81 | """ | ||
443 | 82 | # GIVEN: The `str_to_path` function | ||
444 | 83 | # WHEN: Calling the `str_to_path` function with None | ||
445 | 84 | result = str_to_path('') | ||
446 | 85 | |||
447 | 86 | # THEN: `path_to_str` should return None | ||
448 | 87 | assert result is None | ||
449 | 88 | 0 | ||
450 | === modified file 'tests/functional/openlp_core/lib/test_ui.py' | |||
451 | --- tests/functional/openlp_core/lib/test_ui.py 2017-12-17 20:19:19 +0000 | |||
452 | +++ tests/functional/openlp_core/lib/test_ui.py 2018-01-13 06:00:49 +0000 | |||
453 | @@ -23,14 +23,14 @@ | |||
454 | 23 | Package to test the openlp.core.lib.ui package. | 23 | Package to test the openlp.core.lib.ui package. |
455 | 24 | """ | 24 | """ |
456 | 25 | from unittest import TestCase | 25 | from unittest import TestCase |
458 | 26 | from unittest.mock import MagicMock, patch | 26 | from unittest.mock import MagicMock, patch, call |
459 | 27 | 27 | ||
460 | 28 | from PyQt5 import QtCore, QtGui, QtWidgets | 28 | from PyQt5 import QtCore, QtGui, QtWidgets |
461 | 29 | 29 | ||
462 | 30 | from openlp.core.common.i18n import UiStrings, translate | 30 | from openlp.core.common.i18n import UiStrings, translate |
463 | 31 | from openlp.core.lib.ui import add_welcome_page, create_button_box, create_horizontal_adjusting_combo_box, \ | 31 | from openlp.core.lib.ui import add_welcome_page, create_button_box, create_horizontal_adjusting_combo_box, \ |
464 | 32 | create_button, create_action, create_valign_selection_widgets, find_and_set_in_combo_box, create_widget_action, \ | 32 | create_button, create_action, create_valign_selection_widgets, find_and_set_in_combo_box, create_widget_action, \ |
466 | 33 | set_case_insensitive_completer | 33 | set_case_insensitive_completer, critical_error_message_box |
467 | 34 | 34 | ||
468 | 35 | 35 | ||
469 | 36 | class TestUi(TestCase): | 36 | class TestUi(TestCase): |
470 | @@ -80,6 +80,34 @@ | |||
471 | 80 | assert 1 == len(btnbox.buttons()) | 80 | assert 1 == len(btnbox.buttons()) |
472 | 81 | assert QtWidgets.QDialogButtonBox.HelpRole, btnbox.buttonRole(btnbox.buttons()[0]) | 81 | assert QtWidgets.QDialogButtonBox.HelpRole, btnbox.buttonRole(btnbox.buttons()[0]) |
473 | 82 | 82 | ||
474 | 83 | @patch('openlp.core.lib.ui.Registry') | ||
475 | 84 | def test_critical_error_message_box(self, MockRegistry): | ||
476 | 85 | """ | ||
477 | 86 | Test the critical_error_message_box() function | ||
478 | 87 | """ | ||
479 | 88 | # GIVEN: A mocked Registry | ||
480 | 89 | # WHEN: critical_error_message_box() is called | ||
481 | 90 | critical_error_message_box('Error', 'This is an error') | ||
482 | 91 | |||
483 | 92 | # THEN: The error_message() method on the main window should be called | ||
484 | 93 | MockRegistry.return_value.get.return_value.error_message.assert_called_once_with('Error', 'This is an error') | ||
485 | 94 | |||
486 | 95 | @patch('openlp.core.lib.ui.QtWidgets.QMessageBox.critical') | ||
487 | 96 | def test_critical_error_question(self, mocked_critical): | ||
488 | 97 | """ | ||
489 | 98 | Test the critical_error_message_box() function | ||
490 | 99 | """ | ||
491 | 100 | # GIVEN: A mocked critical() method and some other mocks | ||
492 | 101 | mocked_parent = MagicMock() | ||
493 | 102 | |||
494 | 103 | # WHEN: critical_error_message_box() is called | ||
495 | 104 | critical_error_message_box(None, 'This is a question', mocked_parent, True) | ||
496 | 105 | |||
497 | 106 | # THEN: The error_message() method on the main window should be called | ||
498 | 107 | mocked_critical.assert_called_once_with(mocked_parent, 'Error', 'This is a question', | ||
499 | 108 | QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes | | ||
500 | 109 | QtWidgets.QMessageBox.No)) | ||
501 | 110 | |||
502 | 83 | def test_create_horizontal_adjusting_combo_box(self): | 111 | def test_create_horizontal_adjusting_combo_box(self): |
503 | 84 | """ | 112 | """ |
504 | 85 | Test creating a horizontal adjusting combo box | 113 | Test creating a horizontal adjusting combo box |
505 | @@ -92,65 +120,64 @@ | |||
506 | 92 | 120 | ||
507 | 93 | # THEN: We should get a ComboBox | 121 | # THEN: We should get a ComboBox |
508 | 94 | assert isinstance(combo, QtWidgets.QComboBox) | 122 | assert isinstance(combo, QtWidgets.QComboBox) |
510 | 95 | assert 'combo1' == combo.objectName() | 123 | assert combo.objectName() == 'combo1' |
511 | 96 | assert QtWidgets.QComboBox.AdjustToMinimumContentsLength == combo.sizeAdjustPolicy() | 124 | assert QtWidgets.QComboBox.AdjustToMinimumContentsLength == combo.sizeAdjustPolicy() |
512 | 97 | 125 | ||
514 | 98 | def test_create_button(self): | 126 | @patch('openlp.core.lib.ui.log') |
515 | 127 | def test_create_button(self, mocked_log): | ||
516 | 99 | """ | 128 | """ |
517 | 100 | Test creating a button | 129 | Test creating a button |
518 | 101 | """ | 130 | """ |
519 | 102 | # GIVEN: A dialog | 131 | # GIVEN: A dialog |
520 | 103 | dialog = QtWidgets.QDialog() | 132 | dialog = QtWidgets.QDialog() |
521 | 104 | 133 | ||
522 | 105 | # WHEN: We create the button | ||
523 | 106 | btn = create_button(dialog, 'my_btn') | ||
524 | 107 | |||
525 | 108 | # THEN: We should get a button with a name | ||
526 | 109 | assert isinstance(btn, QtWidgets.QPushButton) | ||
527 | 110 | assert 'my_btn' == btn.objectName() | ||
528 | 111 | assert btn.isEnabled() is True | ||
529 | 112 | |||
530 | 113 | # WHEN: We create a button with some attributes | 134 | # WHEN: We create a button with some attributes |
532 | 114 | btn = create_button(dialog, 'my_btn', text='Hello', tooltip='How are you?', enabled=False) | 135 | btn = create_button(dialog, 'my_btn', text='Hello', tooltip='How are you?', enabled=False, role='test', test=1) |
533 | 115 | 136 | ||
534 | 116 | # THEN: We should get a button with those attributes | 137 | # THEN: We should get a button with those attributes |
535 | 117 | assert isinstance(btn, QtWidgets.QPushButton) | 138 | assert isinstance(btn, QtWidgets.QPushButton) |
538 | 118 | assert 'Hello' == btn.text() | 139 | assert btn.objectName() == 'my_btn' |
539 | 119 | assert 'How are you?' == btn.toolTip() | 140 | assert btn.text() == 'Hello' |
540 | 141 | assert btn.toolTip() == 'How are you?' | ||
541 | 120 | assert btn.isEnabled() is False | 142 | assert btn.isEnabled() is False |
542 | 143 | assert mocked_log.warning.call_args_list == [call('The role "test" is not defined in create_push_button().'), | ||
543 | 144 | call('Parameter test was not consumed in create_button().')] | ||
544 | 145 | |||
545 | 146 | def test_create_tool_button(self): | ||
546 | 147 | """ | ||
547 | 148 | Test creating a toolbutton | ||
548 | 149 | """ | ||
549 | 150 | # GIVEN: A dialog | ||
550 | 151 | dialog = QtWidgets.QDialog() | ||
551 | 121 | 152 | ||
552 | 122 | # WHEN: We create a toolbutton | 153 | # WHEN: We create a toolbutton |
553 | 123 | btn = create_button(dialog, 'my_btn', btn_class='toolbutton') | 154 | btn = create_button(dialog, 'my_btn', btn_class='toolbutton') |
554 | 124 | 155 | ||
555 | 125 | # THEN: We should get a toolbutton | 156 | # THEN: We should get a toolbutton |
556 | 126 | assert isinstance(btn, QtWidgets.QToolButton) | 157 | assert isinstance(btn, QtWidgets.QToolButton) |
558 | 127 | assert 'my_btn' == btn.objectName() | 158 | assert btn.objectName() == 'my_btn' |
559 | 128 | assert btn.isEnabled() is True | 159 | assert btn.isEnabled() is True |
560 | 129 | 160 | ||
562 | 130 | def test_create_action(self): | 161 | @patch('openlp.core.lib.ui.log') |
563 | 162 | def test_create_action(self, mocked_log): | ||
564 | 131 | """ | 163 | """ |
565 | 132 | Test creating an action | 164 | Test creating an action |
566 | 133 | """ | 165 | """ |
567 | 134 | # GIVEN: A dialog | 166 | # GIVEN: A dialog |
568 | 135 | dialog = QtWidgets.QDialog() | 167 | dialog = QtWidgets.QDialog() |
569 | 136 | 168 | ||
570 | 137 | # WHEN: We create an action | ||
571 | 138 | action = create_action(dialog, 'my_action') | ||
572 | 139 | |||
573 | 140 | # THEN: We should get a QAction | ||
574 | 141 | assert isinstance(action, QtWidgets.QAction) | ||
575 | 142 | assert 'my_action' == action.objectName() | ||
576 | 143 | |||
577 | 144 | # WHEN: We create an action with some properties | 169 | # WHEN: We create an action with some properties |
578 | 145 | action = create_action(dialog, 'my_action', text='my text', icon=':/wizards/wizard_firsttime.bmp', | 170 | action = create_action(dialog, 'my_action', text='my text', icon=':/wizards/wizard_firsttime.bmp', |
580 | 146 | tooltip='my tooltip', statustip='my statustip') | 171 | tooltip='my tooltip', statustip='my statustip', test=1) |
581 | 147 | 172 | ||
582 | 148 | # THEN: These properties should be set | 173 | # THEN: These properties should be set |
583 | 149 | assert isinstance(action, QtWidgets.QAction) | 174 | assert isinstance(action, QtWidgets.QAction) |
585 | 150 | assert 'my text' == action.text() | 175 | assert action.objectName() == 'my_action' |
586 | 176 | assert action.text() == 'my text' | ||
587 | 151 | assert isinstance(action.icon(), QtGui.QIcon) | 177 | assert isinstance(action.icon(), QtGui.QIcon) |
590 | 152 | assert 'my tooltip' == action.toolTip() | 178 | assert action.toolTip() == 'my tooltip' |
591 | 153 | assert 'my statustip' == action.statusTip() | 179 | assert action.statusTip() == 'my statustip' |
592 | 180 | mocked_log.warning.assert_called_once_with('Parameter test was not consumed in create_action().') | ||
593 | 154 | 181 | ||
594 | 155 | def test_create_action_on_mac_osx(self): | 182 | def test_create_action_on_mac_osx(self): |
595 | 156 | """ | 183 | """ |
596 | 157 | 184 | ||
597 | === modified file 'tests/functional/openlp_core/test_threading.py' | |||
598 | --- tests/functional/openlp_core/test_threading.py 2018-01-07 17:50:29 +0000 | |||
599 | +++ tests/functional/openlp_core/test_threading.py 2018-01-13 06:00:49 +0000 | |||
600 | @@ -22,9 +22,10 @@ | |||
601 | 22 | """ | 22 | """ |
602 | 23 | Package to test the openlp.core.threading package. | 23 | Package to test the openlp.core.threading package. |
603 | 24 | """ | 24 | """ |
604 | 25 | from inspect import isfunction | ||
605 | 25 | from unittest.mock import MagicMock, call, patch | 26 | from unittest.mock import MagicMock, call, patch |
606 | 26 | 27 | ||
608 | 27 | from openlp.core.version import run_thread | 28 | from openlp.core.threading import ThreadWorker, run_thread, get_thread_worker, is_thread_finished, make_remove_thread |
609 | 28 | 29 | ||
610 | 29 | 30 | ||
611 | 30 | def test_run_thread_no_name(): | 31 | def test_run_thread_no_name(): |
612 | @@ -47,9 +48,9 @@ | |||
613 | 47 | Test that trying to run a thread with a name that already exists will throw a KeyError | 48 | Test that trying to run a thread with a name that already exists will throw a KeyError |
614 | 48 | """ | 49 | """ |
615 | 49 | # GIVEN: A mocked registry with a main window object | 50 | # GIVEN: A mocked registry with a main window object |
619 | 50 | mocked_main_window = MagicMock() | 51 | mocked_application = MagicMock() |
620 | 51 | mocked_main_window.threads = {'test_thread': MagicMock()} | 52 | mocked_application.worker_threads = {'test_thread': MagicMock()} |
621 | 52 | MockRegistry.return_value.get.return_value = mocked_main_window | 53 | MockRegistry.return_value.get.return_value = mocked_application |
622 | 53 | 54 | ||
623 | 54 | # WHEN: run_thread() is called | 55 | # WHEN: run_thread() is called |
624 | 55 | try: | 56 | try: |
625 | @@ -66,18 +67,19 @@ | |||
626 | 66 | Test that running a thread works correctly | 67 | Test that running a thread works correctly |
627 | 67 | """ | 68 | """ |
628 | 68 | # GIVEN: A mocked registry with a main window object | 69 | # GIVEN: A mocked registry with a main window object |
632 | 69 | mocked_main_window = MagicMock() | 70 | mocked_application = MagicMock() |
633 | 70 | mocked_main_window.threads = {} | 71 | mocked_application.worker_threads = {} |
634 | 71 | MockRegistry.return_value.get.return_value = mocked_main_window | 72 | MockRegistry.return_value.get.return_value = mocked_application |
635 | 72 | 73 | ||
636 | 73 | # WHEN: run_thread() is called | 74 | # WHEN: run_thread() is called |
637 | 74 | run_thread(MagicMock(), 'test_thread') | 75 | run_thread(MagicMock(), 'test_thread') |
638 | 75 | 76 | ||
639 | 76 | # THEN: The thread should be in the threads list and the correct methods should have been called | 77 | # THEN: The thread should be in the threads list and the correct methods should have been called |
644 | 77 | assert len(mocked_main_window.threads.keys()) == 1, 'There should be 1 item in the list of threads' | 78 | assert len(mocked_application.worker_threads.keys()) == 1, 'There should be 1 item in the list of threads' |
645 | 78 | assert list(mocked_main_window.threads.keys()) == ['test_thread'], 'The test_thread item should be in the list' | 79 | assert list(mocked_application.worker_threads.keys()) == ['test_thread'], \ |
646 | 79 | mocked_worker = mocked_main_window.threads['test_thread']['worker'] | 80 | 'The test_thread item should be in the list' |
647 | 80 | mocked_thread = mocked_main_window.threads['test_thread']['thread'] | 81 | mocked_worker = mocked_application.worker_threads['test_thread']['worker'] |
648 | 82 | mocked_thread = mocked_application.worker_threads['test_thread']['thread'] | ||
649 | 81 | mocked_worker.moveToThread.assert_called_once_with(mocked_thread) | 83 | mocked_worker.moveToThread.assert_called_once_with(mocked_thread) |
650 | 82 | mocked_thread.started.connect.assert_called_once_with(mocked_worker.start) | 84 | mocked_thread.started.connect.assert_called_once_with(mocked_worker.start) |
651 | 83 | expected_quit_calls = [call(mocked_thread.quit), call(mocked_worker.deleteLater)] | 85 | expected_quit_calls = [call(mocked_thread.quit), call(mocked_worker.deleteLater)] |
652 | @@ -87,3 +89,103 @@ | |||
653 | 87 | 'The threads finished signal should be connected to its deleteLater slot' | 89 | 'The threads finished signal should be connected to its deleteLater slot' |
654 | 88 | assert mocked_thread.finished.connect.call_count == 2, 'The signal should have been connected twice' | 90 | assert mocked_thread.finished.connect.call_count == 2, 'The signal should have been connected twice' |
655 | 89 | mocked_thread.start.assert_called_once_with() | 91 | mocked_thread.start.assert_called_once_with() |
656 | 92 | |||
657 | 93 | |||
658 | 94 | def test_thread_worker(): | ||
659 | 95 | """ | ||
660 | 96 | Test that creating a thread worker object and calling start throws and NotImplementedError | ||
661 | 97 | """ | ||
662 | 98 | # GIVEN: A ThreadWorker class | ||
663 | 99 | worker = ThreadWorker() | ||
664 | 100 | |||
665 | 101 | try: | ||
666 | 102 | # WHEN: calling start() | ||
667 | 103 | worker.start() | ||
668 | 104 | assert False, 'A NotImplementedError should have been thrown' | ||
669 | 105 | except NotImplementedError: | ||
670 | 106 | # A NotImplementedError should be thrown | ||
671 | 107 | pass | ||
672 | 108 | except Exception: | ||
673 | 109 | assert False, 'A NotImplementedError should have been thrown' | ||
674 | 110 | |||
675 | 111 | |||
676 | 112 | @patch('openlp.core.threading.Registry') | ||
677 | 113 | def test_get_thread_worker(MockRegistry): | ||
678 | 114 | """ | ||
679 | 115 | Test that calling the get_thread_worker() function returns the correct worker | ||
680 | 116 | """ | ||
681 | 117 | # GIVEN: A mocked thread worker | ||
682 | 118 | mocked_worker = MagicMock() | ||
683 | 119 | MockRegistry.return_value.get.return_value.worker_threads = {'test_thread': {'worker': mocked_worker}} | ||
684 | 120 | |||
685 | 121 | # WHEN: get_thread_worker() is called | ||
686 | 122 | worker = get_thread_worker('test_thread') | ||
687 | 123 | |||
688 | 124 | # THEN: The mocked worker is returned | ||
689 | 125 | assert worker is mocked_worker, 'The mocked worker should have been returned' | ||
690 | 126 | |||
691 | 127 | |||
692 | 128 | @patch('openlp.core.threading.Registry') | ||
693 | 129 | def test_get_thread_worker_mising(MockRegistry): | ||
694 | 130 | """ | ||
695 | 131 | Test that calling the get_thread_worker() function raises a KeyError if it does not exist | ||
696 | 132 | """ | ||
697 | 133 | # GIVEN: A mocked thread worker | ||
698 | 134 | MockRegistry.return_value.get.return_value.worker_threads = {} | ||
699 | 135 | |||
700 | 136 | try: | ||
701 | 137 | # WHEN: get_thread_worker() is called | ||
702 | 138 | get_thread_worker('test_thread') | ||
703 | 139 | assert False, 'A KeyError should have been raised' | ||
704 | 140 | except KeyError: | ||
705 | 141 | # THEN: The mocked worker is returned | ||
706 | 142 | pass | ||
707 | 143 | except Exception: | ||
708 | 144 | assert False, 'A KeyError should have been raised' | ||
709 | 145 | |||
710 | 146 | |||
711 | 147 | @patch('openlp.core.threading.Registry') | ||
712 | 148 | def test_is_thread_finished(MockRegistry): | ||
713 | 149 | """ | ||
714 | 150 | Test the is_thread_finished() function | ||
715 | 151 | """ | ||
716 | 152 | # GIVEN: A mock thread and worker | ||
717 | 153 | mocked_thread = MagicMock() | ||
718 | 154 | mocked_thread.isFinished.return_value = False | ||
719 | 155 | MockRegistry.return_value.get.return_value.worker_threads = {'test': {'thread': mocked_thread}} | ||
720 | 156 | |||
721 | 157 | # WHEN: is_thread_finished() is called | ||
722 | 158 | result = is_thread_finished('test') | ||
723 | 159 | |||
724 | 160 | # THEN: The result should be correct | ||
725 | 161 | assert result is False, 'is_thread_finished should have returned False' | ||
726 | 162 | |||
727 | 163 | |||
728 | 164 | @patch('openlp.core.threading.Registry') | ||
729 | 165 | def test_is_thread_finished_missing(MockRegistry): | ||
730 | 166 | """ | ||
731 | 167 | Test that calling the is_thread_finished() function returns True if the thread doesn't exist | ||
732 | 168 | """ | ||
733 | 169 | # GIVEN: A mocked thread worker | ||
734 | 170 | MockRegistry.return_value.get.return_value.worker_threads = {} | ||
735 | 171 | |||
736 | 172 | # WHEN: get_thread_worker() is called | ||
737 | 173 | result = is_thread_finished('test_thread') | ||
738 | 174 | |||
739 | 175 | # THEN: The result should be correct | ||
740 | 176 | assert result is True, 'is_thread_finished should return True when a thread is missing' | ||
741 | 177 | |||
742 | 178 | |||
743 | 179 | def test_make_remove_thread(): | ||
744 | 180 | """ | ||
745 | 181 | Test the make_remove_thread() function | ||
746 | 182 | """ | ||
747 | 183 | # GIVEN: A thread name | ||
748 | 184 | thread_name = 'test_thread' | ||
749 | 185 | |||
750 | 186 | # WHEN: make_remove_thread() is called | ||
751 | 187 | rm_func = make_remove_thread(thread_name) | ||
752 | 188 | |||
753 | 189 | # THEN: The result should be a function | ||
754 | 190 | assert isfunction(rm_func), 'make_remove_thread should return a function' | ||
755 | 191 | assert rm_func.__name__ == 'remove_thread' |