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