Merge lp:~alisonken1/openlp/pjlink2-p into lp:openlp

Proposed by Ken Roberts
Status: Merged
Merged at revision: 2813
Proposed branch: lp:~alisonken1/openlp/pjlink2-p
Merge into: lp:openlp
Diff against target: 852 lines (+595/-40)
10 files modified
openlp/core/api/tab.py (+7/-11)
openlp/core/common/__init__.py (+39/-0)
openlp/core/projectors/manager.py (+2/-5)
openlp/core/projectors/pjlink.py (+143/-18)
tests/functional/openlp_core/api/test_tab.py (+11/-0)
tests/openlp_core/__init__.py (+26/-0)
tests/openlp_core/projectors/__init__.py (+1/-1)
tests/openlp_core/projectors/test_projector_pjlink_udp.py (+360/-0)
tests/openlp_core/projectors/test_projectorsourceform.py (+0/-2)
tests/resources/projector/data.py (+6/-3)
To merge this branch: bzr merge lp:~alisonken1/openlp/pjlink2-p
Reviewer Review Type Date Requested Status
Tomas Groth Approve
Tim Bentley Approve
Raoul Snyman Pending
Review via email: mp+337519@code.launchpad.net

This proposal supersedes a proposal from 2018-02-11.

Commit message

PJLink2 Update P

Description of the change

- manager: Remove unused signal disconnect projectorNetwork.disconnect()
- Change PJLinkUDP.pjlink_udp_commands to dict with link to processing methods
- Add test_projector_pjlink_udp.test_process_ackn_duplicate
- Add test_projector_pjlink_udp.test_process_ackn_multiple
- Add test_projector_pjlink_udp.test_process_ackn_single
- Add test_projector_pjlink_udp.test_process_srch
- Add PJLinkUDP.get_datagram method
- Add PJLinkUDP._trash_udp_buffer method
- Add PJLinkUDP.process_ackn method
- Add PJLinkUDP.process_srch method
- Move projector tests to tests/openlp_core/projectors/
- Update resources.projector.data.TESTx_DATA to add mac_adx
- Added dictionary of IP interfaces/addresses to core.common
- Refactor tab.ApiTab.get_ip_address() to use MY_IP4 dict
- Fix tests for api.tab.ApiTab.get_ip_address()
- Move dictionary of IP interfaces to function

--------------------------------------------------------------------------------
lp:~alisonken1/openlp/pjlink2-p (revision 2811)
https://ci.openlp.io/job/Branch-01-Pull/2446/ [SUCCESS]
https://ci.openlp.io/job/Branch-02a-Linux-Tests/2347/ [SUCCESS]
https://ci.openlp.io/job/Branch-02b-macOS-Tests/141/ [FAILURE]

Passed local tests so not sure what happened now

To post a comment you must log in.
Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

One question and a request to refactor.

review: Needs Fixing
Revision history for this message
Ken Roberts (alisonken1) wrote : Posted in a previous version of this proposal

>> === modified file 'openlp/.version'
>> --- openlp/.version 2016-12-12 22:16:23 +0000
>> +++ openlp/.version 2018-02-10 09:09:49 +0000
>> @@ -1 +1 @@
>> -2.5.0
>> +2.5-bzr2809
>
> why?

huh?
Didn't know that happened. Must have been when I was twiddling with
pip install trying something out.
Reverted

Revision history for this message
Tim Bentley (trb143) wrote : Posted in a previous version of this proposal

Looks ok and a good refactor.
Not 100% sure about the inline code for the Network lookup.
Let TGC or Superfly comment on that.

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

Just the one request, everything else looks spiffy.

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) :
review: Approve
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 2017-12-29 09:15:48 +0000
+++ openlp/core/api/tab.py 2018-02-12 01:05:51 +0000
@@ -24,6 +24,7 @@
24"""24"""
25from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets25from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
2626
27from openlp.core.common import get_local_ip4
27from openlp.core.common.i18n import UiStrings, translate28from openlp.core.common.i18n import UiStrings, translate
28from openlp.core.common.registry import Registry29from openlp.core.common.registry import Registry
29from openlp.core.common.settings import Settings30from openlp.core.common.settings import Settings
@@ -219,17 +220,12 @@
219 else: return ip_address220 else: return ip_address
220 """221 """
221 if ip_address == ZERO_URL:222 if ip_address == ZERO_URL:
222 interfaces = QtNetwork.QNetworkInterface.allInterfaces()223 # In case we have more than one interface
223 for interface in interfaces:224 ifaces = get_local_ip4()
224 if not interface.isValid():225 for key in iter(ifaces):
225 continue226 ip_address = ifaces.get(key)['ip']
226 if not (interface.flags() & (QtNetwork.QNetworkInterface.IsUp | QtNetwork.QNetworkInterface.IsRunning)):227 # We only want the first interface returned
227 continue228 break
228 for address in interface.addressEntries():
229 ip = address.ip()
230 if ip.protocol() == QtNetwork.QAbstractSocket.IPv4Protocol and \
231 ip != QtNetwork.QHostAddress.LocalHost:
232 return ip.toString()
233 return ip_address229 return ip_address
234230
235 def load(self):231 def load(self):
236232
=== modified file 'openlp/core/common/__init__.py'
--- openlp/core/common/__init__.py 2017-12-29 09:15:48 +0000
+++ openlp/core/common/__init__.py 2018-02-12 01:05:51 +0000
@@ -36,6 +36,7 @@
3636
37from PyQt5 import QtGui37from PyQt5 import QtGui
38from PyQt5.QtCore import QCryptographicHash as QHash38from PyQt5.QtCore import QCryptographicHash as QHash
39from PyQt5.QtNetwork import QAbstractSocket, QHostAddress, QNetworkInterface
39from chardet.universaldetector import UniversalDetector40from chardet.universaldetector import UniversalDetector
4041
41log = logging.getLogger(__name__ + '.__init__')42log = logging.getLogger(__name__ + '.__init__')
@@ -52,6 +53,44 @@
52WHITESPACE_REGEX = re.compile(r'[ \t]+')53WHITESPACE_REGEX = re.compile(r'[ \t]+')
5354
5455
56def get_local_ip4():
57 """
58 Creates a dictionary of local IPv4 interfaces on local machine.
59 If no active interfaces available, returns a dict of localhost IPv4 information
60
61 :returns: Dict of interfaces
62 """
63 # Get the local IPv4 active address(es) that are NOT localhost (lo or '127.0.0.1')
64 log.debug('Getting local IPv4 interface(es) information')
65 MY_IP4 = {}
66 for iface in QNetworkInterface.allInterfaces():
67 if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
68 continue
69 for address in iface.addressEntries():
70 ip = address.ip()
71 # NOTE: Next line will skip if interface is localhost - keep for now until we decide about it later
72 # if (ip.protocol() == QAbstractSocket.IPv4Protocol) and (ip != QHostAddress.LocalHost):
73 if (ip.protocol() == QAbstractSocket.IPv4Protocol):
74 MY_IP4[iface.name()] = {'ip': ip.toString(),
75 'broadcast': address.broadcast().toString(),
76 'netmask': address.netmask().toString(),
77 'prefix': address.prefixLength(),
78 'localnet': QHostAddress(address.netmask().toIPv4Address() &
79 ip.toIPv4Address()).toString()
80 }
81 log.debug('Adding {iface} to active list'.format(iface=iface.name()))
82 if len(MY_IP4) == 1:
83 if 'lo' in MY_IP4:
84 # No active interfaces - so leave localhost in there
85 log.warning('No active IPv4 interfaces found except localhost')
86 else:
87 # Since we have a valid IP4 interface, remove localhost
88 log.debug('Found at least one IPv4 interface, removing localhost')
89 MY_IP4.pop('lo')
90
91 return MY_IP4
92
93
55def trace_error_handler(logger):94def trace_error_handler(logger):
56 """95 """
57 Log the calling path of an exception96 Log the calling path of an exception
5897
=== modified file 'openlp/core/projectors/manager.py'
--- openlp/core/projectors/manager.py 2018-01-13 05:41:42 +0000
+++ openlp/core/projectors/manager.py 2018-02-12 01:05:51 +0000
@@ -308,7 +308,6 @@
308 self.settings_section = 'projector'308 self.settings_section = 'projector'
309 self.projectordb = projectordb309 self.projectordb = projectordb
310 self.projector_list = []310 self.projector_list = []
311 self.pjlink_udp = PJLinkUDP(self.projector_list)
312 self.source_select_form = None311 self.source_select_form = None
313312
314 def bootstrap_initialise(self):313 def bootstrap_initialise(self):
@@ -323,6 +322,7 @@
323 else:322 else:
324 log.debug('Using existing ProjectorDB() instance')323 log.debug('Using existing ProjectorDB() instance')
325 self.get_settings()324 self.get_settings()
325 self.pjlink_udp = PJLinkUDP(self.projector_list)
326326
327 def bootstrap_post_set_up(self):327 def bootstrap_post_set_up(self):
328 """328 """
@@ -344,6 +344,7 @@
344 """344 """
345 Retrieve the saved settings345 Retrieve the saved settings
346 """346 """
347 log.debug('Updating ProjectorManager settings')
347 settings = Settings()348 settings = Settings()
348 settings.beginGroup(self.settings_section)349 settings.beginGroup(self.settings_section)
349 self.autostart = settings.value('connect on start')350 self.autostart = settings.value('connect on start')
@@ -502,10 +503,6 @@
502 if ans == msg.Cancel:503 if ans == msg.Cancel:
503 return504 return
504 try:505 try:
505 projector.link.projectorNetwork.disconnect(self.update_status)
506 except (AttributeError, TypeError):
507 pass
508 try:
509 projector.link.changeStatus.disconnect(self.update_status)506 projector.link.changeStatus.disconnect(self.update_status)
510 except (AttributeError, TypeError):507 except (AttributeError, TypeError):
511 pass508 pass
512509
=== modified file 'openlp/core/projectors/pjlink.py'
--- openlp/core/projectors/pjlink.py 2018-01-13 05:41:42 +0000
+++ openlp/core/projectors/pjlink.py 2018-02-12 01:05:51 +0000
@@ -64,7 +64,7 @@
64log = logging.getLogger(__name__)64log = logging.getLogger(__name__)
65log.debug('pjlink loaded')65log.debug('pjlink loaded')
6666
67__all__ = ['PJLink']67__all__ = ['PJLink', 'PJLinkUDP']
6868
69# Shortcuts69# Shortcuts
70SocketError = QtNetwork.QAbstractSocket.SocketError70SocketError = QtNetwork.QAbstractSocket.SocketError
@@ -79,22 +79,145 @@
79 """79 """
80 Socket service for PJLink UDP socket.80 Socket service for PJLink UDP socket.
81 """81 """
82 # New commands available in PJLink Class 2
83 pjlink_udp_commands = [
84 'ACKN', # Class 2 (cmd is SRCH)
85 'ERST', # Class 1/2
86 'INPT', # Class 1/2
87 'LKUP', # Class 2 (reply only - no cmd)
88 'POWR', # Class 1/2
89 'SRCH' # Class 2 (reply is ACKN)
90 ]
91
92 def __init__(self, projector_list, port=PJLINK_PORT):82 def __init__(self, projector_list, port=PJLINK_PORT):
93 """83 """
94 Initialize socket84 Socket services for PJLink UDP packets.
85
86 Since all UDP packets from any projector will come into the same
87 port, process UDP packets here then route to the appropriate
88 projector instance as needed.
95 """89 """
90 # Keep track of currently defined projectors so we can route
91 # inbound packets to the correct instance
92 super().__init__()
96 self.projector_list = projector_list93 self.projector_list = projector_list
97 self.port = port94 self.port = port
95 # Local defines
96 self.ackn_list = {} # Replies from online projetors
97 self.search_active = False
98 self.search_time = 30000 # 30 seconds for allowed time
99 self.search_timer = QtCore.QTimer()
100 # New commands available in PJLink Class 2
101 # ACKN/SRCH is processed here since it's used to find available projectors
102 # Other commands are processed by the individual projector instances
103 self.pjlink_udp_functions = {
104 'ACKN': self.process_ackn, # Class 2, command is 'SRCH'
105 'ERST': None, # Class 1/2
106 'INPT': None, # Class 1/2
107 'LKUP': None, # Class 2 (reply only - no cmd)
108 'POWR': None, # Class 1/2
109 'SRCH': self.process_srch # Class 2 (reply is ACKN)
110 }
111
112 self.readyRead.connect(self.get_datagram)
113 log.debug('(UDP) PJLinkUDP() Initialized')
114
115 @QtCore.pyqtSlot()
116 def get_datagram(self):
117 """
118 Retrieve packet and basic checks
119 """
120 log.debug('(UDP) get_datagram() - Receiving data')
121 read = self.pendingDatagramSize()
122 if read < 0:
123 log.warn('(UDP) No data (-1)')
124 return
125 if read < 1:
126 log.warn('(UDP) get_datagram() called when pending data size is 0')
127 return
128 data, peer_address, peer_port = self.readDatagram(self.pendingDatagramSize())
129 log.debug('(UDP) {size} bytes received from {adx} on port {port}'.format(size=len(data),
130 adx=peer_address,
131 port=peer_port))
132 log.debug('(UDP) packet "{data}"'.format(data=data))
133 if len(data) < 0:
134 log.warn('(UDP) No data (-1)')
135 return
136 elif len(data) < 8:
137 # Minimum packet is '%2CCCC='
138 log.warn('(UDP) Invalid packet - not enough data')
139 return
140 elif data is None:
141 log.warn('(UDP) No data (None)')
142 return
143 elif len(data) > PJLINK_MAX_PACKET:
144 log.warn('(UDP) Invalid packet - length too long')
145 return
146 elif not data.startswith(PJLINK_PREFIX):
147 log.warn('(UDP) Invalid packet - does not start with PJLINK_PREFIX')
148 return
149 elif data[1] != '2':
150 log.warn('(UDP) Invalid packet - missing/invalid PJLink class version')
151 return
152 elif data[6] != '=':
153 log.warn('(UDP) Invalid packet - separator missing')
154 return
155 # First two characters are header information we don't need at this time
156 cmd, data = data[2:].split('=')
157 if cmd not in self.pjlink_udp_functions:
158 log.warn('(UDP) Invalid packet - not a valid PJLink UDP reply')
159 return
160 if self.pjlink_udp_functions[cmd] is not None:
161 log.debug('(UDP) Processing {cmd} with "{data}"'.format(cmd=cmd, data=data))
162 return self.pjlink_udp_functions[cmd](data=data, host=peer_address, port=peer_port)
163 else:
164 log.debug('(UDP) Checking projector list for ip {host} to process'.format(host=peer_address))
165 for projector in self.projector_list:
166 if peer_address == projector.ip:
167 if cmd not in projector.pjlink_functions:
168 log.error('(UDP) Could not find method to process '
169 '"{cmd}" in {host}'.format(cmd=cmd, host=projector.ip))
170 return
171 log.debug('(UDP) Calling "{cmd}" in {host}'.format(cmd=cmd, host=projector.ip))
172 return projector.pjlink_functions[cmd](data=data)
173 log.warn('(UDP) Could not find projector with ip {ip} to process packet'.format(ip=peer_address))
174 return
175
176 def process_ackn(self, data, host, port):
177 """
178 Process the ACKN command.
179
180 :param data: Data in packet
181 :param host: IP address of sending host
182 :param port: Port received on
183 """
184 log.debug('(UDP) Processing ACKN packet')
185 if host not in self.ackn_list:
186 log.debug('(UDP) Adding {host} to ACKN list'.format(host=host))
187 self.ackn_list[host] = {'data': data,
188 'port': port}
189 else:
190 log.warn('(UDP) Host {host} already replied - ignoring'.format(host=host))
191
192 def process_srch(self, data, host, port):
193 """
194 Process the SRCH command.
195
196 SRCH is processed by terminals so we ignore any packet.
197
198 :param data: Data in packet
199 :param host: IP address of sending host
200 :param port: Port received on
201 """
202 log.debug('(UDP) SRCH packet received - ignoring')
203 return
204
205 def search_start(self):
206 """
207 Start search for projectors on local network
208 """
209 self.search_active = True
210 self.ackn_list = {}
211 # TODO: Send SRCH packet here
212 self.search_timer.singleShot(self.search_time, self.search_stop)
213
214 @QtCore.pyqtSlot()
215 def search_stop(self):
216 """
217 Stop search
218 """
219 self.search_active = False
220 self.search_timer.stop()
98221
99222
100class PJLinkCommands(object):223class PJLinkCommands(object):
@@ -257,8 +380,9 @@
257 else:380 else:
258 clss = data381 clss = data
259 self.pjlink_class = clss382 self.pjlink_class = clss
260 log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.entry.name,383 log.debug('({ip}) Setting pjlink_class for this projector '
261 data=self.pjlink_class))384 'to "{data}"'.format(ip=self.entry.name,
385 data=self.pjlink_class))
262 # Since we call this one on first connect, setup polling from here386 # Since we call this one on first connect, setup polling from here
263 if not self.no_poll:387 if not self.no_poll:
264 log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.entry.name))388 log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.entry.name))
@@ -276,9 +400,10 @@
276 """400 """
277 if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:401 if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:
278 count = PJLINK_ERST_DATA['DATA_LENGTH']402 count = PJLINK_ERST_DATA['DATA_LENGTH']
279 log.warning('({ip}) Invalid error status response "{data}": length != {count}'.format(ip=self.entry.name,403 log.warning('({ip}) Invalid error status response "{data}": '
280 data=data,404 'length != {count}'.format(ip=self.entry.name,
281 count=count))405 data=data,
406 count=count))
282 return407 return
283 try:408 try:
284 datacheck = int(data)409 datacheck = int(data)
@@ -557,7 +682,7 @@
557682
558class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):683class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
559 """684 """
560 Socket service for PJLink TCP socket.685 Socket services for PJLink TCP packets.
561 """686 """
562 # Signals sent by this module687 # Signals sent by this module
563 changeStatus = QtCore.pyqtSignal(str, int, str)688 changeStatus = QtCore.pyqtSignal(str, int, str)
564689
=== modified file 'tests/functional/openlp_core/api/test_tab.py'
--- tests/functional/openlp_core/api/test_tab.py 2017-12-29 09:15:48 +0000
+++ tests/functional/openlp_core/api/test_tab.py 2018-02-12 01:05:51 +0000
@@ -29,6 +29,7 @@
29from PyQt5 import QtWidgets29from PyQt5 import QtWidgets
3030
31from openlp.core.api.tab import ApiTab31from openlp.core.api.tab import ApiTab
32from openlp.core.common import get_local_ip4
32from openlp.core.common.registry import Registry33from openlp.core.common.registry import Registry
33from openlp.core.common.settings import Settings34from openlp.core.common.settings import Settings
34from tests.helpers.testmixin import TestMixin35from tests.helpers.testmixin import TestMixin
@@ -63,6 +64,7 @@
63 Registry().create()64 Registry().create()
64 Registry().set_flag('website_version', '00-00-0000')65 Registry().set_flag('website_version', '00-00-0000')
65 self.form = ApiTab(self.parent)66 self.form = ApiTab(self.parent)
67 self.my_ip4_list = get_local_ip4()
6668
67 def tearDown(self):69 def tearDown(self):
68 """70 """
@@ -76,11 +78,18 @@
76 """78 """
77 Test the get_ip_address function with ZERO_URL79 Test the get_ip_address function with ZERO_URL
78 """80 """
81 # GIVEN: list of local IP addresses for this machine
82 ip4_list = []
83 for ip4 in iter(self.my_ip4_list):
84 ip4_list.append(self.my_ip4_list.get(ip4)['ip'])
85
79 # WHEN: the default ip address is given86 # WHEN: the default ip address is given
80 ip_address = self.form.get_ip_address(ZERO_URL)87 ip_address = self.form.get_ip_address(ZERO_URL)
88
81 # THEN: the default ip address will be returned89 # THEN: the default ip address will be returned
82 assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \90 assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \
83 'The return value should be a valid ip address'91 'The return value should be a valid ip address'
92 assert ip_address in ip4_list, 'The return address should be in the list of local IP addresses'
8493
85 def test_get_ip_address_with_ip(self):94 def test_get_ip_address_with_ip(self):
86 """95 """
@@ -88,8 +97,10 @@
88 """97 """
89 # GIVEN: An ip address98 # GIVEN: An ip address
90 given_ip = '192.168.1.1'99 given_ip = '192.168.1.1'
100
91 # WHEN: the default ip address is given101 # WHEN: the default ip address is given
92 ip_address = self.form.get_ip_address(given_ip)102 ip_address = self.form.get_ip_address(given_ip)
103
93 # THEN: the default ip address will be returned104 # THEN: the default ip address will be returned
94 assert ip_address == given_ip, 'The return value should be %s' % given_ip105 assert ip_address == given_ip, 'The return value should be %s' % given_ip
95106
96107
=== added directory 'tests/openlp_core'
=== added file 'tests/openlp_core/__init__.py'
--- tests/openlp_core/__init__.py 1970-01-01 00:00:00 +0000
+++ tests/openlp_core/__init__.py 2018-02-12 01:05:51 +0000
@@ -0,0 +1,26 @@
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-2018 OpenLP Developers #
8# --------------------------------------------------------------------------- #
9# This program is free software; you can redistribute it and/or modify it #
10# under the terms of the GNU General Public License as published by the Free #
11# Software Foundation; version 2 of the License. #
12# #
13# This program is distributed in the hope that it will be useful, but WITHOUT #
14# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
15# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
16# more details. #
17# #
18# You should have received a copy of the GNU General Public License along #
19# with this program; if not, write to the Free Software Foundation, Inc., 59 #
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################
22"""
23:mod: `tests.openlp_core` module
24
25Tests modules/files for module openlp.core
26"""
027
=== renamed directory 'tests/functional/openlp_core/projectors' => 'tests/openlp_core/projectors'
=== modified file 'tests/openlp_core/projectors/__init__.py'
--- tests/functional/openlp_core/projectors/__init__.py 2017-11-10 11:59:38 +0000
+++ tests/openlp_core/projectors/__init__.py 2018-02-12 01:05:51 +0000
@@ -20,5 +20,5 @@
20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #20# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
21###############################################################################21###############################################################################
22"""22"""
23Module-level functions for the functional test suite23Module-level functions for the projector test suite
24"""24"""
2525
=== added file 'tests/openlp_core/projectors/test_projector_pjlink_udp.py'
--- tests/openlp_core/projectors/test_projector_pjlink_udp.py 1970-01-01 00:00:00 +0000
+++ tests/openlp_core/projectors/test_projector_pjlink_udp.py 2018-02-12 01:05:51 +0000
@@ -0,0 +1,360 @@
1
2# -*- coding: utf-8 -*-
3# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
4
5###############################################################################
6# OpenLP - Open Source Lyrics Projection #
7# --------------------------------------------------------------------------- #
8# Copyright (c) 2008-2018 OpenLP Developers #
9# --------------------------------------------------------------------------- #
10# This program is free software; you can redistribute it and/or modify it #
11# under the terms of the GNU General Public License as published by the Free #
12# Software Foundation; version 2 of the License. #
13# #
14# This program is distributed in the hope that it will be useful, but WITHOUT #
15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
17# more details. #
18# #
19# You should have received a copy of the GNU General Public License along #
20# with this program; if not, write to the Free Software Foundation, Inc., 59 #
21# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
22###############################################################################
23"""
24Package to test the PJLink UDP functions
25"""
26
27from unittest import TestCase
28from unittest.mock import call, patch
29
30import openlp.core.projectors.pjlink
31from openlp.core.projectors.constants import PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_PREFIX
32
33from openlp.core.projectors.db import Projector
34from openlp.core.projectors.pjlink import PJLinkUDP
35from tests.resources.projector.data import TEST1_DATA, TEST2_DATA
36
37
38class TestPJLinkBase(TestCase):
39 """
40 Tests for the PJLinkUDP class
41 """
42 def setUp(self):
43 """
44 Setup generic test conditions
45 """
46 self.test_list = [Projector(**TEST1_DATA), Projector(**TEST2_DATA)]
47
48 def tearDown(self):
49 """
50 Close generic test condidtions
51 """
52 self.test_list = None
53
54 @patch.object(openlp.core.projectors.pjlink, 'log')
55 def test_get_datagram_data_invalid_class(self, mock_log):
56 """
57 Test get_datagram with invalid class number
58 """
59 # GIVEN: Test setup
60 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
61 log_warn_calls = [call('(UDP) Invalid packet - missing/invalid PJLink class version')]
62 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
63 call('(UDP) get_datagram() - Receiving data'),
64 call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
65 call('(UDP) packet "%1ACKN=11:11:11:11:11:11"')]
66 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
67 patch.object(pjlink_udp, 'readDatagram') as mock_read:
68 mock_datagram.return_value = 24
69 mock_read.return_value = ('{prefix}1ACKN={mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
70 TEST1_DATA['ip'], PJLINK_PORT)
71
72 # WHEN: get_datagram called with 0 bytes ready
73 pjlink_udp.get_datagram()
74
75 # THEN: Log entries should be made and method returns
76 mock_log.debug.assert_has_calls(log_debug_calls)
77 mock_log.warn.assert_has_calls(log_warn_calls)
78
79 @patch.object(openlp.core.projectors.pjlink, 'log')
80 def test_get_datagram_data_invalid_command(self, mock_log):
81 """
82 Test get_datagram with invalid PJLink UDP command
83 """
84 # GIVEN: Test setup
85 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
86 log_warn_calls = [call('(UDP) Invalid packet - not a valid PJLink UDP reply')]
87 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
88 call('(UDP) get_datagram() - Receiving data'),
89 call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
90 call('(UDP) packet "%2DUMB=11:11:11:11:11:11"')]
91 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
92 patch.object(pjlink_udp, 'readDatagram') as mock_read:
93 mock_datagram.return_value = 24
94 mock_read.return_value = ('{prefix}2DUMB={mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
95 TEST1_DATA['ip'], PJLINK_PORT)
96
97 # WHEN: get_datagram called with 0 bytes ready
98 pjlink_udp.get_datagram()
99
100 # THEN: Log entries should be made and method returns
101 mock_log.debug.assert_has_calls(log_debug_calls)
102 mock_log.warn.assert_has_calls(log_warn_calls)
103
104 @patch.object(openlp.core.projectors.pjlink, 'log')
105 def test_get_datagram_data_invalid_prefix(self, mock_log):
106 """
107 Test get_datagram when prefix != PJLINK_PREFIX
108 """
109 # GIVEN: Test setup
110 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
111 log_warn_calls = [call('(UDP) Invalid packet - does not start with PJLINK_PREFIX')]
112 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
113 call('(UDP) get_datagram() - Receiving data'),
114 call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
115 call('(UDP) packet "$2ACKN=11:11:11:11:11:11"')]
116 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
117 patch.object(pjlink_udp, 'readDatagram') as mock_read:
118 mock_datagram.return_value = 24
119 mock_read.return_value = ('{prefix}2ACKN={mac}'.format(prefix='$', mac=TEST1_DATA['mac_adx']),
120 TEST1_DATA['ip'], PJLINK_PORT)
121
122 # WHEN: get_datagram called with 0 bytes ready
123 pjlink_udp.get_datagram()
124
125 # THEN: Log entries should be made and method returns
126 mock_log.debug.assert_has_calls(log_debug_calls)
127 mock_log.warn.assert_has_calls(log_warn_calls)
128
129 @patch.object(openlp.core.projectors.pjlink, 'log')
130 def test_get_datagram_data_invalid_separator(self, mock_log):
131 """
132 Test get_datagram when separator not equal to =
133 """
134 # GIVEN: Test setup
135 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
136 log_warn_calls = [call('(UDP) Invalid packet - separator missing')]
137 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
138 call('(UDP) get_datagram() - Receiving data'),
139 call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
140 call('(UDP) packet "%2ACKN 11:11:11:11:11:11"')]
141 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
142 patch.object(pjlink_udp, 'readDatagram') as mock_read:
143 mock_datagram.return_value = 24
144 mock_read.return_value = ('{prefix}2ACKN {mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
145 TEST1_DATA['ip'], PJLINK_PORT)
146
147 # WHEN: get_datagram called with 0 bytes ready
148 pjlink_udp.get_datagram()
149
150 # THEN: Log entries should be made and method returns
151 mock_log.debug.assert_has_calls(log_debug_calls)
152 mock_log.warn.assert_has_calls(log_warn_calls)
153
154 @patch.object(openlp.core.projectors.pjlink, 'log')
155 def test_get_datagram_data_long(self, mock_log):
156 """
157 Test get_datagram when datagram > PJLINK_MAX_PACKET
158 """
159 # GIVEN: Test setup
160 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
161 log_warn_calls = [call('(UDP) Invalid packet - length too long')]
162 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
163 call('(UDP) get_datagram() - Receiving data'),
164 call('(UDP) 143 bytes received from 111.111.111.111 on port 4352'),
165 call('(UDP) packet "%2ACKN={long}"'.format(long='X' * PJLINK_MAX_PACKET))]
166 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
167 patch.object(pjlink_udp, 'readDatagram') as mock_read:
168 mock_datagram.return_value = PJLINK_MAX_PACKET + 7
169 mock_read.return_value = ('{prefix}2ACKN={long}'.format(prefix=PJLINK_PREFIX,
170 long='X' * PJLINK_MAX_PACKET),
171 TEST1_DATA['ip'], PJLINK_PORT)
172
173 # WHEN: get_datagram called with 0 bytes ready
174 pjlink_udp.get_datagram()
175
176 # THEN: Log entries should be made and method returns
177 mock_log.debug.assert_has_calls(log_debug_calls)
178 mock_log.warn.assert_has_calls(log_warn_calls)
179
180 @patch.object(openlp.core.projectors.pjlink, 'log')
181 def test_get_datagram_data_negative_zero_length(self, mock_log):
182 """
183 Test get_datagram when pendingDatagramSize = 0
184 """
185 # GIVEN: Test setup
186 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
187 log_warn_calls = [call('(UDP) No data (-1)')]
188 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
189 call('(UDP) get_datagram() - Receiving data')]
190 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
191 patch.object(pjlink_udp, 'readDatagram') as mock_read:
192 mock_datagram.return_value = -1
193 mock_read.return_value = ('', TEST1_DATA['ip'], PJLINK_PORT)
194
195 # WHEN: get_datagram called with 0 bytes ready
196 pjlink_udp.get_datagram()
197
198 # THEN: Log entries should be made and method returns
199 mock_log.warn.assert_has_calls(log_warn_calls)
200 mock_log.debug.assert_has_calls(log_debug_calls)
201
202 @patch.object(openlp.core.projectors.pjlink, 'log')
203 def test_get_datagram_data_no_data(self, mock_log):
204 """
205 Test get_datagram when data length = 0
206 """
207 # GIVEN: Test setup
208 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
209 log_warn_calls = [call('(UDP) Invalid packet - not enough data')]
210 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
211 call('(UDP) get_datagram() - Receiving data')]
212 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
213 patch.object(pjlink_udp, 'readDatagram') as mock_read:
214 mock_datagram.return_value = 1
215 mock_read.return_value = ('', TEST1_DATA['ip'], PJLINK_PORT)
216
217 # WHEN: get_datagram called with 0 bytes ready
218 pjlink_udp.get_datagram()
219
220 # THEN: Log entries should be made and method returns
221 mock_log.warn.assert_has_calls(log_warn_calls)
222 mock_log.debug.assert_has_calls(log_debug_calls)
223
224 @patch.object(openlp.core.projectors.pjlink, 'log')
225 def test_get_datagram_data_short(self, mock_log):
226 """
227 Test get_datagram when data length < 8
228 """
229 # GIVEN: Test setup
230 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
231 log_warn_calls = [call('(UDP) Invalid packet - not enough data')]
232 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
233 call('(UDP) get_datagram() - Receiving data')]
234 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
235 patch.object(pjlink_udp, 'readDatagram') as mock_read:
236 mock_datagram.return_value = 6
237 mock_read.return_value = ('{prefix}2ACKN'.format(prefix=PJLINK_PREFIX), TEST1_DATA['ip'], PJLINK_PORT)
238
239 # WHEN: get_datagram called with 0 bytes ready
240 pjlink_udp.get_datagram()
241
242 # THEN: Log entries should be made and method returns
243 mock_log.warn.assert_has_calls(log_warn_calls)
244 mock_log.debug.assert_has_calls(log_debug_calls)
245
246 @patch.object(openlp.core.projectors.pjlink, 'log')
247 def test_get_datagram_pending_zero_length(self, mock_log):
248 """
249 Test get_datagram when pendingDatagramSize = 0
250 """
251 # GIVEN: Test setup
252 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
253 log_warn_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
254 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
255 call('(UDP) get_datagram() - Receiving data')]
256 with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram:
257 mock_datagram.return_value = 0
258
259 # WHEN: get_datagram called with 0 bytes ready
260 pjlink_udp.get_datagram()
261
262 # THEN: Log entries should be made and method returns
263 mock_log.warn.assert_has_calls(log_warn_calls)
264 mock_log.debug.assert_has_calls(log_debug_calls)
265
266 @patch.object(openlp.core.projectors.pjlink, 'log')
267 def test_process_ackn_duplicate(self, mock_log):
268 """
269 Test process_ackn method with multiple calls with same data
270 """
271 # GIVEN: Test setup
272 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
273 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT}}
274 log_warn_calls = [call('(UDP) Host {host} already replied - ignoring'.format(host=TEST1_DATA['ip']))]
275 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
276 call('(UDP) Processing ACKN packet'),
277 call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip'])),
278 call('(UDP) Processing ACKN packet')]
279
280 # WHEN: process_ackn called twice with same data
281 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
282 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
283
284 # THEN: pjlink_udp.ack_list should equal test_list
285 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
286 if pjlink_udp.ackn_list != check_list:
287 # Check this way so we can print differences to stdout
288 print('\nackn_list: ', pjlink_udp.ackn_list)
289 print('test_list: ', check_list)
290 assert pjlink_udp.ackn_list == check_list
291 mock_log.debug.assert_has_calls(log_debug_calls)
292 mock_log.warn.assert_has_calls(log_warn_calls)
293
294 @patch.object(openlp.core.projectors.pjlink, 'log')
295 def test_process_ackn_multiple(self, mock_log):
296 """
297 Test process_ackn method with multiple calls
298 """
299 # GIVEN: Test setup
300 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
301 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT},
302 TEST2_DATA['ip']: {'data': TEST2_DATA['mac_adx'], 'port': PJLINK_PORT}}
303 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
304 call('(UDP) Processing ACKN packet'),
305 call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip'])),
306 call('(UDP) Processing ACKN packet'),
307 call('(UDP) Adding {host} to ACKN list'.format(host=TEST2_DATA['ip']))]
308
309 # WHEN: process_ackn called twice with different data
310 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
311 pjlink_udp.process_ackn(data=TEST2_DATA['mac_adx'], host=TEST2_DATA['ip'], port=PJLINK_PORT)
312
313 # THEN: pjlink_udp.ack_list should equal test_list
314 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
315 if pjlink_udp.ackn_list != check_list:
316 # Check this way so we can print differences to stdout
317 print('\nackn_list: ', pjlink_udp.ackn_list)
318 print('test_list: ', check_list)
319 assert pjlink_udp.ackn_list == check_list
320 mock_log.debug.assert_has_calls(log_debug_calls)
321
322 @patch.object(openlp.core.projectors.pjlink, 'log')
323 def test_process_ackn_single(self, mock_log):
324 """
325 Test process_ackn method with single call
326 """
327 # GIVEN: Test setup
328 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
329 check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT}}
330 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
331 call('(UDP) Processing ACKN packet'),
332 call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip']))]
333
334 # WHEN: process_ackn called twice with different data
335 pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
336
337 # THEN: pjlink_udp.ack_list should equal test_list
338 # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
339 if pjlink_udp.ackn_list != check_list:
340 # Check this way so we can print differences to stdout
341 print('\nackn_list: ', pjlink_udp.ackn_list)
342 print('test_list: ', check_list)
343 assert pjlink_udp.ackn_list == check_list
344 mock_log.debug.assert_has_calls(log_debug_calls)
345
346 @patch.object(openlp.core.projectors.pjlink, 'log')
347 def test_process_srch(self, mock_log):
348 """
349 Test process_srch method
350 """
351 # GIVEN: Test setup
352 pjlink_udp = PJLinkUDP(projector_list=self.test_list)
353 log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
354 call('(UDP) SRCH packet received - ignoring')]
355
356 # WHEN: process_srch called
357 pjlink_udp.process_srch(data=None, host=None, port=None)
358
359 # THEN: debug log entry should be entered
360 mock_log.debug.assert_has_calls(log_debug_calls)
0361
=== renamed file 'tests/functional/openlp_core/common/test_projector_utilities.py' => 'tests/openlp_core/projectors/test_projector_utilities.py'
=== renamed file 'tests/interfaces/openlp_core/ui/test_projectoreditform.py' => 'tests/openlp_core/projectors/test_projectoreditform.py'
=== renamed file 'tests/interfaces/openlp_core/ui/test_projectormanager.py' => 'tests/openlp_core/projectors/test_projectormanager.py'
=== renamed file 'tests/interfaces/openlp_core/ui/test_projectorsourceform.py' => 'tests/openlp_core/projectors/test_projectorsourceform.py'
--- tests/interfaces/openlp_core/ui/test_projectorsourceform.py 2017-12-29 09:15:48 +0000
+++ tests/openlp_core/projectors/test_projectorsourceform.py 2018-02-12 01:05:51 +0000
@@ -125,7 +125,6 @@
125 select_form = SourceSelectSingle(parent=None, projectordb=self.projectordb)125 select_form = SourceSelectSingle(parent=None, projectordb=self.projectordb)
126 select_form.edit = True126 select_form.edit = True
127 select_form.exec(projector=self.projector)127 select_form.exec(projector=self.projector)
128 projector = select_form.projector
129128
130 # THEN: Verify all 4 buttons are available129 # THEN: Verify all 4 buttons are available
131 assert len(select_form.button_box.buttons()) == 4, \130 assert len(select_form.button_box.buttons()) == 4, \
@@ -144,7 +143,6 @@
144 select_form = SourceSelectSingle(parent=None, projectordb=self.projectordb)143 select_form = SourceSelectSingle(parent=None, projectordb=self.projectordb)
145 select_form.edit = False144 select_form.edit = False
146 select_form.exec(projector=self.projector)145 select_form.exec(projector=self.projector)
147 projector = select_form.projector
148146
149 # THEN: Verify only 2 buttons are available147 # THEN: Verify only 2 buttons are available
150 assert len(select_form.button_box.buttons()) == 2, \148 assert len(select_form.button_box.buttons()) == 2, \
151149
=== modified file 'tests/resources/projector/data.py'
--- tests/resources/projector/data.py 2017-12-29 09:15:48 +0000
+++ tests/resources/projector/data.py 2018-02-12 01:05:51 +0000
@@ -45,7 +45,8 @@
45 serial_no='Serial Number 1',45 serial_no='Serial Number 1',
46 sw_version='Version 1',46 sw_version='Version 1',
47 model_filter='Filter type 1',47 model_filter='Filter type 1',
48 model_lamp='Lamp type 1')48 model_lamp='Lamp type 1',
49 mac_adx='11:11:11:11:11:11')
4950
50TEST2_DATA = dict(ip='222.222.222.222',51TEST2_DATA = dict(ip='222.222.222.222',
51 port='2222',52 port='2222',
@@ -56,7 +57,8 @@
56 serial_no='Serial Number 2',57 serial_no='Serial Number 2',
57 sw_version='Version 2',58 sw_version='Version 2',
58 model_filter='Filter type 2',59 model_filter='Filter type 2',
59 model_lamp='Lamp type 2')60 model_lamp='Lamp type 2',
61 mac_adx='22:22:22:22:22:22')
6062
61TEST3_DATA = dict(ip='333.333.333.333',63TEST3_DATA = dict(ip='333.333.333.333',
62 port='3333',64 port='3333',
@@ -67,7 +69,8 @@
67 serial_no='Serial Number 3',69 serial_no='Serial Number 3',
68 sw_version='Version 3',70 sw_version='Version 3',
69 model_filter='Filter type 3',71 model_filter='Filter type 3',
70 model_lamp='Lamp type 3')72 model_lamp='Lamp type 3',
73 mac_adx='33:33:33:33:33:33')
7174
72TEST_VIDEO_CODES = {75TEST_VIDEO_CODES = {
73 '11': 'RGB 1',76 '11': 'RGB 1',