Merge lp:~trb143/openlp/localserver into lp:openlp

Proposed by Tim Bentley
Status: Merged
Merged at revision: 2816
Proposed branch: lp:~trb143/openlp/localserver
Merge into: lp:openlp
Diff against target: 633 lines (+405/-77)
8 files modified
openlp/.version (+1/-1)
openlp/core/app.py (+21/-25)
openlp/core/common/i18n.py (+1/-1)
openlp/core/server.py (+109/-0)
openlp/core/version.py (+6/-42)
tests/functional/openlp_core/api/http/test_init.py (+151/-0)
tests/functional/openlp_core/test_app.py (+2/-8)
tests/functional/openlp_core/test_server.py (+114/-0)
To merge this branch: bzr merge lp:~trb143/openlp/localserver
Reviewer Review Type Date Requested Status
Tomas Groth Approve
Review via email: mp+342804@code.launchpad.net

This proposal supersedes a proposal from 2018-04-05.

Commit message

Replace the Memory check with a local server which stops and starts correctly.
One the 2nd instance pass the service file if one is included
Stop the 2nd instance starting as it will fail due to port clashes and a monster thread issue(may be connected).
Removed redundant code
Add a number of new tests.

Description of the change

Replace the Memory check with a local server which stops and starts correctly.
One the 2nd instance pass the service file if one is included
Stop the 2nd instance starting as it will fail due to port clashes and a monster thread issue(may be connected).
Removed redundant code
Add a number of new tests.

lp:~trb143/openlp/localserver (revision 2850)
https://ci.openlp.io/job/Branch-01-Pull/2501/ [SUCCESS]
https://ci.openlp.io/job/Branch-02a-Linux-Tests/2402/ [SUCCESS]
https://ci.openlp.io/job/Branch-02b-macOS-Tests/188/ [FAILURE]
https://ci.openlp.io/job/Branch-03a-Build-Source/100/ [SUCCESS]
https://ci.openlp.io/job/Branch-03b-Build-macOS/93/ [SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code-Analysis/1562/ [SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test-Coverage/1375/ [SUCCESS]

To post a comment you must log in.
Revision history for this message
Tomas Groth (tomasgroth) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/.version'
2--- openlp/.version 2016-12-12 22:16:23 +0000
3+++ openlp/.version 2018-04-06 15:58:59 +0000
4@@ -1,1 +1,1 @@
5-2.5.0
6+2.9.0
7
8=== modified file 'openlp/core/app.py'
9--- openlp/core/app.py 2018-02-03 12:03:37 +0000
10+++ openlp/core/app.py 2018-04-06 15:58:59 +0000
11@@ -38,7 +38,6 @@
12 from openlp.core.common import is_macosx, is_win
13 from openlp.core.common.applocation import AppLocation
14 from openlp.core.common.i18n import LanguageManager, UiStrings, translate
15-from openlp.core.common.mixins import LogMixin
16 from openlp.core.common.path import create_paths, copytree
17 from openlp.core.common.registry import Registry
18 from openlp.core.common.settings import Settings
19@@ -50,6 +49,7 @@
20 from openlp.core.ui.firsttimelanguageform import FirstTimeLanguageForm
21 from openlp.core.ui.mainwindow import MainWindow
22 from openlp.core.ui.style import get_application_stylesheet
23+from openlp.core.server import Server
24 from openlp.core.version import check_for_update, get_version
25
26 __all__ = ['OpenLP', 'main']
27@@ -58,7 +58,7 @@
28 log = logging.getLogger()
29
30
31-class OpenLP(QtWidgets.QApplication, LogMixin):
32+class OpenLP(QtWidgets.QApplication):
33 """
34 The core application class. This class inherits from Qt's QApplication
35 class in order to provide the core of the application.
36@@ -72,7 +72,7 @@
37 """
38 self.is_event_loop_active = True
39 result = QtWidgets.QApplication.exec()
40- self.shared_memory.detach()
41+ self.server.close_server()
42 return result
43
44 def run(self, args):
45@@ -135,23 +135,16 @@
46 self.main_window.app_startup()
47 return self.exec()
48
49- def is_already_running(self):
50- """
51- Look to see if OpenLP is already running and ask if a 2nd instance is to be started.
52- """
53- self.shared_memory = QtCore.QSharedMemory('OpenLP')
54- if self.shared_memory.attach():
55- status = QtWidgets.QMessageBox.critical(None, UiStrings().Error, UiStrings().OpenLPStart,
56- QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Yes |
57- QtWidgets.QMessageBox.No))
58- if status == QtWidgets.QMessageBox.No:
59- return True
60- return False
61- else:
62- self.shared_memory.create(1)
63- return False
64+ @staticmethod
65+ def is_already_running():
66+ """
67+ Tell the user there is a 2nd instance running.
68+ """
69+ QtWidgets.QMessageBox.critical(None, UiStrings().Error, UiStrings().OpenLPStart,
70+ QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok))
71
72- def is_data_path_missing(self):
73+ @staticmethod
74+ def is_data_path_missing():
75 """
76 Check if the data folder path exists.
77 """
78@@ -301,10 +294,7 @@
79 parser.add_argument('-l', '--log-level', dest='loglevel', default='warning', metavar='LEVEL',
80 help='Set logging to LEVEL level. Valid values are "debug", "info", "warning".')
81 parser.add_argument('-p', '--portable', dest='portable', action='store_true',
82- help='Specify if this should be run as a portable app, '
83- 'off a USB flash drive (not implemented).')
84- parser.add_argument('-d', '--dev-version', dest='dev_version', action='store_true',
85- help='Ignore the version file and pull the version directly from Bazaar')
86+ help='Specify if this should be run as a portable app, ')
87 parser.add_argument('-w', '--no-web-server', dest='no_web_server', action='store_true',
88 help='Turn off the Web and Socket Server ')
89 parser.add_argument('rargs', nargs='?', default=[])
90@@ -383,11 +373,17 @@
91 Registry().set_flag('no_web_server', args.no_web_server)
92 application.setApplicationVersion(get_version()['version'])
93 # Check if an instance of OpenLP is already running. Quit if there is a running instance and the user only wants one
94- if application.is_already_running():
95+ server = Server()
96+ if server.is_another_instance_running():
97+ application.is_already_running()
98+ server.post_to_server(qt_args)
99 sys.exit()
100+ else:
101+ server.start_server()
102+ application.server = server
103 # If the custom data path is missing and the user wants to restore the data path, quit OpenLP.
104 if application.is_data_path_missing():
105- application.shared_memory.detach()
106+ server.close_server()
107 sys.exit()
108 # Upgrade settings.
109 settings = Settings()
110
111=== modified file 'openlp/core/common/i18n.py'
112--- openlp/core/common/i18n.py 2017-12-29 09:15:48 +0000
113+++ openlp/core/common/i18n.py 2018-04-06 15:58:59 +0000
114@@ -415,7 +415,7 @@
115 self.NoResults = translate('OpenLP.Ui', 'No Search Results')
116 self.OpenLP = translate('OpenLP.Ui', 'OpenLP')
117 self.OpenLPv2AndUp = translate('OpenLP.Ui', 'OpenLP 2.0 and up')
118- self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running. Do you wish to continue?')
119+ self.OpenLPStart = translate('OpenLP.Ui', 'OpenLP is already running on this machine. \nClosing this instance')
120 self.OpenService = translate('OpenLP.Ui', 'Open service.')
121 self.OptionalShowInFooter = translate('OpenLP.Ui', 'Optional, this will be displayed in footer.')
122 self.OptionalHideInFooter = translate('OpenLP.Ui', 'Optional, this won\'t be displayed in footer.')
123
124=== added file 'openlp/core/server.py'
125--- openlp/core/server.py 1970-01-01 00:00:00 +0000
126+++ openlp/core/server.py 2018-04-06 15:58:59 +0000
127@@ -0,0 +1,109 @@
128+# -*- coding: utf-8 -*-
129+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
130+
131+###############################################################################
132+# OpenLP - Open Source Lyrics Projection #
133+# --------------------------------------------------------------------------- #
134+# Copyright (c) 2008-2018 OpenLP Developers #
135+# --------------------------------------------------------------------------- #
136+# This program is free software; you can redistribute it and/or modify it #
137+# under the terms of the GNU General Public License as published by the Free #
138+# Software Foundation; version 2 of the License. #
139+# #
140+# This program is distributed in the hope that it will be useful, but WITHOUT #
141+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
142+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
143+# more details. #
144+# #
145+# You should have received a copy of the GNU General Public License along #
146+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
147+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
148+###############################################################################
149+from PyQt5 import QtCore, QtNetwork
150+
151+from openlp.core.common.registry import Registry
152+from openlp.core.common.mixins import LogMixin
153+
154+
155+class Server(QtCore.QObject, LogMixin):
156+ """
157+ The local server to handle OpenLP running in more than one instance and allows file
158+ handles to be transferred from the new to the existing one.
159+ """
160+ def __init__(self):
161+ super(Server, self).__init__()
162+ self.out_socket = QtNetwork.QLocalSocket()
163+ self.server = None
164+ self.id = 'OpenLPDual'
165+
166+ def is_another_instance_running(self):
167+ """
168+ Check the see if an other instance is running
169+ :return: True of False
170+ """
171+ # Is there another instance running?
172+ self.out_socket.connectToServer(self.id)
173+ return self.out_socket.waitForConnected()
174+
175+ def post_to_server(self, args):
176+ """
177+ Post the file name to the over instance
178+ :param args: The passed arguments including maybe a file name
179+ """
180+ if 'OpenLP' in args:
181+ args.remove('OpenLP')
182+ # Yes, there is.
183+ self.out_stream = QtCore.QTextStream(self.out_socket)
184+ self.out_stream.setCodec('UTF-8')
185+ self.out_socket.write(str.encode("".join(args)))
186+ if not self.out_socket.waitForBytesWritten(10):
187+ raise Exception(str(self.out_socket.errorString()))
188+ self.out_socket.disconnectFromServer()
189+
190+ def start_server(self):
191+ """
192+ Start the socket server to allow inter app communication
193+ :return:
194+ """
195+ self.out_socket = None
196+ self.out_stream = None
197+ self.in_socket = None
198+ self.in_stream = None
199+ self.server = QtNetwork.QLocalServer()
200+ self.server.listen(self.id)
201+ self.server.newConnection.connect(self._on_new_connection)
202+ return True
203+
204+ def _on_new_connection(self):
205+ """
206+ Handle a new connection to the server
207+ :return:
208+ """
209+ if self.in_socket:
210+ self.in_socket.readyRead.disconnect(self._on_ready_read)
211+ self.in_socket = self.server.nextPendingConnection()
212+ if not self.in_socket:
213+ return
214+ self.in_stream = QtCore.QTextStream(self.in_socket)
215+ self.in_stream.setCodec('UTF-8')
216+ self.in_socket.readyRead.connect(self._on_ready_read)
217+
218+ def _on_ready_read(self):
219+ """
220+ Read a record passed to the server and pass to the service manager to handle
221+ :return:
222+ """
223+ msg = self.in_stream.readLine()
224+ if msg:
225+ self.log_debug("socket msg = " + msg)
226+ Registry().get('service_manager').on_load_service_clicked(msg)
227+
228+ def close_server(self):
229+ """
230+ Shutdown to local socket server and make sure the server is removed.
231+ :return:
232+ """
233+ if self.server:
234+ self.server.close()
235+ # Make sure the server file is removed.
236+ QtNetwork.QLocalServer.removeServer(self.id)
237
238=== modified file 'openlp/core/version.py'
239--- openlp/core/version.py 2018-01-04 06:10:20 +0000
240+++ openlp/core/version.py 2018-04-06 15:58:59 +0000
241@@ -136,48 +136,12 @@
242 global APPLICATION_VERSION
243 if APPLICATION_VERSION:
244 return APPLICATION_VERSION
245- if '--dev-version' in sys.argv or '-d' in sys.argv:
246- # NOTE: The following code is a duplicate of the code in setup.py. Any fix applied here should also be applied
247- # there.
248-
249- # Get the revision of this tree.
250- bzr = Popen(('bzr', 'revno'), stdout=PIPE)
251- tree_revision, error = bzr.communicate()
252- tree_revision = tree_revision.decode()
253- code = bzr.wait()
254- if code != 0:
255- raise Exception('Error running bzr log')
256-
257- # Get all tags.
258- bzr = Popen(('bzr', 'tags'), stdout=PIPE)
259- output, error = bzr.communicate()
260- code = bzr.wait()
261- if code != 0:
262- raise Exception('Error running bzr tags')
263- tags = list(map(bytes.decode, output.splitlines()))
264- if not tags:
265- tag_version = '0.0.0'
266- tag_revision = '0'
267- else:
268- # Remove any tag that has "?" as revision number. A "?" as revision number indicates, that this tag is from
269- # another series.
270- tags = [tag for tag in tags if tag.split()[-1].strip() != '?']
271- # Get the last tag and split it in a revision and tag name.
272- tag_version, tag_revision = tags[-1].split()
273- # If they are equal, then this tree is tarball with the source for the release. We do not want the revision
274- # number in the full version.
275- if tree_revision == tag_revision:
276- full_version = tag_version.strip()
277- else:
278- full_version = '{tag}-bzr{tree}'.format(tag=tag_version.strip(), tree=tree_revision.strip())
279- else:
280- # We're not running the development version, let's use the file.
281- file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version'
282- try:
283- full_version = file_path.read_text().rstrip()
284- except OSError:
285- log.exception('Error in version file.')
286- full_version = '0.0.0-bzr000'
287+ file_path = AppLocation.get_directory(AppLocation.VersionDir) / '.version'
288+ try:
289+ full_version = file_path.read_text().rstrip()
290+ except OSError:
291+ log.exception('Error in version file.')
292+ full_version = '0.0.0-bzr000'
293 bits = full_version.split('-')
294 APPLICATION_VERSION = {
295 'full': full_version,
296
297=== added file 'tests/functional/openlp_core/api/http/test_init.py'
298--- tests/functional/openlp_core/api/http/test_init.py 1970-01-01 00:00:00 +0000
299+++ tests/functional/openlp_core/api/http/test_init.py 2018-04-06 15:58:59 +0000
300@@ -0,0 +1,151 @@
301+# -*- coding: utf-8 -*-
302+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
303+
304+###############################################################################
305+# OpenLP - Open Source Lyrics Projection #
306+# --------------------------------------------------------------------------- #
307+# Copyright (c) 2008-2018 OpenLP Developers #
308+# --------------------------------------------------------------------------- #
309+# This program is free software; you can redistribute it and/or modify it #
310+# under the terms of the GNU General Public License as published by the Free #
311+# Software Foundation; version 2 of the License. #
312+# #
313+# This program is distributed in the hope that it will be useful, but WITHOUT #
314+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
315+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
316+# more details. #
317+# #
318+# You should have received a copy of the GNU General Public License along #
319+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
320+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
321+###############################################################################
322+"""
323+Functional tests to test the Http init.
324+"""
325+from unittest import TestCase
326+from unittest.mock import MagicMock
327+
328+from openlp.core.api.http import check_auth, requires_auth, authenticate
329+from openlp.core.common.registry import Registry
330+from openlp.core.common.settings import Settings
331+
332+from tests.helpers.testmixin import TestMixin
333+
334+
335+class TestInit(TestCase, TestMixin):
336+ """
337+ A test suite to test the functions on the init
338+ """
339+
340+ def setUp(self):
341+ """
342+ Create the UI
343+ """
344+ Registry().create()
345+ Registry().register('service_list', MagicMock())
346+ self.build_settings()
347+ self.password = 'c3VwZXJmbHk6bGFtYXM='
348+
349+ def tearDown(self):
350+ self.destroy_settings()
351+
352+ def test_auth(self):
353+ """
354+ Test the check_auth method with a match
355+ :return:
356+ """
357+ # GIVEN: a known user
358+ Settings().setValue('api/user id', "superfly")
359+ Settings().setValue('api/password', "lamas")
360+
361+ # WHEN : I check the authorisation
362+ is_valid = check_auth(['aaaaa', self.password])
363+
364+ # THEN:
365+ assert is_valid is True
366+
367+ def test_auth_falure(self):
368+ """
369+ Test the check_auth method with a match
370+ :return:
371+ """
372+ # GIVEN: a known user
373+ Settings().setValue('api/user id', 'superfly')
374+ Settings().setValue('api/password', 'lamas')
375+
376+ # WHEN : I check the authorisation
377+ is_valid = check_auth(['aaaaa', 'monkey123'])
378+
379+ # THEN:
380+ assert is_valid is False
381+
382+ def test_requires_auth_disabled(self):
383+ """
384+ Test the requires_auth wrapper with disabled security
385+ :return:
386+ """
387+ # GIVEN: A disabled security
388+ Settings().setValue('api/authentication enabled', False)
389+
390+ # WHEN: I call the function
391+ wrapped_function = requires_auth(func)
392+ value = wrapped_function()
393+
394+ # THEN: the result will be as expected
395+ assert value == 'called'
396+
397+ def test_requires_auth_enabled(self):
398+ """
399+ Test the requires_auth wrapper with enabled security
400+ :return:
401+ """
402+ # GIVEN: A disabled security
403+ Settings().setValue('api/authentication enabled', True)
404+
405+ # WHEN: I call the function
406+ wrapped_function = requires_auth(func)
407+ req = MagicMock()
408+ value = wrapped_function(req)
409+
410+ # THEN: the result will be as expected
411+ assert str(value) == str(authenticate())
412+
413+ def test_requires_auth_enabled_auth_error(self):
414+ """
415+ Test the requires_auth wrapper with enabled security and authorization taken place and and error
416+ :return:
417+ """
418+ # GIVEN: A enabled security
419+ Settings().setValue('api/authentication enabled', True)
420+
421+ # WHEN: I call the function with the wrong password
422+ wrapped_function = requires_auth(func)
423+ req = MagicMock()
424+ req.authorization = ['Basic', 'cccccccc']
425+ value = wrapped_function(req)
426+
427+ # THEN: the result will be as expected - try again
428+ assert str(value) == str(authenticate())
429+
430+ def test_requires_auth_enabled_auth(self):
431+ """
432+ Test the requires_auth wrapper with enabled security and authorization taken place and and error
433+ :return:
434+ """
435+ # GIVEN: An enabled security and a known user
436+ Settings().setValue('api/authentication enabled', True)
437+ Settings().setValue('api/user id', 'superfly')
438+ Settings().setValue('api/password', 'lamas')
439+
440+ # WHEN: I call the function with the wrong password
441+ wrapped_function = requires_auth(func)
442+ req = MagicMock()
443+ req.authorization = ['Basic', self.password]
444+ value = wrapped_function(req)
445+
446+ # THEN: the result will be as expected - try again
447+ assert str(value) == 'called'
448+
449+
450+def func(field=None):
451+ return 'called'
452
453=== modified file 'tests/functional/openlp_core/test_app.py'
454--- tests/functional/openlp_core/test_app.py 2018-01-07 05:24:55 +0000
455+++ tests/functional/openlp_core/test_app.py 2018-04-06 15:58:59 +0000
456@@ -41,7 +41,6 @@
457 args = parse_options()
458
459 # THEN: the following fields will have been extracted.
460- assert args.dev_version is False, 'The dev_version flag should be False'
461 assert args.loglevel == 'warning', 'The log level should be set to warning'
462 assert args.no_error_form is False, 'The no_error_form should be set to False'
463 assert args.portable is False, 'The portable flag should be set to false'
464@@ -59,7 +58,6 @@
465 args = parse_options()
466
467 # THEN: the following fields will have been extracted.
468- assert args.dev_version is False, 'The dev_version flag should be False'
469 assert args.loglevel == ' debug', 'The log level should be set to debug'
470 assert args.no_error_form is False, 'The no_error_form should be set to False'
471 assert args.portable is False, 'The portable flag should be set to false'
472@@ -77,7 +75,6 @@
473 args = parse_options()
474
475 # THEN: the following fields will have been extracted.
476- assert args.dev_version is False, 'The dev_version flag should be False'
477 assert args.loglevel == 'warning', 'The log level should be set to warning'
478 assert args.no_error_form is False, 'The no_error_form should be set to False'
479 assert args.portable is True, 'The portable flag should be set to true'
480@@ -89,16 +86,15 @@
481 Test the parse options process works with two options
482 """
483 # GIVEN: a a set of system arguments.
484- sys.argv[1:] = ['-l debug', '-d']
485+ sys.argv[1:] = ['-l debug', '-p']
486
487 # WHEN: We we parse them to expand to options
488 args = parse_options()
489
490 # THEN: the following fields will have been extracted.
491- assert args.dev_version is True, 'The dev_version flag should be True'
492 assert args.loglevel == ' debug', 'The log level should be set to debug'
493 assert args.no_error_form is False, 'The no_error_form should be set to False'
494- assert args.portable is False, 'The portable flag should be set to false'
495+ assert args.portable is True, 'The portable flag should be set to false'
496 assert args.rargs == [], 'The service file should be blank'
497
498
499@@ -113,7 +109,6 @@
500 args = parse_options()
501
502 # THEN: the following fields will have been extracted.
503- assert args.dev_version is False, 'The dev_version flag should be False'
504 assert args.loglevel == 'warning', 'The log level should be set to warning'
505 assert args.no_error_form is False, 'The no_error_form should be set to False'
506 assert args.portable is False, 'The portable flag should be set to false'
507@@ -131,7 +126,6 @@
508 args = parse_options()
509
510 # THEN: the following fields will have been extracted.
511- assert args.dev_version is False, 'The dev_version flag should be False'
512 assert args.loglevel == ' debug', 'The log level should be set to debug'
513 assert args.no_error_form is False, 'The no_error_form should be set to False'
514 assert args.portable is False, 'The portable flag should be set to false'
515
516=== added file 'tests/functional/openlp_core/test_server.py'
517--- tests/functional/openlp_core/test_server.py 1970-01-01 00:00:00 +0000
518+++ tests/functional/openlp_core/test_server.py 2018-04-06 15:58:59 +0000
519@@ -0,0 +1,114 @@
520+# -*- coding: utf-8 -*-
521+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
522+
523+###############################################################################
524+# OpenLP - Open Source Lyrics Projection #
525+# --------------------------------------------------------------------------- #
526+# Copyright (c) 2008-2018 OpenLP Developers #
527+# --------------------------------------------------------------------------- #
528+# This program is free software; you can redistribute it and/or modify it #
529+# under the terms of the GNU General Public License as published by the Free #
530+# Software Foundation; version 2 of the License. #
531+# #
532+# This program is distributed in the hope that it will be useful, but WITHOUT #
533+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
534+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
535+# more details. #
536+# #
537+# You should have received a copy of the GNU General Public License along #
538+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
539+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
540+###############################################################################
541+from unittest import TestCase
542+from unittest.mock import MagicMock, patch
543+
544+from openlp.core.server import Server
545+from openlp.core.common.registry import Registry
546+
547+from tests.helpers.testmixin import TestMixin
548+
549+
550+class TestServer(TestCase, TestMixin):
551+ """
552+ Test the Server Class used to check if OpenLP is running.
553+ """
554+ def setUp(self):
555+ Registry.create()
556+ with patch('PyQt5.QtNetwork.QLocalSocket'):
557+ self.server = Server()
558+
559+ def tearDown(self):
560+ self.server.close_server()
561+
562+ def test_is_another_instance_running(self):
563+ """
564+ Run a test as if this was the first time and no instance is running
565+ """
566+ # GIVEN: A running Server
567+
568+ # WHEN: I ask for it to start
569+ value = self.server.is_another_instance_running()
570+
571+ # THEN the following is called
572+ self.server.out_socket.waitForConnected.assert_called_once_with()
573+ self.server.out_socket.connectToServer.assert_called_once_with(self.server.id)
574+ assert isinstance(value, MagicMock)
575+
576+ def test_is_another_instance_running_true(self):
577+ """
578+ Run a test as if there is another instance running
579+ """
580+ # GIVEN: A running Server
581+ self.server.out_socket.waitForConnected.return_value = True
582+
583+ # WHEN: I ask for it to start
584+ value = self.server.is_another_instance_running()
585+
586+ # THEN the following is called
587+ self.server.out_socket.waitForConnected.assert_called_once_with()
588+ self.server.out_socket.connectToServer.assert_called_once_with(self.server.id)
589+ assert value is True
590+
591+ def test_on_read_ready(self):
592+ """
593+ Test the on_read_ready method calls the service_manager
594+ """
595+ # GIVEN: A server with a service manager
596+ self.server.in_stream = MagicMock()
597+ service_manager = MagicMock()
598+ Registry().register('service_manager', service_manager)
599+
600+ # WHEN: a file is added to the socket and the method called
601+ file_name = '\\home\\superfly\\'
602+ self.server.in_stream.readLine.return_value = file_name
603+ self.server._on_ready_read()
604+
605+ # THEN: the service will be loaded
606+ assert service_manager.on_load_service_clicked.call_count == 1
607+ service_manager.on_load_service_clicked.assert_called_once_with(file_name)
608+
609+ @patch("PyQt5.QtCore.QTextStream")
610+ def test_post_to_server(self, mocked_stream):
611+ """
612+ A Basic test with a post to the service
613+ :return:
614+ """
615+ # GIVEN: A server
616+ # WHEN: I post to a server
617+ self.server.post_to_server(['l', 'a', 'm', 'a', 's'])
618+
619+ # THEN: the file should be passed out to the socket
620+ self.server.out_socket.write.assert_called_once_with(b'lamas')
621+
622+ @patch("PyQt5.QtCore.QTextStream")
623+ def test_post_to_server_openlp(self, mocked_stream):
624+ """
625+ A Basic test with a post to the service with OpenLP
626+ :return:
627+ """
628+ # GIVEN: A server
629+ # WHEN: I post to a server
630+ self.server.post_to_server(['l', 'a', 'm', 'a', 's', 'OpenLP'])
631+
632+ # THEN: the file should be passed out to the socket
633+ self.server.out_socket.write.assert_called_once_with(b'lamas')