← Back to team overview

openlp-core team mailing list archive

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

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/zeroconf into lp:openlp.

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

Requested reviews:
  Tomas Groth (tomasgroth)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/zeroconf/+merge/369632

Add Zeroconf services to OpenLP so that external devices can find OpenLP on the network.
-- 
Your team OpenLP Core is subscribed to branch lp:openlp.
=== 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 @@
 """
 from PyQt5 import QtCore, QtGui, QtWidgets
 
-from openlp.core.common import get_local_ip4
+from openlp.core.common import get_network_interfaces
 from openlp.core.common.i18n import UiStrings, translate
 from openlp.core.common.registry import Registry
 from openlp.core.common.settings import Settings
@@ -194,8 +194,7 @@
         http_url_temp = http_url + 'main'
         self.live_url.setText('<a href="{url}">{url}</a>'.format(url=http_url_temp))
 
-    @staticmethod
-    def get_ip_address(ip_address):
+    def get_ip_address(self, ip_address):
         """
         returns the IP address in dependency of the passed address
         ip_address == 0.0.0.0: return the IP address of the first valid interface
@@ -203,9 +202,8 @@
         """
         if ip_address == ZERO_URL:
             # In case we have more than one interface
-            ifaces = get_local_ip4()
-            for key in iter(ifaces):
-                ip_address = ifaces.get(key)['ip']
+            for _, interface in get_network_interfaces().items():
+                ip_address = interface['ip']
                 # We only want the first interface returned
                 break
         return ip_address

=== 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 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection                                 #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 OpenLP Developers                              #
+# ---------------------------------------------------------------------- #
+# This program is free software: you can redistribute it and/or modify   #
+# it under the terms of the GNU General Public License as published by   #
+# the Free Software Foundation, either version 3 of the License, or      #
+# (at your option) any later version.                                    #
+#                                                                        #
+# This program is distributed in the hope that it will be useful,        #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of         #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          #
+# GNU General Public License for more details.                           #
+#                                                                        #
+# You should have received a copy of the GNU General Public License      #
+# along with this program.  If not, see <https://www.gnu.org/licenses/>. #
+##########################################################################
+"""
+The :mod:`~openlp.core.api.zeroconf` module runs a Zerconf server so that OpenLP can advertise the
+RESTful API for devices on the network to discover.
+"""
+import socket
+from time import sleep
+
+from zeroconf import ServiceInfo, Zeroconf
+
+from openlp.core.common import get_network_interfaces
+from openlp.core.common.registry import Registry
+from openlp.core.common.settings import Settings
+from openlp.core.threading import ThreadWorker, run_thread
+
+
+class ZeroconfWorker(ThreadWorker):
+    """
+    This thread worker runs a Zeroconf service
+    """
+    address = None
+    http_port = 4316
+    ws_port = 4317
+    _can_run = False
+
+    def __init__(self, ip_address, http_port=4316, ws_port=4317):
+        """
+        Create the worker for the Zeroconf service
+        """
+        super().__init__()
+        self.address = socket.inet_aton(ip_address)
+        self.http_port = http_port
+        self.ws_port = ws_port
+
+    def can_run(self):
+        """
+        Check if the worker can continue to run. This is mostly so that we can override this method
+        and test the class.
+        """
+        return self._can_run
+
+    def start(self):
+        """
+        Start the service
+        """
+        http_info = ServiceInfo('_http._tcp.local.', 'OpenLP._http._tcp.local.',
+                                address=self.address, port=self.http_port, properties={})
+        ws_info = ServiceInfo('_ws._tcp.local.', 'OpenLP._ws._tcp.local.',
+                              address=self.address, port=self.ws_port, properties={})
+        zc = Zeroconf()
+        zc.register_service(http_info)
+        zc.register_service(ws_info)
+        self._can_run = True
+        while self.can_run():
+            sleep(0.1)
+        zc.unregister_service(http_info)
+        zc.unregister_service(ws_info)
+        zc.close()
+        self.quit.emit()
+
+    def stop(self):
+        """
+        Stop the service
+        """
+        self._can_run = False
+
+
+def start_zeroconf():
+    """
+    Start the Zeroconf service
+    """
+    # When we're running tests, just skip this set up if this flag is set
+    if Registry().get_flag('no_web_server'):
+        return
+    http_port = Settings().value('api/port')
+    ws_port = Settings().value('api/websocket port')
+    for name, interface in get_network_interfaces().items():
+        worker = ZeroconfWorker(interface['ip'], http_port, ws_port)
+        run_thread(worker, 'api_zeroconf_{name}'.format(name=name))

=== 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 @@
                                       '\u2013': '-', '\u2014': '-', '\v': '\n\n', '\f': '\n\n'})
 NEW_LINE_REGEX = re.compile(r' ?(\r\n?|\n) ?')
 WHITESPACE_REGEX = re.compile(r'[ \t]+')
-
-
-def get_local_ip4():
+INTERFACE_FILTER = re.compile('lo|loopback|docker|tun', re.IGNORECASE)
+
+
+def get_network_interfaces():
     """
     Creates a dictionary of local IPv4 interfaces on local machine.
     If no active interfaces available, returns a dict of localhost IPv4 information
@@ -61,43 +62,33 @@
     :returns: Dict of interfaces
     """
     log.debug('Getting local IPv4 interface(es) information')
-    my_ip4 = {}
-    for iface in QNetworkInterface.allInterfaces():
+    interfaces = {}
+    for interface in QNetworkInterface.allInterfaces():
+        interface_name = interface.name()
+        if INTERFACE_FILTER.search(interface_name):
+            log.debug('Filtering out interfaces we don\'t care about: {name}'.format(name=interface_name))
+            continue
         log.debug('Checking for isValid and flags == IsUP | IsRunning')
-        if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
+        if not interface.isValid() or not (interface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
             continue
         log.debug('Checking address(es) protocol')
-        for address in iface.addressEntries():
+        for address in interface.addressEntries():
             ip = address.ip()
             log.debug('Checking for protocol == IPv4Protocol')
             if ip.protocol() == QAbstractSocket.IPv4Protocol:
                 log.debug('Getting interface information')
-                my_ip4[iface.name()] = {'ip': ip.toString(),
-                                        'broadcast': address.broadcast().toString(),
-                                        'netmask': address.netmask().toString(),
-                                        'prefix': address.prefixLength(),
-                                        'localnet': QHostAddress(address.netmask().toIPv4Address() &
-                                                                 ip.toIPv4Address()).toString()
-                                        }
-                log.debug('Adding {iface} to active list'.format(iface=iface.name()))
-    if len(my_ip4) == 0:
+                interfaces[interface_name] = {
+                    'ip': ip.toString(),
+                    'broadcast': address.broadcast().toString(),
+                    'netmask': address.netmask().toString(),
+                    'prefix': address.prefixLength(),
+                    'localnet': QHostAddress(address.netmask().toIPv4Address() &
+                                             ip.toIPv4Address()).toString()
+                }
+                log.debug('Adding {interface} to active list'.format(interface=interface.name()))
+    if len(interfaces) == 0:
         log.warning('No active IPv4 network interfaces detected')
-        return my_ip4
-    if 'localhost' in my_ip4:
-        log.debug('Renaming windows localhost to lo')
-        my_ip4['lo'] = my_ip4['localhost']
-        my_ip4.pop('localhost')
-    if len(my_ip4) == 1:
-        if 'lo' in my_ip4:
-            # No active interfaces - so leave localhost in there
-            log.warning('No active IPv4 interfaces found except localhost')
-    else:
-        # Since we have a valid IP4 interface, remove localhost
-        if 'lo' in my_ip4:
-            log.debug('Found at least one IPv4 interface, removing localhost')
-            my_ip4.pop('lo')
-
-    return my_ip4
+    return interfaces
 
 
 def trace_error_handler(logger):

=== 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 @@
 from PyQt5 import QtCore, QtGui, QtWidgets
 
 from openlp.core.state import State
-from openlp.core.api import websockets
-from openlp.core.api.http import server
+from openlp.core.api.websockets import WebSocketServer
+from openlp.core.api.http.server import HttpServer
+from openlp.core.api.zeroconf import start_zeroconf
 from openlp.core.common import add_actions, is_macosx, is_win
 from openlp.core.common.actions import ActionList, CategoryOrder
 from openlp.core.common.applocation import AppLocation
@@ -495,8 +496,9 @@
         self.copy_data = False
         Settings().set_up_default_values()
         self.about_form = AboutForm(self)
-        self.ws_server = websockets.WebSocketServer()
-        self.http_server = server.HttpServer(self)
+        self.ws_server = WebSocketServer()
+        self.http_server = HttpServer(self)
+        start_zeroconf()
         SettingsForm(self)
         self.formatting_tag_form = FormattingTagForm(self)
         self.shortcut_form = ShortcutListForm(self)

=== 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 @@
 """
 The entrypoint for OpenLP
 """
+import atexit
 import faulthandler
 import logging
 import multiprocessing
@@ -36,18 +37,33 @@
 from openlp.core.common.path import create_paths
 
 log = logging.getLogger(__name__)
+error_log_file = None
+
+
+def tear_down_fault_handling():
+    """
+    When Python exits, close the file we were using for the faulthandler
+    """
+    global error_log_file
+    error_log_file.close()
 
 
 def set_up_fault_handling():
     """
     Set up the Python fault handler
     """
+    global error_log_file
     # Create the cache directory if it doesn't exist, and enable the fault handler to log to an error log file
     try:
         create_paths(AppLocation.get_directory(AppLocation.CacheDir))
-        faulthandler.enable((AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb'))
+        error_log_file = (AppLocation.get_directory(AppLocation.CacheDir) / 'error.log').open('wb')
+        atexit.register(tear_down_fault_handling)
+        faulthandler.enable(error_log_file)
     except OSError:
         log.exception('An exception occurred when enabling the fault handler')
+        atexit.unregister(tear_down_fault_handling)
+        if error_log_file:
+            error_log_file.close()
 
 
 def start():

=== 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 @@
 
 install:
   # Install dependencies from pypi
-  - "%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"
+  - "%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"
 
 build: off
 

=== 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 @@
     'requests',
     'qtawesome',
     'pymediainfo',
-    'vlc'
+    'vlc',
+    'zeroconf'
 ]
 
 

=== 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 @@
         'SQLAlchemy >= 0.5',
         'waitress',
         'WebOb',
-        'websockets'
+        'websockets',
+        'zeroconf'
     ],
     extras_require={
         'agpl-pdf': ['PyMuPDF'],

=== 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 @@
 from PyQt5 import QtWidgets
 
 from openlp.core.api.tab import ApiTab
-from openlp.core.common import get_local_ip4
+from openlp.core.common import get_network_interfaces
 from openlp.core.common.registry import Registry
 from openlp.core.common.settings import Settings
 from tests.helpers.testmixin import TestMixin
@@ -62,7 +62,7 @@
         Registry().create()
         Registry().set_flag('website_version', '00-00-0000')
         self.form = ApiTab(self.parent)
-        self.my_ip4_list = get_local_ip4()
+        self.interfaces = get_network_interfaces()
 
     def tearDown(self):
         """
@@ -77,9 +77,9 @@
         Test the get_ip_address function with ZERO_URL
         """
         # GIVEN: list of local IP addresses for this machine
-        ip4_list = []
-        for ip4 in iter(self.my_ip4_list):
-            ip4_list.append(self.my_ip4_list.get(ip4)['ip'])
+        ip_addresses = []
+        for _, interface in self.interfaces.items():
+            ip_addresses.append(interface['ip'])
 
         # WHEN: the default ip address is given
         ip_address = self.form.get_ip_address(ZERO_URL)
@@ -87,7 +87,7 @@
         # THEN: the default ip address will be returned
         assert re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \
             'The return value should be a valid ip address'
-        assert ip_address in ip4_list, 'The return address should be in the list of local IP addresses'
+        assert ip_address in ip_addresses, 'The return address should be in the list of local IP addresses'
 
     def test_get_ip_address_with_ip(self):
         """

=== 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 @@
 from openlp.core.common.json import JSONMixin, OpenLPJSONDecoder, OpenLPJSONEncoder, PathSerializer, _registered_classes
 
 
-class TestClassBase(object):
+class BaseTestClass(object):
     """
     Simple class to avoid repetition
     """
@@ -81,7 +81,7 @@
         Test that an instance of a JSONMixin subclass is properly serialized to a JSON string
         """
         # GIVEN: A instance of a subclass of the JSONMixin class
-        class TestClass(TestClassBase, JSONMixin):
+        class TestClass(BaseTestClass, JSONMixin):
             _json_keys = ['a', 'b']
 
         instance = TestClass(a=1, c=2)
@@ -97,7 +97,7 @@
         Test that an instance of a JSONMixin subclass is properly deserialized from a JSON string
         """
         # GIVEN: A subclass of the JSONMixin class
-        class TestClass(TestClassBase, JSONMixin):
+        class TestClass(BaseTestClass, JSONMixin):
             _json_keys = ['a', 'b']
 
         # WHEN: Deserializing a JSON representation of the TestClass
@@ -115,7 +115,7 @@
         Test that an instance of a JSONMixin subclass is properly serialized to a JSON string when using a custom name
         """
         # GIVEN: A instance of a subclass of the JSONMixin class with a custom name
-        class TestClass(TestClassBase, JSONMixin, register_names=('AltName', )):
+        class TestClass(BaseTestClass, JSONMixin, register_names=('AltName', )):
             _json_keys = ['a', 'b']
             _name = 'AltName'
             _version = 2
@@ -134,7 +134,7 @@
         name
         """
         # GIVEN: A instance of a subclass of the JSONMixin class with a custom name
-        class TestClass(TestClassBase, JSONMixin, register_names=('AltName', )):
+        class TestClass(BaseTestClass, JSONMixin, register_names=('AltName', )):
             _json_keys = ['a', 'b']
             _name = 'AltName'
             _version = 2

=== 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 @@
                 patch('openlp.core.ui.mainwindow.ServiceManager'), \
                 patch('openlp.core.ui.mainwindow.ThemeManager'), \
                 patch('openlp.core.ui.mainwindow.ProjectorManager'), \
-                patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \
-                patch('openlp.core.ui.mainwindow.PluginForm'), \
-                patch('openlp.core.ui.mainwindow.server.HttpServer'):
+                patch('openlp.core.ui.mainwindow.HttpServer'), \
+                patch('openlp.core.ui.mainwindow.WebSocketServer'), \
+                patch('openlp.core.ui.mainwindow.start_zeroconf'), \
+                patch('openlp.core.ui.mainwindow.PluginForm'):
             self.main_window = MainWindow()
 
     def tearDown(self):

=== 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 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection                                 #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 OpenLP Developers                              #
+# ---------------------------------------------------------------------- #
+# This program is free software: you can redistribute it and/or modify   #
+# it under the terms of the GNU General Public License as published by   #
+# the Free Software Foundation, either version 3 of the License, or      #
+# (at your option) any later version.                                    #
+#                                                                        #
+# This program is distributed in the hope that it will be useful,        #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of         #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          #
+# GNU General Public License for more details.                           #
+#                                                                        #
+# You should have received a copy of the GNU General Public License      #
+# along with this program.  If not, see <https://www.gnu.org/licenses/>. #
+##########################################################################
+from unittest.mock import MagicMock, call, patch
+
+from openlp.core.api.zeroconf import ZeroconfWorker, start_zeroconf
+
+
+@patch('openlp.core.api.zeroconf.socket.inet_aton')
+def test_zeroconf_worker_constructor(mocked_inet_aton):
+    """Test creating the Zeroconf worker object"""
+    # GIVEN: A ZeroconfWorker class and a mocked inet_aton
+    mocked_inet_aton.return_value = 'processed_ip'
+
+    # WHEN: An instance of the ZeroconfWorker is created
+    worker = ZeroconfWorker('127.0.0.1', 8000, 8001)
+
+    # THEN: The inet_aton function should have been called and the attrs should be set
+    mocked_inet_aton.assert_called_once_with('127.0.0.1')
+    assert worker.address == 'processed_ip'
+    assert worker.http_port == 8000
+    assert worker.ws_port == 8001
+
+
+@patch('openlp.core.api.zeroconf.ServiceInfo')
+@patch('openlp.core.api.zeroconf.Zeroconf')
+def test_zeroconf_worker_start(MockedZeroconf, MockedServiceInfo):
+    """Test the start() method of ZeroconfWorker"""
+    # GIVEN: A few mocks and a ZeroconfWorker instance with a mocked can_run method
+    mocked_http_info = MagicMock()
+    mocked_ws_info = MagicMock()
+    mocked_zc = MagicMock()
+    MockedServiceInfo.side_effect = [mocked_http_info, mocked_ws_info]
+    MockedZeroconf.return_value = mocked_zc
+    worker = ZeroconfWorker('127.0.0.1', 8000, 8001)
+
+    # WHEN: The start() method is called
+    with patch.object(worker, 'can_run') as mocked_can_run:
+        mocked_can_run.side_effect = [True, False]
+        worker.start()
+
+    # THEN: The correct calls are made
+    assert MockedServiceInfo.call_args_list == [
+        call('_http._tcp.local.', 'OpenLP._http._tcp.local.', address=b'\x7f\x00\x00\x01', port=8000, properties={}),
+        call('_ws._tcp.local.', 'OpenLP._ws._tcp.local.', address=b'\x7f\x00\x00\x01', port=8001, properties={})
+    ]
+    assert MockedZeroconf.call_count == 1
+    assert mocked_zc.register_service.call_args_list == [call(mocked_http_info), call(mocked_ws_info)]
+    assert mocked_can_run.call_count == 2
+    assert mocked_zc.unregister_service.call_args_list == [call(mocked_http_info), call(mocked_ws_info)]
+    assert mocked_zc.close.call_count == 1
+
+
+def test_zeroconf_worker_stop():
+    """Test that the ZeroconfWorker.stop() method correctly stops the service"""
+    # GIVEN: A worker object with _can_run set to True
+    worker = ZeroconfWorker('127.0.0.1', 8000, 8001)
+    worker._can_run = True
+
+    # WHEN: stop() is called
+    worker.stop()
+
+    # THEN: _can_run should be False
+    assert worker._can_run is False
+
+
+@patch('openlp.core.api.zeroconf.get_network_interfaces')
+@patch('openlp.core.api.zeroconf.Registry')
+@patch('openlp.core.api.zeroconf.Settings')
+@patch('openlp.core.api.zeroconf.ZeroconfWorker')
+@patch('openlp.core.api.zeroconf.run_thread')
+def test_start_zeroconf(mocked_run_thread, MockedZeroconfWorker, MockedSettings, MockedRegistry,
+                        mocked_get_network_interfaces):
+    """Test the start_zeroconf() function"""
+    # GIVEN: A whole bunch of stuff that's mocked out
+    mocked_get_network_interfaces.return_value = {
+        'eth0': {
+            'ip': '192.168.1.191',
+            'broadcast': '192.168.1.255',
+            'netmask': '255.255.255.0',
+            'prefix': 24,
+            'localnet': '192.168.1.0'
+        }
+    }
+    MockedRegistry.return_value.get_flag.return_value = False
+    MockedSettings.return_value.value.side_effect = [8000, 8001]
+    mocked_worker = MagicMock()
+    MockedZeroconfWorker.return_value = mocked_worker
+
+    # WHEN: start_zeroconf() is called
+    start_zeroconf()
+
+    # THEN: A worker is added to the list of threads
+    mocked_run_thread.assert_called_once_with(mocked_worker, 'api_zeroconf_eth0')

=== 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 @@
 from PyQt5.QtNetwork import QHostAddress, QNetworkAddressEntry, QNetworkInterface
 
 import openlp.core.common
-from openlp.core.common import get_local_ip4
+from openlp.core.common import get_network_interfaces
 from tests.helpers.testmixin import TestMixin
 
 
@@ -101,7 +101,7 @@
         self.destroy_settings()
 
     @patch.object(openlp.core.common, 'log')
-    def test_ip4_no_interfaces(self, mock_log):
+    def test_network_interfaces_no_interfaces(self, mock_log):
         """
         Test no interfaces available
         """
@@ -109,115 +109,101 @@
         call_debug = [call('Getting local IPv4 interface(es) information')]
         call_warning = [call('No active IPv4 network interfaces detected')]
 
-        # WHEN: get_local_ip4 is called
+        # WHEN: get_network_interfaces() is called
         with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
             mock_network_interface.allInterfaces.return_value = []
-            ifaces = get_local_ip4()
+            interfaces = get_network_interfaces()
 
         # THEN: There should not be any interfaces detected
         mock_log.debug.assert_has_calls(call_debug)
         mock_log.warning.assert_has_calls(call_warning)
-        assert not ifaces, 'There should have been no active interfaces listed'
+        assert not interfaces, 'There should have been no active interfaces listed'
 
     @patch.object(openlp.core.common, 'log')
-    def test_ip4_lo(self, mock_log):
+    def test_network_interfaces_lo(self, mock_log):
         """
-        Test get_local_ip4 returns proper dictionary with 'lo'
+        Test get_network_interfaces() returns an empty dictionary if "lo" is the only network interface
         """
         # GIVEN: Test environment
-        call_debug = [call('Getting local IPv4 interface(es) information'),
-                      call('Checking for isValid and flags == IsUP | IsRunning'),
-                      call('Checking address(es) protocol'),
-                      call('Checking for protocol == IPv4Protocol'),
-                      call('Getting interface information'),
-                      call('Adding lo to active list')]
-        call_warning = [call('No active IPv4 interfaces found except localhost')]
+        call_debug = [
+            call('Getting local IPv4 interface(es) information'),
+            call("Filtering out interfaces we don't care about: lo")
+        ]
 
-        # WHEN: get_local_ip4 is called
+        # WHEN: get_network_interfaces() is called
         with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
             mock_network_interface.allInterfaces.return_value = [self.fake_lo]
-            ifaces = get_local_ip4()
+            interfaces = get_network_interfaces()
 
-        # THEN: There should be a fake 'lo' interface
+        # THEN: There should be no interfaces
         mock_log.debug.assert_has_calls(call_debug)
-        mock_log.warning.assert_has_calls(call_warning)
-        assert ifaces == self.fake_lo.fake_data, "There should have been an 'lo' interface listed"
+        assert interfaces == {}, 'There should be no interfaces listed'
 
     @patch.object(openlp.core.common, 'log')
-    def test_ip4_localhost(self, mock_log):
+    def test_network_interfaces_localhost(self, mock_log):
         """
-        Test get_local_ip4 returns proper dictionary with 'lo' if interface is 'localhost'
+        Test get_network_interfaces() returns an empty dictionary if "localhost" is the only network interface
         """
         # GIVEN: Test environment
-        call_debug = [call('Getting local IPv4 interface(es) information'),
-                      call('Checking for isValid and flags == IsUP | IsRunning'),
-                      call('Checking address(es) protocol'),
-                      call('Checking for protocol == IPv4Protocol'),
-                      call('Getting interface information'),
-                      call('Adding localhost to active list'),
-                      call('Renaming windows localhost to lo')]
-        call_warning = [call('No active IPv4 interfaces found except localhost')]
+        call_debug = [
+            call('Getting local IPv4 interface(es) information'),
+            call("Filtering out interfaces we don't care about: localhost")
+        ]
 
-        # WHEN: get_local_ip4 is called
+        # WHEN: get_network_interfaces() is called
         with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
             mock_network_interface.allInterfaces.return_value = [self.fake_localhost]
-            ifaces = get_local_ip4()
+            interfaces = get_network_interfaces()
 
-        # THEN: There should be a fake 'lo' interface
+        # THEN: There should be no interfaces
         mock_log.debug.assert_has_calls(call_debug)
-        mock_log.warning.assert_has_calls(call_warning)
-        assert ifaces == self.fake_lo.fake_data, "There should have been an 'lo' interface listed"
+        assert interfaces == {}, 'There should be no interfaces listed'
 
     @patch.object(openlp.core.common, 'log')
-    def test_ip4_eth25(self, mock_log):
+    def test_network_interfaces_eth25(self, mock_log):
         """
-        Test get_local_ip4 returns proper dictionary with 'eth25'
+        Test get_network_interfaces() returns proper dictionary with 'eth25'
         """
         # GIVEN: Test environment
-        call_debug = [call('Getting local IPv4 interface(es) information'),
-                      call('Checking for isValid and flags == IsUP | IsRunning'),
-                      call('Checking address(es) protocol'),
-                      call('Checking for protocol == IPv4Protocol'),
-                      call('Getting interface information'),
-                      call('Adding eth25 to active list')]
-        call_warning = []
+        call_debug = [
+            call('Getting local IPv4 interface(es) information'),
+            call('Checking for isValid and flags == IsUP | IsRunning'),
+            call('Checking address(es) protocol'),
+            call('Checking for protocol == IPv4Protocol'),
+            call('Getting interface information'),
+            call('Adding eth25 to active list')
+        ]
 
-        # WHEN: get_local_ip4 is called
+        # WHEN: get_network_interfaces() is called
         with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
             mock_network_interface.allInterfaces.return_value = [self.fake_address]
-            ifaces = get_local_ip4()
+            interfaces = get_network_interfaces()
 
         # THEN: There should be a fake 'eth25' interface
         mock_log.debug.assert_has_calls(call_debug)
-        mock_log.warning.assert_has_calls(call_warning)
-        assert ifaces == self.fake_address.fake_data
+        assert interfaces == self.fake_address.fake_data
 
     @patch.object(openlp.core.common, 'log')
-    def test_ip4_lo_eth25(self, mock_log):
+    def test_network_interfaces_lo_eth25(self, mock_log):
         """
-        Test get_local_ip4 returns proper dictionary with 'eth25'
+        Test get_network_interfaces() returns proper dictionary with 'eth25'
         """
         # GIVEN: Test environment
-        call_debug = [call('Getting local IPv4 interface(es) information'),
-                      call('Checking for isValid and flags == IsUP | IsRunning'),
-                      call('Checking address(es) protocol'),
-                      call('Checking for protocol == IPv4Protocol'),
-                      call('Getting interface information'),
-                      call('Adding lo to active list'),
-                      call('Checking for isValid and flags == IsUP | IsRunning'),
-                      call('Checking address(es) protocol'),
-                      call('Checking for protocol == IPv4Protocol'),
-                      call('Getting interface information'),
-                      call('Adding eth25 to active list'),
-                      call('Found at least one IPv4 interface, removing localhost')]
-        call_warning = []
+        call_debug = [
+            call('Getting local IPv4 interface(es) information'),
+            call("Filtering out interfaces we don't care about: lo"),
+            call('Checking for isValid and flags == IsUP | IsRunning'),
+            call('Checking address(es) protocol'),
+            call('Checking for protocol == IPv4Protocol'),
+            call('Getting interface information'),
+            call('Adding eth25 to active list')
+        ]
 
-        # WHEN: get_local_ip4 is called
+        # WHEN: get_network_interfaces() is called
         with patch('openlp.core.common.QNetworkInterface') as mock_network_interface:
             mock_network_interface.allInterfaces.return_value = [self.fake_lo, self.fake_address]
-            ifaces = get_local_ip4()
+            interfaces = get_network_interfaces()
 
         # THEN: There should be a fake 'eth25' interface
         mock_log.debug.assert_has_calls(call_debug)
-        mock_log.warning.assert_has_calls(call_warning)
-        assert ifaces == self.fake_address.fake_data, "There should have been only 'eth25' interface listed"
+        assert interfaces == self.fake_address.fake_data, "There should have been only 'eth25' interface listed"

=== 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 @@
                 patch('openlp.core.ui.mainwindow.ServiceManager'), \
                 patch('openlp.core.ui.mainwindow.ThemeManager'), \
                 patch('openlp.core.ui.mainwindow.ProjectorManager'), \
-                patch('openlp.core.ui.mainwindow.websockets.WebSocketServer'), \
-                patch('openlp.core.ui.mainwindow.server.HttpServer'), \
+                patch('openlp.core.ui.mainwindow.WebSocketServer'), \
+                patch('openlp.core.ui.mainwindow.HttpServer'), \
+                patch('openlp.core.ui.mainwindow.start_zeroconf'), \
                 patch('openlp.core.state.State.list_plugins') as mock_plugins:
             mock_plugins.return_value = []
             self.main_window = MainWindow()


Follow ups