Merge lp:~raoul-snyman/openlp/zeroconf into lp:openlp

Proposed by Raoul Snyman
Status: Merged
Approved by: Raoul Snyman
Approved revision: 2890
Merged at revision: 2885
Proposed branch: lp:~raoul-snyman/openlp/zeroconf
Merge into: lp:openlp
Diff against target: 768 lines (+336/-128)
14 files modified
openlp/core/api/tab.py (+4/-6)
openlp/core/api/zeroconf.py (+99/-0)
openlp/core/common/__init__.py (+23/-32)
openlp/core/ui/mainwindow.py (+6/-4)
run_openlp.py (+17/-1)
scripts/appveyor.yml (+1/-1)
scripts/check_dependencies.py (+2/-1)
setup.py (+2/-1)
tests/functional/openlp_core/api/test_tab.py (+6/-6)
tests/functional/openlp_core/common/test_json.py (+5/-5)
tests/interfaces/openlp_core/ui/test_mainwindow.py (+4/-3)
tests/openlp_core/api/test_zeroconf.py (+112/-0)
tests/openlp_core/common/test_network_interfaces.py (+52/-66)
tests/openlp_core/projectors/test_projector_db.py (+3/-2)
To merge this branch: bzr merge lp:~raoul-snyman/openlp/zeroconf
Reviewer Review Type Date Requested Status
Tomas Groth Approve
Review via email: mp+369632@code.launchpad.net

This proposal supersedes a proposal from 2019-07-03.

Commit message

Add Zeroconf services to OpenLP so that external devices can find OpenLP on the network.

Description of the change

Add Zeroconf services to OpenLP so that external devices can find OpenLP on the network.

To post a comment you must log in.
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/201/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linting passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

macOS tests failed, please see https://ci.openlp.io/job/MP-04-macOS-Tests/107/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

macOS tests passed!

Revision history for this message
Tomas Groth (tomasgroth) wrote : Posted in a previous version of this proposal

Please see inline comments.

review: Needs Fixing
Revision history for this message
Raoul Snyman (raoul-snyman) : Posted in a previous version of this proposal
Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

See inline

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

Linux tests failed, please see https://ci.openlp.io/job/MP-02-Linux_Tests/203/ for more details

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linux tests passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

Linting passed!

Revision history for this message
Raoul Snyman (raoul-snyman) wrote :

macOS tests passed!

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
=== modified file 'openlp/core/api/tab.py'
--- openlp/core/api/tab.py 2019-04-13 13:00:22 +0000
+++ openlp/core/api/tab.py 2019-07-03 06:34:48 +0000
@@ -24,7 +24,7 @@
24"""24"""
25from PyQt5 import QtCore, QtGui, QtWidgets25from PyQt5 import QtCore, QtGui, QtWidgets
2626
27from openlp.core.common import get_local_ip427from openlp.core.common import get_network_interfaces
28from openlp.core.common.i18n import UiStrings, translate28from openlp.core.common.i18n import UiStrings, translate
29from openlp.core.common.registry import Registry29from openlp.core.common.registry import Registry
30from openlp.core.common.settings import Settings30from openlp.core.common.settings import Settings
@@ -194,8 +194,7 @@
194 http_url_temp = http_url + 'main'194 http_url_temp = http_url + 'main'
195 self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))195 self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
196196
197 @staticmethod197 def get_ip_address(self, ip_address):
198 def get_ip_address(ip_address):
199 """198 """
200 returns the IP address in dependency of the passed address199 returns the IP address in dependency of the passed address
201 ip_address == 0.0.0.0: return the IP address of the first valid interface200 ip_address == 0.0.0.0: return the IP address of the first valid interface
@@ -203,9 +202,8 @@
203 """202 """
204 if ip_address == ZERO_URL:203 if ip_address == ZERO_URL:
205 # In case we have more than one interface204 # In case we have more than one interface
206 ifaces = get_local_ip4()205 for _, interface in get_network_interfaces().items():
207 for key in iter(ifaces):206 ip_address = interface['ip']
208 ip_address = ifaces.get(key)['ip']
209 # We only want the first interface returned207 # We only want the first interface returned
210 break208 break
211 return ip_address209 return ip_address
212210
=== added file 'openlp/core/api/zeroconf.py'
--- openlp/core/api/zeroconf.py 1970-01-01 00:00:00 +0000
+++ openlp/core/api/zeroconf.py 2019-07-03 06:34:48 +0000
@@ -0,0 +1,99 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4##########################################################################
5# OpenLP - Open Source Lyrics Projection #
6# ---------------------------------------------------------------------- #
7# Copyright (c) 2008-2019 OpenLP Developers #
8# ---------------------------------------------------------------------- #
9# This program is free software: you can redistribute it and/or modify #
10# it under the terms of the GNU General Public License as published by #
11# the Free Software Foundation, either version 3 of the License, or #
12# (at your option) any later version. #
13# #
14# This program is distributed in the hope that it will be useful, #
15# but WITHOUT ANY WARRANTY; without even the implied warranty of #
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
17# GNU General Public License for more details. #
18# #
19# You should have received a copy of the GNU General Public License #
20# along with this program. If not, see <https://www.gnu.org/licenses/>. #
21##########################################################################
22"""
23The :mod:`~openlp.core.api.zeroconf` module runs a Zerconf server so that OpenLP can advertise the
24RESTful API for devices on the network to discover.
25"""
26import socket
27from time import sleep
28
29from zeroconf import ServiceInfo, Zeroconf
30
31from openlp.core.common import get_network_interfaces
32from openlp.core.common.registry import Registry
33from openlp.core.common.settings import Settings
34from openlp.core.threading import ThreadWorker, run_thread
35
36
37class ZeroconfWorker(ThreadWorker):
38 """
39 This thread worker runs a Zeroconf service
40 """
41 address = None
42 http_port = 4316
43 ws_port = 4317
44 _can_run = False
45
46 def __init__(self, ip_address, http_port=4316, ws_port=4317):
47 """
48 Create the worker for the Zeroconf service
49 """
50 super().__init__()
51 self.address = socket.inet_aton(ip_address)
52 self.http_port = http_port
53 self.ws_port = ws_port
54
55 def can_run(self):
56 """
57 Check if the worker can continue to run. This is mostly so that we can override this method
58 and test the class.
59 """
60 return self._can_run
61
62 def start(self):
63 """
64 Start the service
65 """
66 http_info = ServiceInfo('_http._tcp.local.', 'OpenLP._http._tcp.local.',
67 address=self.address, port=self.http_port, properties={})
68 ws_info = ServiceInfo('_ws._tcp.local.', 'OpenLP._ws._tcp.local.',
69 address=self.address, port=self.ws_port, properties={})
70 zc = Zeroconf()
71 zc.register_service(http_info)
72 zc.register_service(ws_info)
73 self._can_run = True
74 while self.can_run():
75 sleep(0.1)
76 zc.unregister_service(http_info)
77 zc.unregister_service(ws_info)
78 zc.close()
79 self.quit.emit()
80
81 def stop(self):
82 """
83 Stop the service
84 """
85 self._can_run = False
86
87
88def start_zeroconf():
89 """
90 Start the Zeroconf service
91 """
92 # When we're running tests, just skip this set up if this flag is set
93 if Registry().get_flag('no_web_server'):
94 return
95 http_port = Settings().value('api/port')
96 ws_port = Settings().value('api/websocket port')
97 for name, interface in get_network_interfaces().items():
98 worker = ZeroconfWorker(interface['ip'], http_port, ws_port)
99 run_thread(worker, 'api_zeroconf_{name}'.format(name=name))
0100
=== modified file 'openlp/core/common/__init__.py'
--- openlp/core/common/__init__.py 2019-06-05 04:53:18 +0000
+++ openlp/core/common/__init__.py 2019-07-03 06:34:48 +0000
@@ -51,9 +51,10 @@
51 '\u2013': '-', '\u2014': '-', '\v': '\n\n', '\f': '\n\n'})51 '\u2013': '-', '\u2014': '-', '\v': '\n\n', '\f': '\n\n'})
52NEW_LINE_REGEX = re.compile(r' ?(\r\n?|\n) ?')52NEW_LINE_REGEX = re.compile(r' ?(\r\n?|\n) ?')
53WHITESPACE_REGEX = re.compile(r'[ \t]+')53WHITESPACE_REGEX = re.compile(r'[ \t]+')
5454INTERFACE_FILTER = re.compile('lo|loopback|docker|tun', re.IGNORECASE)
5555
56def get_local_ip4():56
57def get_network_interfaces():
57 """58 """
58 Creates a dictionary of local IPv4 interfaces on local machine.59 Creates a dictionary of local IPv4 interfaces on local machine.
59 If no active interfaces available, returns a dict of localhost IPv4 information60 If no active interfaces available, returns a dict of localhost IPv4 information
@@ -61,43 +62,33 @@
61 :returns: Dict of interfaces62 :returns: Dict of interfaces
62 """63 """
63 log.debug('Getting local IPv4 interface(es) information')64 log.debug('Getting local IPv4 interface(es) information')
64 my_ip4 = {}65 interfaces = {}
65 for iface in QNetworkInterface.allInterfaces():66 for interface in QNetworkInterface.allInterfaces():
67 interface_name = interface.name()
68 if INTERFACE_FILTER.search(interface_name):
69 log.debug('Filtering out interfaces we don\'t care about: {name}'.format(name=interface_name))
70 continue
66 log.debug('Checking for isValid and flags == IsUP | IsRunning')71 log.debug('Checking for isValid and flags == IsUP | IsRunning')
67 if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):72 if not interface.isValid() or not (interface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
68 continue73 continue
69 log.debug('Checking address(es) protocol')74 log.debug('Checking address(es) protocol')
70 for address in iface.addressEntries():75 for address in interface.addressEntries():
71 ip = address.ip()76 ip = address.ip()
72 log.debug('Checking for protocol == IPv4Protocol')77 log.debug('Checking for protocol == IPv4Protocol')
73 if ip.protocol() == QAbstractSocket.IPv4Protocol:78 if ip.protocol() == QAbstractSocket.IPv4Protocol:
74 log.debug('Getting interface information')79 log.debug('Getting interface information')
75 my_ip4[iface.name()] = {'ip': ip.toString(),80 interfaces[interface_name] = {
76 'broadcast': address.broadcast().toString(),81 'ip': ip.toString(),
77 'netmask': address.netmask().toString(),82 'broadcast': address.broadcast().toString(),
78 'prefix': address.prefixLength(),83 'netmask': address.netmask().toString(),
79 'localnet': QHostAddress(address.netmask().toIPv4Address() &84 'prefix': address.prefixLength(),
80 ip.toIPv4Address()).toString()85 'localnet': QHostAddress(address.netmask().toIPv4Address() &
81 }86 ip.toIPv4Address()).toString()
82 log.debug('Adding {iface} to active list'.format(iface=iface.name()))87 }
83 if len(my_ip4) == 0:88 log.debug('Adding {interface} to active list'.format(interface=interface.name()))
89 if len(interfaces) == 0:
84 log.warning('No active IPv4 network interfaces detected')90 log.warning('No active IPv4 network interfaces detected')
85 return my_ip491 return interfaces
86 if 'localhost' in my_ip4:
87 log.debug('Renaming windows localhost to lo')
88 my_ip4['lo'] = my_ip4['localhost']
89 my_ip4.pop('localhost')
90 if len(my_ip4) == 1:
91 if 'lo' in my_ip4:
92 # No active interfaces - so leave localhost in there
93 log.warning('No active IPv4 interfaces found except localhost')
94 else:
95 # Since we have a valid IP4 interface, remove localhost
96 if 'lo' in my_ip4:
97 log.debug('Found at least one IPv4 interface, removing localhost')
98 my_ip4.pop('lo')
99
100 return my_ip4
10192
10293
103def trace_error_handler(logger):94def trace_error_handler(logger):
10495
=== modified file 'openlp/core/ui/mainwindow.py'
--- openlp/core/ui/mainwindow.py 2019-05-24 18:50:51 +0000
+++ openlp/core/ui/mainwindow.py 2019-07-03 06:34:48 +0000
@@ -33,8 +33,9 @@
33from PyQt5 import QtCore, QtGui, QtWidgets33from PyQt5 import QtCore, QtGui, QtWidgets
3434
35from openlp.core.state import State35from openlp.core.state import State
36from openlp.core.api import websockets36from openlp.core.api.websockets import WebSocketServer
37from openlp.core.api.http import server37from openlp.core.api.http.server import HttpServer
38from openlp.core.api.zeroconf import start_zeroconf
38from openlp.core.common import add_actions, is_macosx, is_win39from openlp.core.common import add_actions, is_macosx, is_win
39from openlp.core.common.actions import ActionList, CategoryOrder40from openlp.core.common.actions import ActionList, CategoryOrder
40from openlp.core.common.applocation import AppLocation41from openlp.core.common.applocation import AppLocation
@@ -495,8 +496,9 @@
495 self.copy_data = False496 self.copy_data = False
496 Settings().set_up_default_values()497 Settings().set_up_default_values()
497 self.about_form = AboutForm(self)498 self.about_form = AboutForm(self)
498 self.ws_server = websockets.WebSocketServer()499 self.ws_server = WebSocketServer()
499 self.http_server = server.HttpServer(self)500 self.http_server = HttpServer(self)
501 start_zeroconf()
500 SettingsForm(self)502 SettingsForm(self)
501 self.formatting_tag_form = FormattingTagForm(self)503 self.formatting_tag_form = FormattingTagForm(self)
502 self.shortcut_form = ShortcutListForm(self)504 self.shortcut_form = ShortcutListForm(self)
503505
=== modified file 'run_openlp.py'
--- run_openlp.py 2019-06-05 04:53:18 +0000
+++ run_openlp.py 2019-07-03 06:34:48 +0000
@@ -23,6 +23,7 @@
23"""23"""
24The entrypoint for OpenLP24The entrypoint for OpenLP
25"""25"""
26import atexit
26import faulthandler27import faulthandler
27import logging28import logging
28import multiprocessing29import multiprocessing
@@ -36,18 +37,33 @@
36from openlp.core.common.path import create_paths37from openlp.core.common.path import create_paths
3738
38log = logging.getLogger(__name__)39log = logging.getLogger(__name__)
40error_log_file = None
41
42
43def tear_down_fault_handling():
44 """
45 When Python exits, close the file we were using for the faulthandler
46 """
47 global error_log_file
48 error_log_file.close()
3949
4050
41def set_up_fault_handling():51def set_up_fault_handling():
42 """52 """
43 Set up the Python fault handler53 Set up the Python fault handler
44 """54 """
55 global error_log_file
45 # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file56 # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file
46 try:57 try:
47 create_paths(AppLocation.get_directory(AppLocation.CacheDir))58 create_paths(AppLocation.get_directory(AppLocation.CacheDir))
48 faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))59 error_log_file = (AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb')
60 atexit.register(tear_down_fault_handling)
61 faulthandler.enable(error_log_file)
49 except OSError:62 except OSError:
50 log.exception('An exception occurred when enabling the fault handler')63 log.exception('An exception occurred when enabling the fault handler')
64 atexit.unregister(tear_down_fault_handling)
65 if error_log_file:
66 error_log_file.close()
5167
5268
53def start():69def start():
5470
=== modified file 'scripts/appveyor.yml'
--- scripts/appveyor.yml 2019-06-11 19:27:17 +0000
+++ scripts/appveyor.yml 2019-07-03 06:34:48 +0000
@@ -18,7 +18,7 @@
1818
19install:19install:
20 # Install dependencies from pypi20 # Install dependencies from pypi
21 - "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python pytest mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 PyQtWebEngine pymediainfo PyMuPDF QDarkStyle python-vlc Pyro4"21 - "%PYTHON%\\python.exe -m pip install sqlalchemy alembic appdirs chardet beautifulsoup4 lxml Mako mysql-connector-python pytest mock pyodbc psycopg2 pypiwin32 websockets asyncio waitress six webob requests QtAwesome PyQt5 PyQtWebEngine pymediainfo PyMuPDF QDarkStyle python-vlc Pyro4 zeroconf"
2222
23build: off23build: off
2424
2525
=== modified file 'scripts/check_dependencies.py'
--- scripts/check_dependencies.py 2019-06-11 05:01:02 +0000
+++ scripts/check_dependencies.py 2019-07-03 06:34:48 +0000
@@ -90,7 +90,8 @@
90 'requests',90 'requests',
91 'qtawesome',91 'qtawesome',
92 'pymediainfo',92 'pymediainfo',
93 'vlc'93 'vlc',
94 'zeroconf'
94]95]
9596
9697
9798
=== modified file 'setup.py'
--- setup.py 2019-05-25 14:43:43 +0000
+++ setup.py 2019-07-03 06:34:48 +0000
@@ -185,7 +185,8 @@
185 'SQLAlchemy >= 0.5',185 'SQLAlchemy >= 0.5',
186 'waitress',186 'waitress',
187 'WebOb',187 'WebOb',
188 'websockets'188 'websockets',
189 'zeroconf'
189 ],190 ],
190 extras_require={191 extras_require={
191 'agpl-pdf': ['PyMuPDF'],192 'agpl-pdf': ['PyMuPDF'],
192193
=== modified file 'tests/functional/openlp_core/api/test_tab.py'
--- tests/functional/openlp_core/api/test_tab.py 2019-04-13 13:00:22 +0000
+++ tests/functional/openlp_core/api/test_tab.py 2019-07-03 06:34:48 +0000
@@ -28,7 +28,7 @@
28from PyQt5 import QtWidgets28from PyQt5 import QtWidgets
2929
30from openlp.core.api.tab import ApiTab30from openlp.core.api.tab import ApiTab
31from openlp.core.common import get_local_ip431from openlp.core.common import get_network_interfaces
32from openlp.core.common.registry import Registry32from openlp.core.common.registry import Registry
33from openlp.core.common.settings import Settings33from openlp.core.common.settings import Settings
34from tests.helpers.testmixin import TestMixin34from tests.helpers.testmixin import TestMixin
@@ -62,7 +62,7 @@
62 Registry().create()62 Registry().create()
63 Registry().set_flag('website_version', '00-00-0000')63 Registry().set_flag('website_version', '00-00-0000')
64 self.form = ApiTab(self.parent)64 self.form = ApiTab(self.parent)
65 self.my_ip4_list = get_local_ip4()65 self.interfaces = get_network_interfaces()
6666
67 def tearDown(self):67 def tearDown(self):
68 """68 """
@@ -77,9 +77,9 @@
77 Test the get_ip_address function with ZERO_URL77 Test the get_ip_address function with ZERO_URL
78 """78 """
79 # GIVEN: list of local IP addresses for this machine79 # GIVEN: list of local IP addresses for this machine
80 ip4_list = []80 ip_addresses = []
81 for ip4 in iter(self.my_ip4_list):81 for _, interface in self.interfaces.items():
82 ip4_list.append(self.my_ip4_list.get(ip4)['ip'])82 ip_addresses.append(interface['ip'])
8383
84 # WHEN: the default ip address is given84 # WHEN: the default ip address is given
85 ip_address = self.form.get_ip_address(ZERO_URL)85 ip_address = self.form.get_ip_address(ZERO_URL)
@@ -87,7 +87,7 @@
87 # THEN: the default ip address will be returned87 # THEN: the default ip address will be returned
88 assert re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \88 assert re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \
89 'The return value should be a valid ip address'89 'The return value should be a valid ip address'
90 assert ip_address in ip4_list, 'The return address should be in the list of local IP addresses'90 assert ip_address in ip_addresses, 'The return address should be in the list of local IP addresses'
9191
92 def test_get_ip_address_with_ip(self):92 def test_get_ip_address_with_ip(self):
93 """93 """
9494
=== modified file 'tests/functional/openlp_core/common/test_json.py'
--- tests/functional/openlp_core/common/test_json.py 2019-05-22 06:47:00 +0000
+++ tests/functional/openlp_core/common/test_json.py 2019-07-03 06:34:48 +0000
@@ -31,7 +31,7 @@
31from openlp.core.common.json import JSONMixin, OpenLPJSONDecoder, OpenLPJSONEncoder, PathSerializer, _registered_classes31from openlp.core.common.json import JSONMixin, OpenLPJSONDecoder, OpenLPJSONEncoder, PathSerializer, _registered_classes
3232
3333
34class TestClassBase(object):34class BaseTestClass(object):
35 """35 """
36 Simple class to avoid repetition36 Simple class to avoid repetition
37 """37 """
@@ -81,7 +81,7 @@
81 Test that an instance of a JSONMixin subclass is properly serialized to a JSON string81 Test that an instance of a JSONMixin subclass is properly serialized to a JSON string
82 """82 """
83 # GIVEN: A instance of a subclass of the JSONMixin class83 # GIVEN: A instance of a subclass of the JSONMixin class
84 class TestClass(TestClassBase, JSONMixin):84 class TestClass(BaseTestClass, JSONMixin):
85 _json_keys = ['a', 'b']85 _json_keys = ['a', 'b']
8686
87 instance = TestClass(a=1, c=2)87 instance = TestClass(a=1, c=2)
@@ -97,7 +97,7 @@
97 Test that an instance of a JSONMixin subclass is properly deserialized from a JSON string97 Test that an instance of a JSONMixin subclass is properly deserialized from a JSON string
98 """98 """
99 # GIVEN: A subclass of the JSONMixin class99 # GIVEN: A subclass of the JSONMixin class
100 class TestClass(TestClassBase, JSONMixin):100 class TestClass(BaseTestClass, JSONMixin):
101 _json_keys = ['a', 'b']101 _json_keys = ['a', 'b']
102102
103 # WHEN: Deserializing a JSON representation of the TestClass103 # WHEN: Deserializing a JSON representation of the TestClass
@@ -115,7 +115,7 @@
115 Test that an instance of a JSONMixin subclass is properly serialized to a JSON string when using a custom name115 Test that an instance of a JSONMixin subclass is properly serialized to a JSON string when using a custom name
116 """116 """
117 # GIVEN: A instance of a subclass of the JSONMixin class with a custom name117 # GIVEN: A instance of a subclass of the JSONMixin class with a custom name
118 class TestClass(TestClassBase, JSONMixin, register_names=('AltName', )):118 class TestClass(BaseTestClass, JSONMixin, register_names=('AltName', )):
119 _json_keys = ['a', 'b']119 _json_keys = ['a', 'b']
120 _name = 'AltName'120 _name = 'AltName'
121 _version = 2121 _version = 2
@@ -134,7 +134,7 @@
134 name134 name
135 """135 """
136 # GIVEN: A instance of a subclass of the JSONMixin class with a custom name136 # GIVEN: A instance of a subclass of the JSONMixin class with a custom name
137 class TestClass(TestClassBase, JSONMixin, register_names=('AltName', )):137 class TestClass(BaseTestClass, JSONMixin, register_names=('AltName', )):
138 _json_keys = ['a', 'b']138 _json_keys = ['a', 'b']
139 _name = 'AltName'139 _name = 'AltName'
140 _version = 2140 _version = 2
141141
=== modified file 'tests/interfaces/openlp_core/ui/test_mainwindow.py'
--- tests/interfaces/openlp_core/ui/test_mainwindow.py 2019-04-13 13:00:22 +0000
+++ tests/interfaces/openlp_core/ui/test_mainwindow.py 2019-07-03 06:34:48 +0000
@@ -62,9 +62,10 @@
62 patch('openlp.core.ui.mainwindow.ServiceManager'), \62 patch('openlp.core.ui.mainwindow.ServiceManager'), \
63 patch('openlp.core.ui.mainwindow.ThemeManager'), \63 patch('openlp.core.ui.mainwindow.ThemeManager'), \
64 patch('openlp.core.ui.mainwindow.ProjectorManager'), \64 patch('openlp.core.ui.mainwindow.ProjectorManager'), \
65 patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \65 patch('openlp.core.ui.mainwindow.HttpServer'), \
66 patch('openlp.core.ui.mainwindow.PluginForm'), \66 patch('openlp.core.ui.mainwindow.WebSocketServer'), \
67 patch('openlp.core.ui.mainwindow.server.HttpServer'):67 patch('openlp.core.ui.mainwindow.start_zeroconf'), \
68 patch('openlp.core.ui.mainwindow.PluginForm'):
68 self.main_window = MainWindow()69 self.main_window = MainWindow()
6970
70 def tearDown(self):71 def tearDown(self):
7172
=== added directory 'tests/openlp_core/api'
=== added file 'tests/openlp_core/api/test_zeroconf.py'
--- tests/openlp_core/api/test_zeroconf.py 1970-01-01 00:00:00 +0000
+++ tests/openlp_core/api/test_zeroconf.py 2019-07-03 06:34:48 +0000
@@ -0,0 +1,112 @@
1# -*- coding: utf-8 -*-
2# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
3
4##########################################################################
5# OpenLP - Open Source Lyrics Projection #
6# ---------------------------------------------------------------------- #
7# Copyright (c) 2008-2019 OpenLP Developers #
8# ---------------------------------------------------------------------- #
9# This program is free software: you can redistribute it and/or modify #
10# it under the terms of the GNU General Public License as published by #
11# the Free Software Foundation, either version 3 of the License, or #
12# (at your option) any later version. #
13# #
14# This program is distributed in the hope that it will be useful, #
15# but WITHOUT ANY WARRANTY; without even the implied warranty of #
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
17# GNU General Public License for more details. #
18# #
19# You should have received a copy of the GNU General Public License #
20# along with this program. If not, see <https://www.gnu.org/licenses/>. #
21##########################################################################
22from unittest.mock import MagicMock, call, patch
23
24from openlp.core.api.zeroconf import ZeroconfWorker, start_zeroconf
25
26
27@patch('openlp.core.api.zeroconf.socket.inet_aton')
28def test_zeroconf_worker_constructor(mocked_inet_aton):
29 """Test creating the Zeroconf worker object"""
30 # GIVEN: A ZeroconfWorker class and a mocked inet_aton
31 mocked_inet_aton.return_value = 'processed_ip'
32
33 # WHEN: An instance of the ZeroconfWorker is created
34 worker = ZeroconfWorker('127.0.0.1', 8000, 8001)
35
36 # THEN: The inet_aton function should have been called and the attrs should be set
37 mocked_inet_aton.assert_called_once_with('127.0.0.1')
38 assert worker.address == 'processed_ip'
39 assert worker.http_port == 8000
40 assert worker.ws_port == 8001
41
42
43@patch('openlp.core.api.zeroconf.ServiceInfo')
44@patch('openlp.core.api.zeroconf.Zeroconf')
45def test_zeroconf_worker_start(MockedZeroconf, MockedServiceInfo):
46 """Test the start() method of ZeroconfWorker"""
47 # GIVEN: A few mocks and a ZeroconfWorker instance with a mocked can_run method
48 mocked_http_info = MagicMock()
49 mocked_ws_info = MagicMock()
50 mocked_zc = MagicMock()
51 MockedServiceInfo.side_effect = [mocked_http_info, mocked_ws_info]
52 MockedZeroconf.return_value = mocked_zc
53 worker = ZeroconfWorker('127.0.0.1', 8000, 8001)
54
55 # WHEN: The start() method is called
56 with patch.object(worker, 'can_run') as mocked_can_run:
57 mocked_can_run.side_effect = [True, False]
58 worker.start()
59
60 # THEN: The correct calls are made
61 assert MockedServiceInfo.call_args_list == [
62 call('_http._tcp.local.', 'OpenLP._http._tcp.local.', address=b'\x7f\x00\x00\x01', port=8000, properties={}),
63 call('_ws._tcp.local.', 'OpenLP._ws._tcp.local.', address=b'\x7f\x00\x00\x01', port=8001, properties={})
64 ]
65 assert MockedZeroconf.call_count == 1
66 assert mocked_zc.register_service.call_args_list == [call(mocked_http_info), call(mocked_ws_info)]
67 assert mocked_can_run.call_count == 2
68 assert mocked_zc.unregister_service.call_args_list == [call(mocked_http_info), call(mocked_ws_info)]
69 assert mocked_zc.close.call_count == 1
70
71
72def test_zeroconf_worker_stop():
73 """Test that the ZeroconfWorker.stop() method correctly stops the service"""
74 # GIVEN: A worker object with _can_run set to True
75 worker = ZeroconfWorker('127.0.0.1', 8000, 8001)
76 worker._can_run = True
77
78 # WHEN: stop() is called
79 worker.stop()
80
81 # THEN: _can_run should be False
82 assert worker._can_run is False
83
84
85@patch('openlp.core.api.zeroconf.get_network_interfaces')
86@patch('openlp.core.api.zeroconf.Registry')
87@patch('openlp.core.api.zeroconf.Settings')
88@patch('openlp.core.api.zeroconf.ZeroconfWorker')
89@patch('openlp.core.api.zeroconf.run_thread')
90def test_start_zeroconf(mocked_run_thread, MockedZeroconfWorker, MockedSettings, MockedRegistry,
91 mocked_get_network_interfaces):
92 """Test the start_zeroconf() function"""
93 # GIVEN: A whole bunch of stuff that's mocked out
94 mocked_get_network_interfaces.return_value = {
95 'eth0': {
96 'ip': '192.168.1.191',
97 'broadcast': '192.168.1.255',
98 'netmask': '255.255.255.0',
99 'prefix': 24,
100 'localnet': '192.168.1.0'
101 }
102 }
103 MockedRegistry.return_value.get_flag.return_value = False
104 MockedSettings.return_value.value.side_effect = [8000, 8001]
105 mocked_worker = MagicMock()
106 MockedZeroconfWorker.return_value = mocked_worker
107
108 # WHEN: start_zeroconf() is called
109 start_zeroconf()
110
111 # THEN: A worker is added to the list of threads
112 mocked_run_thread.assert_called_once_with(mocked_worker, 'api_zeroconf_eth0')
0113
=== modified file 'tests/openlp_core/common/test_network_interfaces.py'
--- tests/openlp_core/common/test_network_interfaces.py 2019-04-13 13:00:22 +0000
+++ tests/openlp_core/common/test_network_interfaces.py 2019-07-03 06:34:48 +0000
@@ -28,7 +28,7 @@
28from PyQt5.QtNetwork import QHostAddress, QNetworkAddressEntry, QNetworkInterface28from PyQt5.QtNetwork import QHostAddress, QNetworkAddressEntry, QNetworkInterface
2929
30import openlp.core.common30import openlp.core.common
31from openlp.core.common import get_local_ip431from openlp.core.common import get_network_interfaces
32from tests.helpers.testmixin import TestMixin32from tests.helpers.testmixin import TestMixin
3333
3434
@@ -101,7 +101,7 @@
101 self.destroy_settings()101 self.destroy_settings()
102102
103 @patch.object(openlp.core.common, 'log')103 @patch.object(openlp.core.common, 'log')
104 def test_ip4_no_interfaces(self, mock_log):104 def test_network_interfaces_no_interfaces(self, mock_log):
105 """105 """
106 Test no interfaces available106 Test no interfaces available
107 """107 """
@@ -109,115 +109,101 @@
109 call_debug = [call('Getting local IPv4 interface(es) information')]109 call_debug = [call('Getting local IPv4 interface(es) information')]
110 call_warning = [call('No active IPv4 network interfaces detected')]110 call_warning = [call('No active IPv4 network interfaces detected')]
111111
112 # WHEN: get_local_ip4 is called112 # WHEN: get_network_interfaces() is called
113 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:113 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
114 mock_network_interface.allInterfaces.return_value = []114 mock_network_interface.allInterfaces.return_value = []
115 ifaces = get_local_ip4()115 interfaces = get_network_interfaces()
116116
117 # THEN: There should not be any interfaces detected117 # THEN: There should not be any interfaces detected
118 mock_log.debug.assert_has_calls(call_debug)118 mock_log.debug.assert_has_calls(call_debug)
119 mock_log.warning.assert_has_calls(call_warning)119 mock_log.warning.assert_has_calls(call_warning)
120 assert not ifaces, 'There should have been no active interfaces listed'120 assert not interfaces, 'There should have been no active interfaces listed'
121121
122 @patch.object(openlp.core.common, 'log')122 @patch.object(openlp.core.common, 'log')
123 def test_ip4_lo(self, mock_log):123 def test_network_interfaces_lo(self, mock_log):
124 """124 """
125 Test get_local_ip4 returns proper dictionary with 'lo'125 Test get_network_interfaces() returns an empty dictionary if "lo" is the only network interface
126 """126 """
127 # GIVEN: Test environment127 # GIVEN: Test environment
128 call_debug = [call('Getting local IPv4 interface(es) information'),128 call_debug = [
129 call('Checking for isValid and flags == IsUP | IsRunning'),129 call('Getting local IPv4 interface(es) information'),
130 call('Checking address(es) protocol'),130 call("Filtering out interfaces we don't care about: lo")
131 call('Checking for protocol == IPv4Protocol'),131 ]
132 call('Getting interface information'),
133 call('Adding lo to active list')]
134 call_warning = [call('No active IPv4 interfaces found except localhost')]
135132
136 # WHEN: get_local_ip4 is called133 # WHEN: get_network_interfaces() is called
137 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:134 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
138 mock_network_interface.allInterfaces.return_value = [self.fake_lo]135 mock_network_interface.allInterfaces.return_value = [self.fake_lo]
139 ifaces = get_local_ip4()136 interfaces = get_network_interfaces()
140137
141 # THEN: There should be a fake 'lo' interface138 # THEN: There should be no interfaces
142 mock_log.debug.assert_has_calls(call_debug)139 mock_log.debug.assert_has_calls(call_debug)
143 mock_log.warning.assert_has_calls(call_warning)140 assert interfaces == {}, 'There should be no interfaces listed'
144 assert ifaces == self.fake_lo.fake_data, "There should have been an 'lo' interface listed"
145141
146 @patch.object(openlp.core.common, 'log')142 @patch.object(openlp.core.common, 'log')
147 def test_ip4_localhost(self, mock_log):143 def test_network_interfaces_localhost(self, mock_log):
148 """144 """
149 Test get_local_ip4 returns proper dictionary with 'lo' if interface is 'localhost'145 Test get_network_interfaces() returns an empty dictionary if "localhost" is the only network interface
150 """146 """
151 # GIVEN: Test environment147 # GIVEN: Test environment
152 call_debug = [call('Getting local IPv4 interface(es) information'),148 call_debug = [
153 call('Checking for isValid and flags == IsUP | IsRunning'),149 call('Getting local IPv4 interface(es) information'),
154 call('Checking address(es) protocol'),150 call("Filtering out interfaces we don't care about: localhost")
155 call('Checking for protocol == IPv4Protocol'),151 ]
156 call('Getting interface information'),
157 call('Adding localhost to active list'),
158 call('Renaming windows localhost to lo')]
159 call_warning = [call('No active IPv4 interfaces found except localhost')]
160152
161 # WHEN: get_local_ip4 is called153 # WHEN: get_network_interfaces() is called
162 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:154 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
163 mock_network_interface.allInterfaces.return_value = [self.fake_localhost]155 mock_network_interface.allInterfaces.return_value = [self.fake_localhost]
164 ifaces = get_local_ip4()156 interfaces = get_network_interfaces()
165157
166 # THEN: There should be a fake 'lo' interface158 # THEN: There should be no interfaces
167 mock_log.debug.assert_has_calls(call_debug)159 mock_log.debug.assert_has_calls(call_debug)
168 mock_log.warning.assert_has_calls(call_warning)160 assert interfaces == {}, 'There should be no interfaces listed'
169 assert ifaces == self.fake_lo.fake_data, "There should have been an 'lo' interface listed"
170161
171 @patch.object(openlp.core.common, 'log')162 @patch.object(openlp.core.common, 'log')
172 def test_ip4_eth25(self, mock_log):163 def test_network_interfaces_eth25(self, mock_log):
173 """164 """
174 Test get_local_ip4 returns proper dictionary with 'eth25'165 Test get_network_interfaces() returns proper dictionary with 'eth25'
175 """166 """
176 # GIVEN: Test environment167 # GIVEN: Test environment
177 call_debug = [call('Getting local IPv4 interface(es) information'),168 call_debug = [
178 call('Checking for isValid and flags == IsUP | IsRunning'),169 call('Getting local IPv4 interface(es) information'),
179 call('Checking address(es) protocol'),170 call('Checking for isValid and flags == IsUP | IsRunning'),
180 call('Checking for protocol == IPv4Protocol'),171 call('Checking address(es) protocol'),
181 call('Getting interface information'),172 call('Checking for protocol == IPv4Protocol'),
182 call('Adding eth25 to active list')]173 call('Getting interface information'),
183 call_warning = []174 call('Adding eth25 to active list')
175 ]
184176
185 # WHEN: get_local_ip4 is called177 # WHEN: get_network_interfaces() is called
186 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:178 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
187 mock_network_interface.allInterfaces.return_value = [self.fake_address]179 mock_network_interface.allInterfaces.return_value = [self.fake_address]
188 ifaces = get_local_ip4()180 interfaces = get_network_interfaces()
189181
190 # THEN: There should be a fake 'eth25' interface182 # THEN: There should be a fake 'eth25' interface
191 mock_log.debug.assert_has_calls(call_debug)183 mock_log.debug.assert_has_calls(call_debug)
192 mock_log.warning.assert_has_calls(call_warning)184 assert interfaces == self.fake_address.fake_data
193 assert ifaces == self.fake_address.fake_data
194185
195 @patch.object(openlp.core.common, 'log')186 @patch.object(openlp.core.common, 'log')
196 def test_ip4_lo_eth25(self, mock_log):187 def test_network_interfaces_lo_eth25(self, mock_log):
197 """188 """
198 Test get_local_ip4 returns proper dictionary with 'eth25'189 Test get_network_interfaces() returns proper dictionary with 'eth25'
199 """190 """
200 # GIVEN: Test environment191 # GIVEN: Test environment
201 call_debug = [call('Getting local IPv4 interface(es) information'),192 call_debug = [
202 call('Checking for isValid and flags == IsUP | IsRunning'),193 call('Getting local IPv4 interface(es) information'),
203 call('Checking address(es) protocol'),194 call("Filtering out interfaces we don't care about: lo"),
204 call('Checking for protocol == IPv4Protocol'),195 call('Checking for isValid and flags == IsUP | IsRunning'),
205 call('Getting interface information'),196 call('Checking address(es) protocol'),
206 call('Adding lo to active list'),197 call('Checking for protocol == IPv4Protocol'),
207 call('Checking for isValid and flags == IsUP | IsRunning'),198 call('Getting interface information'),
208 call('Checking address(es) protocol'),199 call('Adding eth25 to active list')
209 call('Checking for protocol == IPv4Protocol'),200 ]
210 call('Getting interface information'),
211 call('Adding eth25 to active list'),
212 call('Found at least one IPv4 interface, removing localhost')]
213 call_warning = []
214201
215 # WHEN: get_local_ip4 is called202 # WHEN: get_network_interfaces() is called
216 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:203 with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
217 mock_network_interface.allInterfaces.return_value = [self.fake_lo, self.fake_address]204 mock_network_interface.allInterfaces.return_value = [self.fake_lo, self.fake_address]
218 ifaces = get_local_ip4()205 interfaces = get_network_interfaces()
219206
220 # THEN: There should be a fake 'eth25' interface207 # THEN: There should be a fake 'eth25' interface
221 mock_log.debug.assert_has_calls(call_debug)208 mock_log.debug.assert_has_calls(call_debug)
222 mock_log.warning.assert_has_calls(call_warning)209 assert interfaces == self.fake_address.fake_data, "There should have been only 'eth25' interface listed"
223 assert ifaces == self.fake_address.fake_data, "There should have been only 'eth25' interface listed"
224210
=== modified file 'tests/openlp_core/projectors/test_projector_db.py'
--- tests/openlp_core/projectors/test_projector_db.py 2019-04-13 13:00:22 +0000
+++ tests/openlp_core/projectors/test_projector_db.py 2019-07-03 06:34:48 +0000
@@ -153,8 +153,9 @@
153 patch('openlp.core.ui.mainwindow.ServiceManager'), \153 patch('openlp.core.ui.mainwindow.ServiceManager'), \
154 patch('openlp.core.ui.mainwindow.ThemeManager'), \154 patch('openlp.core.ui.mainwindow.ThemeManager'), \
155 patch('openlp.core.ui.mainwindow.ProjectorManager'), \155 patch('openlp.core.ui.mainwindow.ProjectorManager'), \
156 patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \156 patch('openlp.core.ui.mainwindow.WebSocketServer'), \
157 patch('openlp.core.ui.mainwindow.server.HttpServer'), \157 patch('openlp.core.ui.mainwindow.HttpServer'), \
158 patch('openlp.core.ui.mainwindow.start_zeroconf'), \
158 patch('openlp.core.state.State.list_plugins') as mock_plugins:159 patch('openlp.core.state.State.list_plugins') as mock_plugins:
159 mock_plugins.return_value = []160 mock_plugins.return_value = []
160 self.main_window = MainWindow()161 self.main_window = MainWindow()