Merge lp:~alisonken1/openlp/pjlink2-h into lp:openlp
- pjlink2-h
- Merge into trunk
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 |
Related bugs: |
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.
-------
lp:~alisonken1/openlp/pjlink2-h (revision 2757)
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[FAILURE] https:/
Tim Bentley (trb143) : | # |
Tomas Groth (tomasgroth) : | # |
Preview Diff
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 | """ |