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

Proposed by Ken Roberts
Status: Superseded
Proposed branch: lp:~alisonken1/openlp/pjlink2-p
Merge into: lp:openlp
Diff against target: 826 lines (+572/-40)
10 files modified
openlp/core/api/tab.py (+6/-11)
openlp/core/common/__init__.py (+22/-0)
openlp/core/projectors/manager.py (+2/-5)
openlp/core/projectors/pjlink.py (+143/-18)
tests/functional/openlp_core/api/test_tab.py (+6/-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
Tim Bentley Needs Fixing
Review via email: mp+337504@code.launchpad.net

This proposal has been superseded by 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

--------------------------------------------------------------------------------
lp:~alisonken1/openlp/pjlink2-p (revision 2810)
https://ci.openlp.io/job/Branch-01-Pull/2443/ [SUCCESS]
https://ci.openlp.io/job/Branch-02a-Linux-Tests/2344/ [SUCCESS]
https://ci.openlp.io/job/Branch-02b-macOS-Tests/138/ [SUCCESS]
https://ci.openlp.io/job/Branch-03a-Build-Source/60/ [SUCCESS]
https://ci.openlp.io/job/Branch-03b-Build-macOS/58/ [SUCCESS]
https://ci.openlp.io/job/Branch-04a-Code-Analysis/1522/ [SUCCESS]
https://ci.openlp.io/job/Branch-04b-Test-Coverage/1335/ [SUCCESS]
https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/276/ [FAILURE]

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

One question and a request to refactor.

review: Needs Fixing
Revision history for this message
Ken Roberts (alisonken1) wrote :

>> === 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

lp:~alisonken1/openlp/pjlink2-p updated
2810. By Ken Roberts

PJLink2 Update P

2811. By Ken Roberts

Move MY_IP4 dict to get_local_ip4 function

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/api/tab.py'
2--- openlp/core/api/tab.py 2017-12-29 09:15:48 +0000
3+++ openlp/core/api/tab.py 2018-02-11 11:42:51 +0000
4@@ -24,6 +24,7 @@
5 """
6 from PyQt5 import QtCore, QtGui, QtNetwork, QtWidgets
7
8+from openlp.core.common import MY_IP4
9 from openlp.core.common.i18n import UiStrings, translate
10 from openlp.core.common.registry import Registry
11 from openlp.core.common.settings import Settings
12@@ -219,17 +220,11 @@
13 else: return ip_address
14 """
15 if ip_address == ZERO_URL:
16- interfaces = QtNetwork.QNetworkInterface.allInterfaces()
17- for interface in interfaces:
18- if not interface.isValid():
19- continue
20- if not (interface.flags() & (QtNetwork.QNetworkInterface.IsUp | QtNetwork.QNetworkInterface.IsRunning)):
21- continue
22- for address in interface.addressEntries():
23- ip = address.ip()
24- if ip.protocol() == QtNetwork.QAbstractSocket.IPv4Protocol and \
25- ip != QtNetwork.QHostAddress.LocalHost:
26- return ip.toString()
27+ # In case we have more than one interface
28+ for key in iter(MY_IP4):
29+ ip_address = MY_IP4.get(key)['ip']
30+ # We only want the first interface returned
31+ break
32 return ip_address
33
34 def load(self):
35
36=== modified file 'openlp/core/common/__init__.py'
37--- openlp/core/common/__init__.py 2017-12-29 09:15:48 +0000
38+++ openlp/core/common/__init__.py 2018-02-11 11:42:51 +0000
39@@ -36,6 +36,7 @@
40
41 from PyQt5 import QtGui
42 from PyQt5.QtCore import QCryptographicHash as QHash
43+from PyQt5.QtNetwork import QAbstractSocket, QHostAddress, QNetworkInterface
44 from chardet.universaldetector import UniversalDetector
45
46 log = logging.getLogger(__name__ + '.__init__')
47@@ -51,6 +52,27 @@
48 NEW_LINE_REGEX = re.compile(r' ?(\r\n?|\n) ?')
49 WHITESPACE_REGEX = re.compile(r'[ \t]+')
50
51+# Get the local IPv4 active address(es) that are NOT localhost (lo or '127.0.0.1')
52+log.debug('Getting local IPv4 interface(es) information')
53+MY_IP4 = {}
54+for iface in QNetworkInterface.allInterfaces():
55+ if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
56+ continue
57+ for address in iface.addressEntries():
58+ ip = address.ip()
59+ if (ip.protocol() == QAbstractSocket.IPv4Protocol) and (ip != QHostAddress.LocalHost):
60+ MY_IP4[iface.name()] = {'ip': ip.toString(),
61+ 'broadcast': address.broadcast().toString(),
62+ 'netmask': address.netmask().toString(),
63+ 'prefix': address.prefixLength(),
64+ 'localnet': QHostAddress(address.netmask().toIPv4Address() &
65+ ip.toIPv4Address()).toString()
66+ }
67+ log.debug('Adding {iface} to active list'.format(iface=iface.name()))
68+
69+if not MY_IP4:
70+ log.warning('No active IPv4 interfaces found')
71+
72
73 def trace_error_handler(logger):
74 """
75
76=== modified file 'openlp/core/projectors/manager.py'
77--- openlp/core/projectors/manager.py 2018-01-13 05:41:42 +0000
78+++ openlp/core/projectors/manager.py 2018-02-11 11:42:51 +0000
79@@ -308,7 +308,6 @@
80 self.settings_section = 'projector'
81 self.projectordb = projectordb
82 self.projector_list = []
83- self.pjlink_udp = PJLinkUDP(self.projector_list)
84 self.source_select_form = None
85
86 def bootstrap_initialise(self):
87@@ -323,6 +322,7 @@
88 else:
89 log.debug('Using existing ProjectorDB() instance')
90 self.get_settings()
91+ self.pjlink_udp = PJLinkUDP(self.projector_list)
92
93 def bootstrap_post_set_up(self):
94 """
95@@ -344,6 +344,7 @@
96 """
97 Retrieve the saved settings
98 """
99+ log.debug('Updating ProjectorManager settings')
100 settings = Settings()
101 settings.beginGroup(self.settings_section)
102 self.autostart = settings.value('connect on start')
103@@ -502,10 +503,6 @@
104 if ans == msg.Cancel:
105 return
106 try:
107- projector.link.projectorNetwork.disconnect(self.update_status)
108- except (AttributeError, TypeError):
109- pass
110- try:
111 projector.link.changeStatus.disconnect(self.update_status)
112 except (AttributeError, TypeError):
113 pass
114
115=== modified file 'openlp/core/projectors/pjlink.py'
116--- openlp/core/projectors/pjlink.py 2018-01-13 05:41:42 +0000
117+++ openlp/core/projectors/pjlink.py 2018-02-11 11:42:51 +0000
118@@ -64,7 +64,7 @@
119 log = logging.getLogger(__name__)
120 log.debug('pjlink loaded')
121
122-__all__ = ['PJLink']
123+__all__ = ['PJLink', 'PJLinkUDP']
124
125 # Shortcuts
126 SocketError = QtNetwork.QAbstractSocket.SocketError
127@@ -79,22 +79,145 @@
128 """
129 Socket service for PJLink UDP socket.
130 """
131- # New commands available in PJLink Class 2
132- pjlink_udp_commands = [
133- 'ACKN', # Class 2 (cmd is SRCH)
134- 'ERST', # Class 1/2
135- 'INPT', # Class 1/2
136- 'LKUP', # Class 2 (reply only - no cmd)
137- 'POWR', # Class 1/2
138- 'SRCH' # Class 2 (reply is ACKN)
139- ]
140-
141 def __init__(self, projector_list, port=PJLINK_PORT):
142 """
143- Initialize socket
144+ Socket services for PJLink UDP packets.
145+
146+ Since all UDP packets from any projector will come into the same
147+ port, process UDP packets here then route to the appropriate
148+ projector instance as needed.
149 """
150+ # Keep track of currently defined projectors so we can route
151+ # inbound packets to the correct instance
152+ super().__init__()
153 self.projector_list = projector_list
154 self.port = port
155+ # Local defines
156+ self.ackn_list = {} # Replies from online projetors
157+ self.search_active = False
158+ self.search_time = 30000 # 30 seconds for allowed time
159+ self.search_timer = QtCore.QTimer()
160+ # New commands available in PJLink Class 2
161+ # ACKN/SRCH is processed here since it's used to find available projectors
162+ # Other commands are processed by the individual projector instances
163+ self.pjlink_udp_functions = {
164+ 'ACKN': self.process_ackn, # Class 2, command is 'SRCH'
165+ 'ERST': None, # Class 1/2
166+ 'INPT': None, # Class 1/2
167+ 'LKUP': None, # Class 2 (reply only - no cmd)
168+ 'POWR': None, # Class 1/2
169+ 'SRCH': self.process_srch # Class 2 (reply is ACKN)
170+ }
171+
172+ self.readyRead.connect(self.get_datagram)
173+ log.debug('(UDP) PJLinkUDP() Initialized')
174+
175+ @QtCore.pyqtSlot()
176+ def get_datagram(self):
177+ """
178+ Retrieve packet and basic checks
179+ """
180+ log.debug('(UDP) get_datagram() - Receiving data')
181+ read = self.pendingDatagramSize()
182+ if read < 0:
183+ log.warn('(UDP) No data (-1)')
184+ return
185+ if read < 1:
186+ log.warn('(UDP) get_datagram() called when pending data size is 0')
187+ return
188+ data, peer_address, peer_port = self.readDatagram(self.pendingDatagramSize())
189+ log.debug('(UDP) {size} bytes received from {adx} on port {port}'.format(size=len(data),
190+ adx=peer_address,
191+ port=peer_port))
192+ log.debug('(UDP) packet "{data}"'.format(data=data))
193+ if len(data) < 0:
194+ log.warn('(UDP) No data (-1)')
195+ return
196+ elif len(data) < 8:
197+ # Minimum packet is '%2CCCC='
198+ log.warn('(UDP) Invalid packet - not enough data')
199+ return
200+ elif data is None:
201+ log.warn('(UDP) No data (None)')
202+ return
203+ elif len(data) > PJLINK_MAX_PACKET:
204+ log.warn('(UDP) Invalid packet - length too long')
205+ return
206+ elif not data.startswith(PJLINK_PREFIX):
207+ log.warn('(UDP) Invalid packet - does not start with PJLINK_PREFIX')
208+ return
209+ elif data[1] != '2':
210+ log.warn('(UDP) Invalid packet - missing/invalid PJLink class version')
211+ return
212+ elif data[6] != '=':
213+ log.warn('(UDP) Invalid packet - separator missing')
214+ return
215+ # First two characters are header information we don't need at this time
216+ cmd, data = data[2:].split('=')
217+ if cmd not in self.pjlink_udp_functions:
218+ log.warn('(UDP) Invalid packet - not a valid PJLink UDP reply')
219+ return
220+ if self.pjlink_udp_functions[cmd] is not None:
221+ log.debug('(UDP) Processing {cmd} with "{data}"'.format(cmd=cmd, data=data))
222+ return self.pjlink_udp_functions[cmd](data=data, host=peer_address, port=peer_port)
223+ else:
224+ log.debug('(UDP) Checking projector list for ip {host} to process'.format(host=peer_address))
225+ for projector in self.projector_list:
226+ if peer_address == projector.ip:
227+ if cmd not in projector.pjlink_functions:
228+ log.error('(UDP) Could not find method to process '
229+ '"{cmd}" in {host}'.format(cmd=cmd, host=projector.ip))
230+ return
231+ log.debug('(UDP) Calling "{cmd}" in {host}'.format(cmd=cmd, host=projector.ip))
232+ return projector.pjlink_functions[cmd](data=data)
233+ log.warn('(UDP) Could not find projector with ip {ip} to process packet'.format(ip=peer_address))
234+ return
235+
236+ def process_ackn(self, data, host, port):
237+ """
238+ Process the ACKN command.
239+
240+ :param data: Data in packet
241+ :param host: IP address of sending host
242+ :param port: Port received on
243+ """
244+ log.debug('(UDP) Processing ACKN packet')
245+ if host not in self.ackn_list:
246+ log.debug('(UDP) Adding {host} to ACKN list'.format(host=host))
247+ self.ackn_list[host] = {'data': data,
248+ 'port': port}
249+ else:
250+ log.warn('(UDP) Host {host} already replied - ignoring'.format(host=host))
251+
252+ def process_srch(self, data, host, port):
253+ """
254+ Process the SRCH command.
255+
256+ SRCH is processed by terminals so we ignore any packet.
257+
258+ :param data: Data in packet
259+ :param host: IP address of sending host
260+ :param port: Port received on
261+ """
262+ log.debug('(UDP) SRCH packet received - ignoring')
263+ return
264+
265+ def search_start(self):
266+ """
267+ Start search for projectors on local network
268+ """
269+ self.search_active = True
270+ self.ackn_list = {}
271+ # TODO: Send SRCH packet here
272+ self.search_timer.singleShot(self.search_time, self.search_stop)
273+
274+ @QtCore.pyqtSlot()
275+ def search_stop(self):
276+ """
277+ Stop search
278+ """
279+ self.search_active = False
280+ self.search_timer.stop()
281
282
283 class PJLinkCommands(object):
284@@ -257,8 +380,9 @@
285 else:
286 clss = data
287 self.pjlink_class = clss
288- log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.entry.name,
289- data=self.pjlink_class))
290+ log.debug('({ip}) Setting pjlink_class for this projector '
291+ 'to "{data}"'.format(ip=self.entry.name,
292+ data=self.pjlink_class))
293 # Since we call this one on first connect, setup polling from here
294 if not self.no_poll:
295 log.debug('({ip}) process_pjlink(): Starting timer'.format(ip=self.entry.name))
296@@ -276,9 +400,10 @@
297 """
298 if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:
299 count = PJLINK_ERST_DATA['DATA_LENGTH']
300- log.warning('({ip}) Invalid error status response "{data}": length != {count}'.format(ip=self.entry.name,
301- data=data,
302- count=count))
303+ log.warning('({ip}) Invalid error status response "{data}": '
304+ 'length != {count}'.format(ip=self.entry.name,
305+ data=data,
306+ count=count))
307 return
308 try:
309 datacheck = int(data)
310@@ -557,7 +682,7 @@
311
312 class PJLink(QtNetwork.QTcpSocket, PJLinkCommands):
313 """
314- Socket service for PJLink TCP socket.
315+ Socket services for PJLink TCP packets.
316 """
317 # Signals sent by this module
318 changeStatus = QtCore.pyqtSignal(str, int, str)
319
320=== modified file 'tests/functional/openlp_core/api/test_tab.py'
321--- tests/functional/openlp_core/api/test_tab.py 2017-12-29 09:15:48 +0000
322+++ tests/functional/openlp_core/api/test_tab.py 2018-02-11 11:42:51 +0000
323@@ -29,6 +29,7 @@
324 from PyQt5 import QtWidgets
325
326 from openlp.core.api.tab import ApiTab
327+from openlp.core.common import MY_IP4
328 from openlp.core.common.registry import Registry
329 from openlp.core.common.settings import Settings
330 from tests.helpers.testmixin import TestMixin
331@@ -72,15 +73,18 @@
332 del self.form
333 self.destroy_settings()
334
335+ @patch.dict(MY_IP4, {'test': {'ip': '127.0.0.1'}}, clear=True)
336 def test_get_ip_address_default(self):
337 """
338 Test the get_ip_address function with ZERO_URL
339 """
340 # WHEN: the default ip address is given
341 ip_address = self.form.get_ip_address(ZERO_URL)
342+
343 # THEN: the default ip address will be returned
344 assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \
345 'The return value should be a valid ip address'
346+ assert ip_address == '127.0.0.1', 'The return address should match the test address'
347
348 def test_get_ip_address_with_ip(self):
349 """
350@@ -88,8 +92,10 @@
351 """
352 # GIVEN: An ip address
353 given_ip = '192.168.1.1'
354+
355 # WHEN: the default ip address is given
356 ip_address = self.form.get_ip_address(given_ip)
357+
358 # THEN: the default ip address will be returned
359 assert ip_address == given_ip, 'The return value should be %s' % given_ip
360
361
362=== added directory 'tests/openlp_core'
363=== added file 'tests/openlp_core/__init__.py'
364--- tests/openlp_core/__init__.py 1970-01-01 00:00:00 +0000
365+++ tests/openlp_core/__init__.py 2018-02-11 11:42:51 +0000
366@@ -0,0 +1,26 @@
367+# -*- coding: utf-8 -*-
368+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
369+
370+###############################################################################
371+# OpenLP - Open Source Lyrics Projection #
372+# --------------------------------------------------------------------------- #
373+# Copyright (c) 2008-2018 OpenLP Developers #
374+# --------------------------------------------------------------------------- #
375+# This program is free software; you can redistribute it and/or modify it #
376+# under the terms of the GNU General Public License as published by the Free #
377+# Software Foundation; version 2 of the License. #
378+# #
379+# This program is distributed in the hope that it will be useful, but WITHOUT #
380+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
381+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
382+# more details. #
383+# #
384+# You should have received a copy of the GNU General Public License along #
385+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
386+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
387+###############################################################################
388+"""
389+:mod: `tests.openlp_core` module
390+
391+Tests modules/files for module openlp.core
392+"""
393
394=== renamed directory 'tests/functional/openlp_core/projectors' => 'tests/openlp_core/projectors'
395=== modified file 'tests/openlp_core/projectors/__init__.py'
396--- tests/functional/openlp_core/projectors/__init__.py 2017-11-10 11:59:38 +0000
397+++ tests/openlp_core/projectors/__init__.py 2018-02-11 11:42:51 +0000
398@@ -20,5 +20,5 @@
399 # Temple Place, Suite 330, Boston, MA 02111-1307 USA #
400 ###############################################################################
401 """
402-Module-level functions for the functional test suite
403+Module-level functions for the projector test suite
404 """
405
406=== added file 'tests/openlp_core/projectors/test_projector_pjlink_udp.py'
407--- tests/openlp_core/projectors/test_projector_pjlink_udp.py 1970-01-01 00:00:00 +0000
408+++ tests/openlp_core/projectors/test_projector_pjlink_udp.py 2018-02-11 11:42:51 +0000
409@@ -0,0 +1,360 @@
410+
411+# -*- coding: utf-8 -*-
412+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
413+
414+###############################################################################
415+# OpenLP - Open Source Lyrics Projection #
416+# --------------------------------------------------------------------------- #
417+# Copyright (c) 2008-2018 OpenLP Developers #
418+# --------------------------------------------------------------------------- #
419+# This program is free software; you can redistribute it and/or modify it #
420+# under the terms of the GNU General Public License as published by the Free #
421+# Software Foundation; version 2 of the License. #
422+# #
423+# This program is distributed in the hope that it will be useful, but WITHOUT #
424+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
425+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
426+# more details. #
427+# #
428+# You should have received a copy of the GNU General Public License along #
429+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
430+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
431+###############################################################################
432+"""
433+Package to test the PJLink UDP functions
434+"""
435+
436+from unittest import TestCase
437+from unittest.mock import call, patch
438+
439+import openlp.core.projectors.pjlink
440+from openlp.core.projectors.constants import PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_PREFIX
441+
442+from openlp.core.projectors.db import Projector
443+from openlp.core.projectors.pjlink import PJLinkUDP
444+from tests.resources.projector.data import TEST1_DATA, TEST2_DATA
445+
446+
447+class TestPJLinkBase(TestCase):
448+ """
449+ Tests for the PJLinkUDP class
450+ """
451+ def setUp(self):
452+ """
453+ Setup generic test conditions
454+ """
455+ self.test_list = [Projector(**TEST1_DATA), Projector(**TEST2_DATA)]
456+
457+ def tearDown(self):
458+ """
459+ Close generic test condidtions
460+ """
461+ self.test_list = None
462+
463+ @patch.object(openlp.core.projectors.pjlink, 'log')
464+ def test_get_datagram_data_invalid_class(self, mock_log):
465+ """
466+ Test get_datagram with invalid class number
467+ """
468+ # GIVEN: Test setup
469+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
470+ log_warn_calls = [call('(UDP) Invalid packet - missing/invalid PJLink class version')]
471+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
472+ call('(UDP) get_datagram() - Receiving data'),
473+ call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
474+ call('(UDP) packet "%1ACKN=11:11:11:11:11:11"')]
475+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
476+ patch.object(pjlink_udp, 'readDatagram') as mock_read:
477+ mock_datagram.return_value = 24
478+ mock_read.return_value = ('{prefix}1ACKN={mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
479+ TEST1_DATA['ip'], PJLINK_PORT)
480+
481+ # WHEN: get_datagram called with 0 bytes ready
482+ pjlink_udp.get_datagram()
483+
484+ # THEN: Log entries should be made and method returns
485+ mock_log.debug.assert_has_calls(log_debug_calls)
486+ mock_log.warn.assert_has_calls(log_warn_calls)
487+
488+ @patch.object(openlp.core.projectors.pjlink, 'log')
489+ def test_get_datagram_data_invalid_command(self, mock_log):
490+ """
491+ Test get_datagram with invalid PJLink UDP command
492+ """
493+ # GIVEN: Test setup
494+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
495+ log_warn_calls = [call('(UDP) Invalid packet - not a valid PJLink UDP reply')]
496+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
497+ call('(UDP) get_datagram() - Receiving data'),
498+ call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
499+ call('(UDP) packet "%2DUMB=11:11:11:11:11:11"')]
500+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
501+ patch.object(pjlink_udp, 'readDatagram') as mock_read:
502+ mock_datagram.return_value = 24
503+ mock_read.return_value = ('{prefix}2DUMB={mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
504+ TEST1_DATA['ip'], PJLINK_PORT)
505+
506+ # WHEN: get_datagram called with 0 bytes ready
507+ pjlink_udp.get_datagram()
508+
509+ # THEN: Log entries should be made and method returns
510+ mock_log.debug.assert_has_calls(log_debug_calls)
511+ mock_log.warn.assert_has_calls(log_warn_calls)
512+
513+ @patch.object(openlp.core.projectors.pjlink, 'log')
514+ def test_get_datagram_data_invalid_prefix(self, mock_log):
515+ """
516+ Test get_datagram when prefix != PJLINK_PREFIX
517+ """
518+ # GIVEN: Test setup
519+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
520+ log_warn_calls = [call('(UDP) Invalid packet - does not start with PJLINK_PREFIX')]
521+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
522+ call('(UDP) get_datagram() - Receiving data'),
523+ call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
524+ call('(UDP) packet "$2ACKN=11:11:11:11:11:11"')]
525+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
526+ patch.object(pjlink_udp, 'readDatagram') as mock_read:
527+ mock_datagram.return_value = 24
528+ mock_read.return_value = ('{prefix}2ACKN={mac}'.format(prefix='$', mac=TEST1_DATA['mac_adx']),
529+ TEST1_DATA['ip'], PJLINK_PORT)
530+
531+ # WHEN: get_datagram called with 0 bytes ready
532+ pjlink_udp.get_datagram()
533+
534+ # THEN: Log entries should be made and method returns
535+ mock_log.debug.assert_has_calls(log_debug_calls)
536+ mock_log.warn.assert_has_calls(log_warn_calls)
537+
538+ @patch.object(openlp.core.projectors.pjlink, 'log')
539+ def test_get_datagram_data_invalid_separator(self, mock_log):
540+ """
541+ Test get_datagram when separator not equal to =
542+ """
543+ # GIVEN: Test setup
544+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
545+ log_warn_calls = [call('(UDP) Invalid packet - separator missing')]
546+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
547+ call('(UDP) get_datagram() - Receiving data'),
548+ call('(UDP) 24 bytes received from 111.111.111.111 on port 4352'),
549+ call('(UDP) packet "%2ACKN 11:11:11:11:11:11"')]
550+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
551+ patch.object(pjlink_udp, 'readDatagram') as mock_read:
552+ mock_datagram.return_value = 24
553+ mock_read.return_value = ('{prefix}2ACKN {mac}'.format(prefix=PJLINK_PREFIX, mac=TEST1_DATA['mac_adx']),
554+ TEST1_DATA['ip'], PJLINK_PORT)
555+
556+ # WHEN: get_datagram called with 0 bytes ready
557+ pjlink_udp.get_datagram()
558+
559+ # THEN: Log entries should be made and method returns
560+ mock_log.debug.assert_has_calls(log_debug_calls)
561+ mock_log.warn.assert_has_calls(log_warn_calls)
562+
563+ @patch.object(openlp.core.projectors.pjlink, 'log')
564+ def test_get_datagram_data_long(self, mock_log):
565+ """
566+ Test get_datagram when datagram > PJLINK_MAX_PACKET
567+ """
568+ # GIVEN: Test setup
569+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
570+ log_warn_calls = [call('(UDP) Invalid packet - length too long')]
571+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
572+ call('(UDP) get_datagram() - Receiving data'),
573+ call('(UDP) 143 bytes received from 111.111.111.111 on port 4352'),
574+ call('(UDP) packet "%2ACKN={long}"'.format(long='X' * PJLINK_MAX_PACKET))]
575+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
576+ patch.object(pjlink_udp, 'readDatagram') as mock_read:
577+ mock_datagram.return_value = PJLINK_MAX_PACKET + 7
578+ mock_read.return_value = ('{prefix}2ACKN={long}'.format(prefix=PJLINK_PREFIX,
579+ long='X' * PJLINK_MAX_PACKET),
580+ TEST1_DATA['ip'], PJLINK_PORT)
581+
582+ # WHEN: get_datagram called with 0 bytes ready
583+ pjlink_udp.get_datagram()
584+
585+ # THEN: Log entries should be made and method returns
586+ mock_log.debug.assert_has_calls(log_debug_calls)
587+ mock_log.warn.assert_has_calls(log_warn_calls)
588+
589+ @patch.object(openlp.core.projectors.pjlink, 'log')
590+ def test_get_datagram_data_negative_zero_length(self, mock_log):
591+ """
592+ Test get_datagram when pendingDatagramSize = 0
593+ """
594+ # GIVEN: Test setup
595+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
596+ log_warn_calls = [call('(UDP) No data (-1)')]
597+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
598+ call('(UDP) get_datagram() - Receiving data')]
599+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
600+ patch.object(pjlink_udp, 'readDatagram') as mock_read:
601+ mock_datagram.return_value = -1
602+ mock_read.return_value = ('', TEST1_DATA['ip'], PJLINK_PORT)
603+
604+ # WHEN: get_datagram called with 0 bytes ready
605+ pjlink_udp.get_datagram()
606+
607+ # THEN: Log entries should be made and method returns
608+ mock_log.warn.assert_has_calls(log_warn_calls)
609+ mock_log.debug.assert_has_calls(log_debug_calls)
610+
611+ @patch.object(openlp.core.projectors.pjlink, 'log')
612+ def test_get_datagram_data_no_data(self, mock_log):
613+ """
614+ Test get_datagram when data length = 0
615+ """
616+ # GIVEN: Test setup
617+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
618+ log_warn_calls = [call('(UDP) Invalid packet - not enough data')]
619+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
620+ call('(UDP) get_datagram() - Receiving data')]
621+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
622+ patch.object(pjlink_udp, 'readDatagram') as mock_read:
623+ mock_datagram.return_value = 1
624+ mock_read.return_value = ('', TEST1_DATA['ip'], PJLINK_PORT)
625+
626+ # WHEN: get_datagram called with 0 bytes ready
627+ pjlink_udp.get_datagram()
628+
629+ # THEN: Log entries should be made and method returns
630+ mock_log.warn.assert_has_calls(log_warn_calls)
631+ mock_log.debug.assert_has_calls(log_debug_calls)
632+
633+ @patch.object(openlp.core.projectors.pjlink, 'log')
634+ def test_get_datagram_data_short(self, mock_log):
635+ """
636+ Test get_datagram when data length < 8
637+ """
638+ # GIVEN: Test setup
639+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
640+ log_warn_calls = [call('(UDP) Invalid packet - not enough data')]
641+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
642+ call('(UDP) get_datagram() - Receiving data')]
643+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram, \
644+ patch.object(pjlink_udp, 'readDatagram') as mock_read:
645+ mock_datagram.return_value = 6
646+ mock_read.return_value = ('{prefix}2ACKN'.format(prefix=PJLINK_PREFIX), TEST1_DATA['ip'], PJLINK_PORT)
647+
648+ # WHEN: get_datagram called with 0 bytes ready
649+ pjlink_udp.get_datagram()
650+
651+ # THEN: Log entries should be made and method returns
652+ mock_log.warn.assert_has_calls(log_warn_calls)
653+ mock_log.debug.assert_has_calls(log_debug_calls)
654+
655+ @patch.object(openlp.core.projectors.pjlink, 'log')
656+ def test_get_datagram_pending_zero_length(self, mock_log):
657+ """
658+ Test get_datagram when pendingDatagramSize = 0
659+ """
660+ # GIVEN: Test setup
661+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
662+ log_warn_calls = [call('(UDP) get_datagram() called when pending data size is 0')]
663+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
664+ call('(UDP) get_datagram() - Receiving data')]
665+ with patch.object(pjlink_udp, 'pendingDatagramSize') as mock_datagram:
666+ mock_datagram.return_value = 0
667+
668+ # WHEN: get_datagram called with 0 bytes ready
669+ pjlink_udp.get_datagram()
670+
671+ # THEN: Log entries should be made and method returns
672+ mock_log.warn.assert_has_calls(log_warn_calls)
673+ mock_log.debug.assert_has_calls(log_debug_calls)
674+
675+ @patch.object(openlp.core.projectors.pjlink, 'log')
676+ def test_process_ackn_duplicate(self, mock_log):
677+ """
678+ Test process_ackn method with multiple calls with same data
679+ """
680+ # GIVEN: Test setup
681+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
682+ check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT}}
683+ log_warn_calls = [call('(UDP) Host {host} already replied - ignoring'.format(host=TEST1_DATA['ip']))]
684+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
685+ call('(UDP) Processing ACKN packet'),
686+ call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip'])),
687+ call('(UDP) Processing ACKN packet')]
688+
689+ # WHEN: process_ackn called twice with same data
690+ pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
691+ pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
692+
693+ # THEN: pjlink_udp.ack_list should equal test_list
694+ # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
695+ if pjlink_udp.ackn_list != check_list:
696+ # Check this way so we can print differences to stdout
697+ print('\nackn_list: ', pjlink_udp.ackn_list)
698+ print('test_list: ', check_list)
699+ assert pjlink_udp.ackn_list == check_list
700+ mock_log.debug.assert_has_calls(log_debug_calls)
701+ mock_log.warn.assert_has_calls(log_warn_calls)
702+
703+ @patch.object(openlp.core.projectors.pjlink, 'log')
704+ def test_process_ackn_multiple(self, mock_log):
705+ """
706+ Test process_ackn method with multiple calls
707+ """
708+ # GIVEN: Test setup
709+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
710+ check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT},
711+ TEST2_DATA['ip']: {'data': TEST2_DATA['mac_adx'], 'port': PJLINK_PORT}}
712+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
713+ call('(UDP) Processing ACKN packet'),
714+ call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip'])),
715+ call('(UDP) Processing ACKN packet'),
716+ call('(UDP) Adding {host} to ACKN list'.format(host=TEST2_DATA['ip']))]
717+
718+ # WHEN: process_ackn called twice with different data
719+ pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
720+ pjlink_udp.process_ackn(data=TEST2_DATA['mac_adx'], host=TEST2_DATA['ip'], port=PJLINK_PORT)
721+
722+ # THEN: pjlink_udp.ack_list should equal test_list
723+ # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
724+ if pjlink_udp.ackn_list != check_list:
725+ # Check this way so we can print differences to stdout
726+ print('\nackn_list: ', pjlink_udp.ackn_list)
727+ print('test_list: ', check_list)
728+ assert pjlink_udp.ackn_list == check_list
729+ mock_log.debug.assert_has_calls(log_debug_calls)
730+
731+ @patch.object(openlp.core.projectors.pjlink, 'log')
732+ def test_process_ackn_single(self, mock_log):
733+ """
734+ Test process_ackn method with single call
735+ """
736+ # GIVEN: Test setup
737+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
738+ check_list = {TEST1_DATA['ip']: {'data': TEST1_DATA['mac_adx'], 'port': PJLINK_PORT}}
739+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
740+ call('(UDP) Processing ACKN packet'),
741+ call('(UDP) Adding {host} to ACKN list'.format(host=TEST1_DATA['ip']))]
742+
743+ # WHEN: process_ackn called twice with different data
744+ pjlink_udp.process_ackn(data=TEST1_DATA['mac_adx'], host=TEST1_DATA['ip'], port=PJLINK_PORT)
745+
746+ # THEN: pjlink_udp.ack_list should equal test_list
747+ # NOTE: This assert only returns AssertionError - does not list differences. Maybe add a compare function?
748+ if pjlink_udp.ackn_list != check_list:
749+ # Check this way so we can print differences to stdout
750+ print('\nackn_list: ', pjlink_udp.ackn_list)
751+ print('test_list: ', check_list)
752+ assert pjlink_udp.ackn_list == check_list
753+ mock_log.debug.assert_has_calls(log_debug_calls)
754+
755+ @patch.object(openlp.core.projectors.pjlink, 'log')
756+ def test_process_srch(self, mock_log):
757+ """
758+ Test process_srch method
759+ """
760+ # GIVEN: Test setup
761+ pjlink_udp = PJLinkUDP(projector_list=self.test_list)
762+ log_debug_calls = [call('(UDP) PJLinkUDP() Initialized'),
763+ call('(UDP) SRCH packet received - ignoring')]
764+
765+ # WHEN: process_srch called
766+ pjlink_udp.process_srch(data=None, host=None, port=None)
767+
768+ # THEN: debug log entry should be entered
769+ mock_log.debug.assert_has_calls(log_debug_calls)
770
771=== renamed file 'tests/functional/openlp_core/common/test_projector_utilities.py' => 'tests/openlp_core/projectors/test_projector_utilities.py'
772=== renamed file 'tests/interfaces/openlp_core/ui/test_projectoreditform.py' => 'tests/openlp_core/projectors/test_projectoreditform.py'
773=== renamed file 'tests/interfaces/openlp_core/ui/test_projectormanager.py' => 'tests/openlp_core/projectors/test_projectormanager.py'
774=== renamed file 'tests/interfaces/openlp_core/ui/test_projectorsourceform.py' => 'tests/openlp_core/projectors/test_projectorsourceform.py'
775--- tests/interfaces/openlp_core/ui/test_projectorsourceform.py 2017-12-29 09:15:48 +0000
776+++ tests/openlp_core/projectors/test_projectorsourceform.py 2018-02-11 11:42:51 +0000
777@@ -125,7 +125,6 @@
778 select_form = SourceSelectSingle(parent=None, projectordb=self.projectordb)
779 select_form.edit = True
780 select_form.exec(projector=self.projector)
781- projector = select_form.projector
782
783 # THEN: Verify all 4 buttons are available
784 assert len(select_form.button_box.buttons()) == 4, \
785@@ -144,7 +143,6 @@
786 select_form = SourceSelectSingle(parent=None, projectordb=self.projectordb)
787 select_form.edit = False
788 select_form.exec(projector=self.projector)
789- projector = select_form.projector
790
791 # THEN: Verify only 2 buttons are available
792 assert len(select_form.button_box.buttons()) == 2, \
793
794=== modified file 'tests/resources/projector/data.py'
795--- tests/resources/projector/data.py 2017-12-29 09:15:48 +0000
796+++ tests/resources/projector/data.py 2018-02-11 11:42:51 +0000
797@@ -45,7 +45,8 @@
798 serial_no='Serial Number 1',
799 sw_version='Version 1',
800 model_filter='Filter type 1',
801- model_lamp='Lamp type 1')
802+ model_lamp='Lamp type 1',
803+ mac_adx='11:11:11:11:11:11')
804
805 TEST2_DATA = dict(ip='222.222.222.222',
806 port='2222',
807@@ -56,7 +57,8 @@
808 serial_no='Serial Number 2',
809 sw_version='Version 2',
810 model_filter='Filter type 2',
811- model_lamp='Lamp type 2')
812+ model_lamp='Lamp type 2',
813+ mac_adx='22:22:22:22:22:22')
814
815 TEST3_DATA = dict(ip='333.333.333.333',
816 port='3333',
817@@ -67,7 +69,8 @@
818 serial_no='Serial Number 3',
819 sw_version='Version 3',
820 model_filter='Filter type 3',
821- model_lamp='Lamp type 3')
822+ model_lamp='Lamp type 3',
823+ mac_adx='33:33:33:33:33:33')
824
825 TEST_VIDEO_CODES = {
826 '11': 'RGB 1',