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

Proposed by Ken Roberts
Status: Merged
Merged at revision: 2757
Proposed branch: lp:~alisonken1/openlp/pjlink2-h
Merge into: lp:openlp
Diff against target: 821 lines (+490/-118)
4 files modified
openlp/core/lib/projector/constants.py (+24/-3)
openlp/core/lib/projector/pjlink.py (+65/-52)
tests/functional/openlp_core_lib/test_projector_pjlink_cmd_routing.py (+222/-0)
tests/functional/openlp_core_lib/test_projector_pjlink_commands.py (+179/-63)
To merge this branch: bzr merge lp:~alisonken1/openlp/pjlink2-h
Reviewer Review Type Date Requested Status
Tomas Groth Approve
Tim Bentley Approve
Review via email: mp+328907@code.launchpad.net

Commit message

PJLink 2 update H

Description of the change

- Restructured AVMT to shortcut return on invalid input
- Added AVMT bad data test
- Fix AVMT tests
- Added extra logging information for CLSS errors
- Added CLSS failure tests
- Restructure ERST to not use hard-coded error breakout
- Added several ERST tests
- Fix ERST tests
- Added tests for pjlink.process_command

--------------------------------
lp:~alisonken1/openlp/pjlink2-h (revision 2757)
[SUCCESS] https://ci.openlp.io/job/Branch-01-Pull/2130/
[SUCCESS] https://ci.openlp.io/job/Branch-02-Functional-Tests/2037/
[SUCCESS] https://ci.openlp.io/job/Branch-03-Interface-Tests/1941/
[SUCCESS] https://ci.openlp.io/job/Branch-04a-Code_Analysis/1318/
[SUCCESS] https://ci.openlp.io/job/Branch-04b-Test_Coverage/1158/
[SUCCESS] https://ci.openlp.io/job/Branch-04c-Code_Analysis2/288/
[FAILURE] https://ci.openlp.io/job/Branch-05-AppVeyor-Tests/133/

To post a comment you must log in.
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
1=== modified file 'openlp/core/lib/projector/constants.py'
2--- openlp/core/lib/projector/constants.py 2017-08-06 07:23:26 +0000
3+++ openlp/core/lib/projector/constants.py 2017-08-11 11:14:30 +0000
4@@ -46,7 +46,7 @@
5 'S_NOT_CONNECTED', 'S_CONNECTING', 'S_CONNECTED',
6 'S_STATUS', 'S_OFF', 'S_INITIALIZE', 'S_STANDBY', 'S_WARMUP', 'S_ON', 'S_COOLDOWN',
7 'S_INFO', 'S_NETWORK_SENDING', 'S_NETWORK_RECEIVED',
8- 'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
9+ 'ERROR_STRING', 'CR', 'LF', 'PJLINK_ERST_DATA', 'PJLINK_ERST_STATUS', 'PJLINK_POWR_STATUS',
10 'PJLINK_PORT', 'PJLINK_MAX_PACKET', 'TIMEOUT', 'ERROR_MSG', 'PJLINK_ERRORS',
11 'STATUS_STRING', 'PJLINK_VALID_CMD', 'CONNECTION_ERRORS',
12 'PJLINK_DEFAULT_SOURCES', 'PJLINK_DEFAULT_CODES', 'PJLINK_DEFAULT_ITEMS']
13@@ -393,11 +393,32 @@
14 S_NETWORK_RECEIVED: translate('OpenLP.ProjectorConstants', 'Received data')
15 }
16
17+# Map ERST return code positions to equipment
18+PJLINK_ERST_DATA = {
19+ 'DATA_LENGTH': 6,
20+ 0: 'FAN',
21+ 1: 'LAMP',
22+ 2: 'TEMP',
23+ 3: 'COVER',
24+ 4: 'FILTER',
25+ 5: 'OTHER',
26+ 'FAN': 0,
27+ 'LAMP': 1,
28+ 'TEMP': 2,
29+ 'COVER': 3,
30+ 'FILTER': 4,
31+ 'OTHER': 5
32+}
33+
34 # Map for ERST return codes to string
35 PJLINK_ERST_STATUS = {
36- '0': ERROR_STRING[E_OK],
37+ '0': 'OK',
38 '1': ERROR_STRING[E_WARN],
39- '2': ERROR_STRING[E_ERROR]
40+ '2': ERROR_STRING[E_ERROR],
41+ 'OK': '0',
42+ E_OK: '0',
43+ E_WARN: '1',
44+ E_ERROR: '2'
45 }
46
47 # Map for POWR return codes to status code
48
49=== modified file 'openlp/core/lib/projector/pjlink.py'
50--- openlp/core/lib/projector/pjlink.py 2017-08-06 23:33:53 +0000
51+++ openlp/core/lib/projector/pjlink.py 2017-08-11 11:14:30 +0000
52@@ -54,8 +54,8 @@
53
54 from openlp.core.common import translate, qmd5_hash
55 from openlp.core.lib.projector.constants import CONNECTION_ERRORS, CR, ERROR_MSG, ERROR_STRING, \
56- E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, \
57- E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, \
58+ E_AUTHENTICATION, E_CONNECTION_REFUSED, E_GENERAL, E_INVALID_DATA, E_NETWORK, E_NOT_CONNECTED, E_OK, \
59+ E_PARAMETER, E_PROJECTOR, E_SOCKET_TIMEOUT, E_UNAVAILABLE, E_UNDEFINED, PJLINK_ERRORS, PJLINK_ERST_DATA, \
60 PJLINK_ERST_STATUS, PJLINK_MAX_PACKET, PJLINK_PORT, PJLINK_POWR_STATUS, PJLINK_VALID_CMD, \
61 STATUS_STRING, S_CONNECTED, S_CONNECTING, S_NETWORK_RECEIVED, S_NETWORK_SENDING, \
62 S_NOT_CONNECTED, S_OFF, S_OK, S_ON, S_STATUS
63@@ -154,39 +154,37 @@
64 if _cmd not in PJLINK_VALID_CMD:
65 log.error("({ip}) Ignoring command='{cmd}' (Invalid/Unknown)".format(ip=self.ip, cmd=cmd))
66 return
67+ elif _data == 'OK':
68+ log.debug('({ip}) Command "{cmd}" returned OK'.format(ip=self.ip, cmd=cmd))
69+ # A command returned successfully, no further processing needed
70+ return
71 elif _cmd not in self.pjlink_functions:
72 log.warn("({ip}) Unable to process command='{cmd}' (Future option)".format(ip=self.ip, cmd=cmd))
73 return
74 elif _data in PJLINK_ERRORS:
75 # Oops - projector error
76 log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data))
77- if _data == 'ERRA':
78+ if _data == PJLINK_ERRORS[E_AUTHENTICATION]:
79 # Authentication error
80 self.disconnect_from_host()
81 self.change_status(E_AUTHENTICATION)
82 log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip))
83 self.projectorAuthentication.emit(self.name)
84- elif _data == 'ERR1':
85- # Undefined command
86+ elif _data == PJLINK_ERRORS[E_UNDEFINED]:
87+ # Projector does not recognize command
88 self.change_status(E_UNDEFINED, '{error}: "{data}"'.format(error=ERROR_MSG[E_UNDEFINED],
89 data=cmd))
90- elif _data == 'ERR2':
91+ elif _data == PJLINK_ERRORS[E_PARAMETER]:
92 # Invalid parameter
93 self.change_status(E_PARAMETER)
94- elif _data == 'ERR3':
95+ elif _data == PJLINK_ERRORS[E_UNAVAILABLE]:
96 # Projector busy
97 self.change_status(E_UNAVAILABLE)
98- elif _data == 'ERR4':
99+ elif _data == PJLINK_ERRORS[E_PROJECTOR]:
100 # Projector/display error
101 self.change_status(E_PROJECTOR)
102 self.receive_data_signal()
103 return
104- # Command succeeded - no extra information
105- elif _data == 'OK':
106- log.debug('({ip}) Command returned OK'.format(ip=self.ip))
107- # A command returned successfully
108- self.receive_data_signal()
109- return
110 # Command checks already passed
111 log.debug('({ip}) Calling function for {cmd}'.format(ip=self.ip, cmd=cmd))
112 self.receive_data_signal()
113@@ -196,6 +194,10 @@
114 """
115 Process shutter and speaker status. See PJLink specification for format.
116 Update self.mute (audio) and self.shutter (video shutter).
117+ 11 = Shutter closed, audio unchanged
118+ 21 = Shutter unchanged, Audio muted
119+ 30 = Shutter closed, audio muted
120+ 31 = Shutter open, audio normal
121
122 :param data: Shutter and audio status
123 """
124@@ -204,15 +206,15 @@
125 '30': {'shutter': False, 'mute': False},
126 '31': {'shutter': True, 'mute': True}
127 }
128- if data in settings:
129- shutter = settings[data]['shutter']
130- mute = settings[data]['mute']
131- # Check if we need to update the icons
132- update_icons = (shutter != self.shutter) or (mute != self.mute)
133- self.shutter = shutter
134- self.mute = mute
135- else:
136- log.warning('({ip}) Unknown shutter response: {data}'.format(ip=self.ip, data=data))
137+ if data not in settings:
138+ log.warning('({ip}) Invalid shutter response: {data}'.format(ip=self.ip, data=data))
139+ return
140+ shutter = settings[data]['shutter']
141+ mute = settings[data]['mute']
142+ # Check if we need to update the icons
143+ update_icons = (shutter != self.shutter) or (mute != self.mute)
144+ self.shutter = shutter
145+ self.mute = mute
146 if update_icons:
147 self.projectorUpdateIcons.emit()
148 return
149@@ -236,10 +238,12 @@
150 try:
151 clss = re.findall('\d', data)[0] # Should only be the first match
152 except IndexError:
153- log.error("({ip}) No numbers found in class version reply - defaulting to class '1'".format(ip=self.ip))
154+ log.error("({ip}) No numbers found in class version reply '{data}' - "
155+ "defaulting to class '1'".format(ip=self.ip, data=data))
156 clss = '1'
157 elif not data.isdigit():
158- log.error("({ip}) NAN class version reply - defaulting to class '1'".format(ip=self.ip))
159+ log.error("({ip}) NAN clss version reply '{data}' - "
160+ "defaulting to class '1'".format(ip=self.ip, data=data))
161 clss = '1'
162 else:
163 clss = data
164@@ -253,41 +257,50 @@
165 Error status. See PJLink Specifications for format.
166 Updates self.projector_errors
167
168-\ :param data: Error status
169+ :param data: Error status
170 """
171+ if len(data) != PJLINK_ERST_DATA['DATA_LENGTH']:
172+ count = PJLINK_ERST_DATA['DATA_LENGTH']
173+ log.warn("{ip}) Invalid error status response '{data}': length != {count}".format(ip=self.ip,
174+ data=data,
175+ count=count))
176+ return
177 try:
178 datacheck = int(data)
179 except ValueError:
180 # Bad data - ignore
181+ log.warn("({ip}) Invalid error status response '{data}'".format(ip=self.ip, data=data))
182 return
183 if datacheck == 0:
184 self.projector_errors = None
185- else:
186- self.projector_errors = {}
187- # Fan
188- if data[0] != '0':
189- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
190- PJLINK_ERST_STATUS[data[0]]
191- # Lamp
192- if data[1] != '0':
193- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
194- PJLINK_ERST_STATUS[data[1]]
195- # Temp
196- if data[2] != '0':
197- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
198- PJLINK_ERST_STATUS[data[2]]
199- # Cover
200- if data[3] != '0':
201- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
202- PJLINK_ERST_STATUS[data[3]]
203- # Filter
204- if data[4] != '0':
205- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
206- PJLINK_ERST_STATUS[data[4]]
207- # Other
208- if data[5] != '0':
209- self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
210- PJLINK_ERST_STATUS[data[5]]
211+ # No errors
212+ return
213+ # We have some sort of status error, so check out what it/they are
214+ self.projector_errors = {}
215+ fan, lamp, temp, cover, filt, other = (data[PJLINK_ERST_DATA['FAN']],
216+ data[PJLINK_ERST_DATA['LAMP']],
217+ data[PJLINK_ERST_DATA['TEMP']],
218+ data[PJLINK_ERST_DATA['COVER']],
219+ data[PJLINK_ERST_DATA['FILTER']],
220+ data[PJLINK_ERST_DATA['OTHER']])
221+ if fan != PJLINK_ERST_STATUS[E_OK]:
222+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Fan')] = \
223+ PJLINK_ERST_STATUS[fan]
224+ if lamp != PJLINK_ERST_STATUS[E_OK]:
225+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Lamp')] = \
226+ PJLINK_ERST_STATUS[lamp]
227+ if temp != PJLINK_ERST_STATUS[E_OK]:
228+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Temperature')] = \
229+ PJLINK_ERST_STATUS[temp]
230+ if cover != PJLINK_ERST_STATUS[E_OK]:
231+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Cover')] = \
232+ PJLINK_ERST_STATUS[cover]
233+ if filt != PJLINK_ERST_STATUS[E_OK]:
234+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Filter')] = \
235+ PJLINK_ERST_STATUS[filt]
236+ if other != PJLINK_ERST_STATUS[E_OK]:
237+ self.projector_errors[translate('OpenLP.ProjectorPJLink', 'Other')] = \
238+ PJLINK_ERST_STATUS[other]
239 return
240
241 def process_inf1(self, data):
242
243=== added file 'tests/functional/openlp_core_lib/test_projector_pjlink_cmd_routing.py'
244--- tests/functional/openlp_core_lib/test_projector_pjlink_cmd_routing.py 1970-01-01 00:00:00 +0000
245+++ tests/functional/openlp_core_lib/test_projector_pjlink_cmd_routing.py 2017-08-11 11:14:30 +0000
246@@ -0,0 +1,222 @@
247+# -*- coding: utf-8 -*-
248+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
249+
250+###############################################################################
251+# OpenLP - Open Source Lyrics Projection #
252+# --------------------------------------------------------------------------- #
253+# Copyright (c) 2008-2015 OpenLP Developers #
254+# --------------------------------------------------------------------------- #
255+# This program is free software; you can redistribute it and/or modify it #
256+# under the terms of the GNU General Public License as published by the Free #
257+# Software Foundation; version 2 of the License. #
258+# #
259+# This program is distributed in the hope that it will be useful, but WITHOUT #
260+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
261+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for #
262+# more details. #
263+# #
264+# You should have received a copy of the GNU General Public License along #
265+# with this program; if not, write to the Free Software Foundation, Inc., 59 #
266+# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
267+###############################################################################
268+"""
269+Package to test the openlp.core.lib.projector.pjlink class command routing.
270+"""
271+
272+from unittest import TestCase
273+from unittest.mock import patch, MagicMock
274+
275+import openlp.core.lib.projector.pjlink
276+from openlp.core.lib.projector.pjlink import PJLink
277+from openlp.core.lib.projector.constants import PJLINK_ERRORS, \
278+ E_AUTHENTICATION, E_PARAMETER, E_PROJECTOR, E_UNAVAILABLE, E_UNDEFINED
279+
280+'''
281+from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
282+ PJLINK_POWR_STATUS, PJLINK_VALID_CMD, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
283+'''
284+from tests.resources.projector.data import TEST_PIN
285+
286+pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
287+
288+
289+class TestPJLinkRouting(TestCase):
290+ """
291+ Tests for the PJLink module command routing
292+ """
293+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
294+ def test_process_command_call_clss(self, mock_log):
295+ """
296+ Test process_command calls proper function
297+ """
298+ # GIVEN: Test object
299+ pjlink = pjlink_test
300+ log_text = '(127.0.0.1) Calling function for CLSS'
301+ mock_log.reset_mock()
302+ mock_process_clss = MagicMock()
303+ pjlink.pjlink_functions['CLSS'] = mock_process_clss
304+
305+ # WHEN: process_command is called with valid function and data
306+ pjlink.process_command(cmd='CLSS', data='1')
307+
308+ # THEN: Process method should have been called properly
309+ mock_log.debug.assert_called_with(log_text)
310+ mock_process_clss.assert_called_with('1')
311+
312+ @patch.object(pjlink_test, 'change_status')
313+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
314+ def test_process_command_err1(self, mock_log, mock_change_status):
315+ """
316+ Test ERR1 - Undefined projector function
317+ """
318+ # GIVEN: Test object
319+ pjlink = pjlink_test
320+ log_text = '(127.0.0.1) Projector returned error "ERR1"'
321+ mock_change_status.reset_mock()
322+ mock_log.reset_mock()
323+
324+ # WHEN: process_command called with ERR1
325+ pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNDEFINED])
326+
327+ # THEN: Error should be logged and status_change should be called
328+ mock_change_status.assert_called_once_with(E_UNDEFINED, 'Undefined Command: "CLSS"')
329+ mock_log.error.assert_called_with(log_text)
330+
331+ @patch.object(pjlink_test, 'change_status')
332+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
333+ def test_process_command_err2(self, mock_log, mock_change_status):
334+ """
335+ Test ERR2 - Parameter Error
336+ """
337+ # GIVEN: Test object
338+ pjlink = pjlink_test
339+ log_text = '(127.0.0.1) Projector returned error "ERR2"'
340+ mock_change_status.reset_mock()
341+ mock_log.reset_mock()
342+
343+ # WHEN: process_command called with ERR2
344+ pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PARAMETER])
345+
346+ # THEN: Error should be logged and status_change should be called
347+ mock_change_status.assert_called_once_with(E_PARAMETER)
348+ mock_log.error.assert_called_with(log_text)
349+
350+ @patch.object(pjlink_test, 'change_status')
351+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
352+ def test_process_command_err3(self, mock_log, mock_change_status):
353+ """
354+ Test ERR3 - Unavailable error
355+ """
356+ # GIVEN: Test object
357+ pjlink = pjlink_test
358+ log_text = '(127.0.0.1) Projector returned error "ERR3"'
359+ mock_change_status.reset_mock()
360+ mock_log.reset_mock()
361+
362+ # WHEN: process_command called with ERR3
363+ pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_UNAVAILABLE])
364+
365+ # THEN: Error should be logged and status_change should be called
366+ mock_change_status.assert_called_once_with(E_UNAVAILABLE)
367+ mock_log.error.assert_called_with(log_text)
368+
369+ @patch.object(pjlink_test, 'change_status')
370+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
371+ def test_process_command_err4(self, mock_log, mock_change_status):
372+ """
373+ Test ERR3 - Unavailable error
374+ """
375+ # GIVEN: Test object
376+ pjlink = pjlink_test
377+ log_text = '(127.0.0.1) Projector returned error "ERR4"'
378+ mock_change_status.reset_mock()
379+ mock_log.reset_mock()
380+
381+ # WHEN: process_command called with ERR3
382+ pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_PROJECTOR])
383+
384+ # THEN: Error should be logged and status_change should be called
385+ mock_change_status.assert_called_once_with(E_PROJECTOR)
386+ mock_log.error.assert_called_with(log_text)
387+
388+ @patch.object(pjlink_test, 'projectorAuthentication')
389+ @patch.object(pjlink_test, 'change_status')
390+ @patch.object(pjlink_test, 'disconnect_from_host')
391+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
392+ def test_process_command_erra(self, mock_log, mock_disconnect, mock_change_status, mock_err_authenticate):
393+ """
394+ Test ERRA - Authentication Error
395+ """
396+ # GIVEN: Test object
397+ pjlink = pjlink_test
398+ log_text = '(127.0.0.1) Projector returned error "ERRA"'
399+ mock_change_status.reset_mock()
400+ mock_log.reset_mock()
401+
402+ # WHEN: process_command called with ERRA
403+ pjlink.process_command(cmd='CLSS', data=PJLINK_ERRORS[E_AUTHENTICATION])
404+
405+ # THEN: Error should be logged and several methods should be called
406+ self.assertTrue(mock_disconnect.called, 'disconnect_from_host should have been called')
407+ mock_change_status.assert_called_once_with(E_AUTHENTICATION)
408+ mock_log.error.assert_called_with(log_text)
409+
410+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
411+ def test_process_command_future(self, mock_log):
412+ """
413+ Test command valid but no method to process yet
414+ """
415+ # GIVEN: Test object
416+ pjlink = pjlink_test
417+ log_text = "(127.0.0.1) Unable to process command='CLSS' (Future option)"
418+ mock_log.reset_mock()
419+ # Remove a valid command so we can test valid command but not available yet
420+ pjlink.pjlink_functions.pop('CLSS')
421+
422+ # WHEN: process_command called with an unknown command
423+ with patch.object(pjlink, 'pjlink_functions') as mock_functions:
424+ pjlink.process_command(cmd='CLSS', data='DONT CARE')
425+
426+ # THEN: Error should be logged and no command called
427+ self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
428+ mock_log.warn.assert_called_once_with(log_text)
429+
430+ @patch.object(pjlink_test, 'pjlink_functions')
431+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
432+ def test_process_command_invalid(self, mock_log, mock_functions):
433+ """
434+ Test not a valid command
435+ """
436+ # GIVEN: Test object
437+ pjlink = pjlink_test
438+ mock_functions.reset_mock()
439+ mock_log.reset_mock()
440+
441+ # WHEN: process_command called with an unknown command
442+ pjlink.process_command(cmd='Unknown', data='Dont Care')
443+ log_text = "(127.0.0.1) Ignoring command='Unknown' (Invalid/Unknown)"
444+
445+ # THEN: Error should be logged and no command called
446+ self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
447+ mock_log.error.assert_called_once_with(log_text)
448+
449+ @patch.object(pjlink_test, 'pjlink_functions')
450+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
451+ def test_process_command_ok(self, mock_log, mock_functions):
452+ """
453+ Test command returned success
454+ """
455+ # GIVEN: Test object
456+ pjlink = pjlink_test
457+ mock_functions.reset_mock()
458+ mock_log.reset_mock()
459+
460+ # WHEN: process_command called with an unknown command
461+ pjlink.process_command(cmd='CLSS', data='OK')
462+ log_text = '(127.0.0.1) Command "CLSS" returned OK'
463+
464+ # THEN: Error should be logged and no command called
465+ self.assertFalse(mock_functions.called, 'Should not have gotten to the end of the method')
466+ self.assertEqual(mock_log.debug.call_count, 2, 'log.debug() should have been called twice')
467+ # Although we called it twice, only the last log entry is saved
468+ mock_log.debug.assert_called_with(log_text)
469
470=== modified file 'tests/functional/openlp_core_lib/test_projector_pjlink_commands.py'
471--- tests/functional/openlp_core_lib/test_projector_pjlink_commands.py 2017-08-07 00:08:41 +0000
472+++ tests/functional/openlp_core_lib/test_projector_pjlink_commands.py 2017-08-11 11:14:30 +0000
473@@ -25,16 +25,20 @@
474 from unittest import TestCase
475 from unittest.mock import patch, MagicMock
476
477+import openlp.core.lib.projector.pjlink
478 from openlp.core.lib.projector.pjlink import PJLink
479-from openlp.core.lib.projector.constants import PJLINK_ERST_STATUS, PJLINK_POWR_STATUS, \
480- S_OFF, S_STANDBY, S_ON
481+from openlp.core.lib.projector.constants import ERROR_STRING, PJLINK_ERST_DATA, PJLINK_ERST_STATUS, \
482+ PJLINK_POWR_STATUS, E_WARN, E_ERROR, S_OFF, S_STANDBY, S_ON
483
484 from tests.resources.projector.data import TEST_PIN
485
486 pjlink_test = PJLink(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True)
487
488-# ERST status codes
489-ERST_OK, ERST_WARN, ERST_ERR = '0', '1', '2'
490+# Create a list of ERST positional data so we don't have to redo the same buildup multiple times
491+PJLINK_ERST_POSITIONS = []
492+for pos in range(0, len(PJLINK_ERST_DATA)):
493+ if pos in PJLINK_ERST_DATA:
494+ PJLINK_ERST_POSITIONS.append(PJLINK_ERST_DATA[pos])
495
496
497 class TestPJLinkCommands(TestCase):
498@@ -85,7 +89,25 @@
499 self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called')
500
501 @patch.object(pjlink_test, 'projectorUpdateIcons')
502- def test_projector_process_avmt_closed_muted(self, mock_projectorReceivedData):
503+ def test_projector_process_avmt_bad_data(self, mock_UpdateIcons):
504+ """
505+ Test avmt bad data fail
506+ """
507+ # GIVEN: Test object
508+ pjlink = pjlink_test
509+ pjlink.shutter = True
510+ pjlink.mute = True
511+
512+ # WHEN: Called with an invalid setting
513+ pjlink.process_avmt('36')
514+
515+ # THEN: Shutter should be closed and mute should be True
516+ self.assertTrue(pjlink.shutter, 'Shutter should changed')
517+ self.assertTrue(pjlink.mute, 'Audio should not have changed')
518+ self.assertFalse(mock_UpdateIcons.emit.called, 'Update icons should NOT have been called')
519+
520+ @patch.object(pjlink_test, 'projectorUpdateIcons')
521+ def test_projector_process_avmt_closed_muted(self, mock_UpdateIcons):
522 """
523 Test avmt status shutter closed and mute off
524 """
525@@ -99,12 +121,13 @@
526
527 # THEN: Shutter should be closed and mute should be True
528 self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
529- self.assertTrue(pjlink.mute, 'Audio should be off')
530+ self.assertTrue(pjlink.mute, 'Audio should be muted')
531+ self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
532
533 @patch.object(pjlink_test, 'projectorUpdateIcons')
534- def test_projector_process_avmt_shutter_closed(self, mock_projectorReceivedData):
535+ def test_projector_process_avmt_shutter_closed(self, mock_UpdateIcons):
536 """
537- Test avmt status shutter closed and audio muted
538+ Test avmt status shutter closed and audio unchanged
539 """
540 # GIVEN: Test object
541 pjlink = pjlink_test
542@@ -117,11 +140,12 @@
543 # THEN: Shutter should be True and mute should be False
544 self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed')
545 self.assertTrue(pjlink.mute, 'Audio should not have changed')
546+ self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
547
548 @patch.object(pjlink_test, 'projectorUpdateIcons')
549- def test_projector_process_avmt_audio_muted(self, mock_projectorReceivedData):
550+ def test_projector_process_avmt_audio_muted(self, mock_UpdateIcons):
551 """
552- Test avmt status shutter open and mute on
553+ Test avmt status shutter unchanged and mute on
554 """
555 # GIVEN: Test object
556 pjlink = pjlink_test
557@@ -134,11 +158,12 @@
558 # THEN: Shutter should be closed and mute should be True
559 self.assertTrue(pjlink.shutter, 'Shutter should not have changed')
560 self.assertTrue(pjlink.mute, 'Audio should be off')
561+ self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
562
563 @patch.object(pjlink_test, 'projectorUpdateIcons')
564- def test_projector_process_avmt_open_unmuted(self, mock_projectorReceivedData):
565+ def test_projector_process_avmt_open_unmuted(self, mock_UpdateIcons):
566 """
567- Test avmt status shutter open and mute off off
568+ Test avmt status shutter open and mute off
569 """
570 # GIVEN: Test object
571 pjlink = pjlink_test
572@@ -151,6 +176,7 @@
573 # THEN: Shutter should be closed and mute should be True
574 self.assertFalse(pjlink.shutter, 'Shutter should have been set to open')
575 self.assertFalse(pjlink.mute, 'Audio should be on')
576+ self.assertTrue(mock_UpdateIcons.emit.called, 'Update icons should have been called')
577
578 def test_projector_process_clss_one(self):
579 """
580@@ -164,7 +190,7 @@
581
582 # THEN: Projector class should be set to 1
583 self.assertEqual(pjlink.pjlink_class, '1',
584- 'Projector should have returned class=1')
585+ 'Projector should have set class=1')
586
587 def test_projector_process_clss_two(self):
588 """
589@@ -178,11 +204,11 @@
590
591 # THEN: Projector class should be set to 1
592 self.assertEqual(pjlink.pjlink_class, '2',
593- 'Projector should have returned class=2')
594+ 'Projector should have set class=2')
595
596- def test_projector_process_clss_nonstandard_reply(self):
597+ def test_projector_process_clss_nonstandard_reply_optoma(self):
598 """
599- Bugfix 1550891: CLSS request returns non-standard reply
600+ Bugfix 1550891: CLSS request returns non-standard reply with Optoma projector
601 """
602 # GIVEN: Test object
603 pjlink = pjlink_test
604@@ -194,52 +220,124 @@
605 self.assertEqual(pjlink.pjlink_class, '1',
606 'Non-standard class reply should have set class=1')
607
608+ def test_projector_process_clss_nonstandard_reply_benq(self):
609+ """
610+ Bugfix 1550891: CLSS request returns non-standard reply with BenQ projector
611+ """
612+ # GIVEN: Test object
613+ pjlink = pjlink_test
614+
615+ # WHEN: Process non-standard reply
616+ pjlink.process_clss('Version2')
617+
618+ # THEN: Projector class should be set with proper value
619+ # NOTE: At this time BenQ is Class 1, but we're trying a different value to verify
620+ self.assertEqual(pjlink.pjlink_class, '2',
621+ 'Non-standard class reply should have set class=2')
622+
623+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
624+ def test_projector_process_clss_invalid_nan(self, mock_log):
625+ """
626+ Test CLSS reply has no class number
627+ """
628+ # GIVEN: Test object
629+ pjlink = pjlink_test
630+
631+ # WHEN: Process invalid reply
632+ pjlink.process_clss('Z')
633+ log_warn_text = "(127.0.0.1) NAN clss version reply 'Z' - defaulting to class '1'"
634+
635+ # THEN: Projector class should be set with default value
636+ self.assertEqual(pjlink.pjlink_class, '1',
637+ 'Non-standard class reply should have set class=1')
638+ mock_log.error.assert_called_once_with(log_warn_text)
639+
640+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
641+ def test_projector_process_clss_invalid_no_version(self, mock_log):
642+ """
643+ Test CLSS reply has no class number
644+ """
645+ # GIVEN: Test object
646+ pjlink = pjlink_test
647+
648+ # WHEN: Process invalid reply
649+ pjlink.process_clss('Invalid')
650+ log_warn_text = "(127.0.0.1) No numbers found in class version reply 'Invalid' - defaulting to class '1'"
651+
652+ # THEN: Projector class should be set with default value
653+ self.assertEqual(pjlink.pjlink_class, '1',
654+ 'Non-standard class reply should have set class=1')
655+ mock_log.error.assert_called_once_with(log_warn_text)
656+
657 def test_projector_process_erst_all_ok(self):
658 """
659 Test test_projector_process_erst_all_ok
660 """
661 # GIVEN: Test object
662 pjlink = pjlink_test
663- chk_test = ERST_OK
664+ chk_test = PJLINK_ERST_STATUS['OK']
665+ chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
666
667 # WHEN: process_erst with no errors
668- pjlink.process_erst('{fan}{lamp}{temp}{cover}{filter}{other}'.format(fan=chk_test,
669- lamp=chk_test,
670- temp=chk_test,
671- cover=chk_test,
672- filter=chk_test,
673- other=chk_test))
674+ pjlink.process_erst(chk_param)
675
676- # PJLink instance errors should be None
677+ # THEN: PJLink instance errors should be None
678 self.assertIsNone(pjlink.projector_errors, 'projector_errors should have been set to None')
679
680+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
681+ def test_projector_process_erst_data_invalid_length(self, mock_log):
682+ """
683+ Test test_projector_process_erst_data_invalid_length
684+ """
685+ # GIVEN: Test object
686+ pjlink = pjlink_test
687+ pjlink.projector_errors = None
688+ log_warn_text = "127.0.0.1) Invalid error status response '11111111': length != 6"
689+
690+ # WHEN: process_erst called with invalid data (too many values
691+ pjlink.process_erst('11111111')
692+
693+ # THEN: pjlink.projector_errors should be empty and warning logged
694+ self.assertIsNone(pjlink.projector_errors, 'There should be no errors')
695+ self.assertTrue(mock_log.warn.called, 'Warning should have been logged')
696+ mock_log.warn.assert_called_once_with(log_warn_text)
697+
698+ @patch.object(openlp.core.lib.projector.pjlink, 'log')
699+ def test_projector_process_erst_data_invalid_nan(self, mock_log):
700+ """
701+ Test test_projector_process_erst_data_invalid_nan
702+ """
703+ # GIVEN: Test object
704+ pjlink = pjlink_test
705+ pjlink.projector_errors = None
706+ log_warn_text = "(127.0.0.1) Invalid error status response '1111Z1'"
707+
708+ # WHEN: process_erst called with invalid data (too many values
709+ pjlink.process_erst('1111Z1')
710+
711+ # THEN: pjlink.projector_errors should be empty and warning logged
712+ self.assertIsNone(pjlink.projector_errors, 'There should be no errors')
713+ self.assertTrue(mock_log.warn.called, 'Warning should have been logged')
714+ mock_log.warn.assert_called_once_with(log_warn_text)
715+
716 def test_projector_process_erst_all_warn(self):
717 """
718 Test test_projector_process_erst_all_warn
719 """
720 # GIVEN: Test object
721 pjlink = pjlink_test
722- chk_test = ERST_WARN
723- chk_code = PJLINK_ERST_STATUS[chk_test]
724- chk_value = {'Fan': chk_code,
725- 'Lamp': chk_code,
726- 'Temperature': chk_code,
727- 'Cover': chk_code,
728- 'Filter': chk_code,
729- 'Other': chk_code
730- }
731+ chk_test = PJLINK_ERST_STATUS[E_WARN]
732+ chk_string = ERROR_STRING[E_WARN]
733+ chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
734
735 # WHEN: process_erst with status set to WARN
736- pjlink.process_erst('{fan}{lamp}{temp}{cover}{filter}{other}'.format(fan=chk_test,
737- lamp=chk_test,
738- temp=chk_test,
739- cover=chk_test,
740- filter=chk_test,
741- other=chk_test))
742+ pjlink.process_erst(chk_param)
743
744- # PJLink instance errors should match chk_value
745- self.assertEqual(pjlink.projector_errors, chk_value,
746- 'projector_errors should have been set to all {err}'.format(err=chk_code))
747+ # THEN: PJLink instance errors should match chk_value
748+ for chk in pjlink.projector_errors:
749+ self.assertEqual(pjlink.projector_errors[chk], chk_string,
750+ "projector_errors['{chk}'] should have been set to {err}".format(chk=chk,
751+ err=chk_string))
752
753 def test_projector_process_erst_all_error(self):
754 """
755@@ -247,27 +345,45 @@
756 """
757 # GIVEN: Test object
758 pjlink = pjlink_test
759- chk_test = ERST_ERR
760- chk_code = PJLINK_ERST_STATUS[chk_test]
761- chk_value = {'Fan': chk_code,
762- 'Lamp': chk_code,
763- 'Temperature': chk_code,
764- 'Cover': chk_code,
765- 'Filter': chk_code,
766- 'Other': chk_code
767- }
768-
769- # WHEN: process_erst with status set to ERROR
770- pjlink.process_erst('{fan}{lamp}{temp}{cover}{filter}{other}'.format(fan=chk_test,
771- lamp=chk_test,
772- temp=chk_test,
773- cover=chk_test,
774- filter=chk_test,
775- other=chk_test))
776-
777- # PJLink instance errors should be set to chk_value
778- self.assertEqual(pjlink.projector_errors, chk_value,
779- 'projector_errors should have been set to all {err}'.format(err=chk_code))
780+ chk_test = PJLINK_ERST_STATUS[E_ERROR]
781+ chk_string = ERROR_STRING[E_ERROR]
782+ chk_param = chk_test * len(PJLINK_ERST_POSITIONS)
783+
784+ # WHEN: process_erst with status set to WARN
785+ pjlink.process_erst(chk_param)
786+
787+ # THEN: PJLink instance errors should match chk_value
788+ for chk in pjlink.projector_errors:
789+ self.assertEqual(pjlink.projector_errors[chk], chk_string,
790+ "projector_errors['{chk}'] should have been set to {err}".format(chk=chk,
791+ err=chk_string))
792+
793+ def test_projector_process_erst_warn_cover_only(self):
794+ """
795+ Test test_projector_process_erst_warn_cover_only
796+ """
797+ # GIVEN: Test object
798+ pjlink = pjlink_test
799+ chk_test = PJLINK_ERST_STATUS[E_WARN]
800+ chk_string = ERROR_STRING[E_WARN]
801+ pos = PJLINK_ERST_DATA['COVER']
802+ build_chk = []
803+ for check in range(0, len(PJLINK_ERST_POSITIONS)):
804+ if check == pos:
805+ build_chk.append(chk_test)
806+ else:
807+ build_chk.append(PJLINK_ERST_STATUS['OK'])
808+ chk_param = ''.join(build_chk)
809+
810+ # WHEN: process_erst with cover only set to WARN and all others set to OK
811+ pjlink.process_erst(chk_param)
812+
813+ # THEN: Only COVER should have an error
814+ self.assertEqual(len(pjlink.projector_errors), 1, 'projector_errors should only have 1 error')
815+ self.assertTrue(('Cover' in pjlink.projector_errors), 'projector_errors should have an error for "Cover"')
816+ self.assertEqual(pjlink.projector_errors['Cover'],
817+ chk_string,
818+ 'projector_errors["Cover"] should have error "{err}"'.format(err=chk_string))
819
820 def test_projector_process_inpt(self):
821 """