Merge lp:~alisonken1/openlp/2.4-bug-1593883-projector-authentication into lp:openlp/2.4
- 2.4-bug-1593883-projector-authentication
- Merge into 2.4
Status: | Merged |
---|---|
Merged at revision: | 2639 |
Proposed branch: | lp:~alisonken1/openlp/2.4-bug-1593883-projector-authentication |
Merge into: | lp:openlp/2.4 |
Diff against target: |
1082 lines (+452/-98) 5 files modified
openlp/core/common/__init__.py (+2/-1) openlp/core/lib/projector/constants.py (+5/-1) openlp/core/lib/projector/pjlink1.py (+125/-83) tests/functional/openlp_core_common/test_projector_utilities.py (+3/-4) tests/functional/openlp_core_lib/test_projector_pjlink1.py (+317/-9) |
To merge this branch: | bzr merge lp:~alisonken1/openlp/2.4-bug-1593883-projector-authentication |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raoul Snyman | Approve | ||
Tomas Groth | Approve | ||
Review via email: mp+297820@code.launchpad.net |
Commit message
Bugfix 1593882 and 1593883 - projector authorization backport
Description of the change
Bugfix 1593882 and 1593883 - projector authorization backport
- Fix exception when authenticated connection requested and pin is None
- Fix pjlink authentication (use python hash instead of qt hash)
- Fix md5_hash functions
- Fix qmd5_hash functions
- Added tests for bugfixes
-------
lp:~alisonken1/openlp/2.4-bug-1593883-projector-authentication (revision 2637)
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
[SUCCESS] https:/
Ken Roberts (alisonken1) wrote : | # |
Sorry about the string format changes - forgot I had changed that when I copied the file.
The projector code is pretty well exercised when I run it at church, so I believe I would have seen an issue if the formatting was incorrect.
Still adding tests as I make merge requests.
Raoul Snyman (raoul-snyman) wrote : | # |
I agree with Tomas about testing the strings out. Even if it's just going through everything with your emulator. Either that or write some tests :-P
Ken Roberts (alisonken1) wrote : | # |
I tested the trunk code at church with the Eiki XL200 projector. I just finished testing the 2.4 backport code using the emulator. All of the buttons, both the projector and input source edit wizards, and reviewed the log entries. The only thing I found was a minor issue with port validation (the network port field being empty) that will be included in a later fix.
Tomas Groth (tomasgroth) : | # |
Raoul Snyman (raoul-snyman) : | # |
Preview Diff
1 | === modified file 'openlp/core/common/__init__.py' |
2 | --- openlp/core/common/__init__.py 2015-12-31 22:46:06 +0000 |
3 | +++ openlp/core/common/__init__.py 2016-06-18 03:24:30 +0000 |
4 | @@ -219,7 +219,8 @@ |
5 | log.debug('qmd5_hash(salt="%s"' % salt) |
6 | hash_obj = QHash(QHash.Md5) |
7 | hash_obj.addData(salt) |
8 | - hash_obj.addData(data) |
9 | + if data: |
10 | + hash_obj.addData(data) |
11 | hash_value = hash_obj.result().toHex() |
12 | log.debug('qmd5_hash() returning "%s"' % hash_value) |
13 | return hash_value.data() |
14 | |
15 | === modified file 'openlp/core/lib/projector/constants.py' |
16 | --- openlp/core/lib/projector/constants.py 2015-12-31 22:46:06 +0000 |
17 | +++ openlp/core/lib/projector/constants.py 2016-06-18 03:24:30 +0000 |
18 | @@ -297,7 +297,11 @@ |
19 | PJLINK_POWR_STATUS = {'0': S_STANDBY, |
20 | '1': S_ON, |
21 | '2': S_COOLDOWN, |
22 | - '3': S_WARMUP} |
23 | + '3': S_WARMUP, |
24 | + S_STANDBY: '0', |
25 | + S_ON: '1', |
26 | + S_COOLDOWN: '2', |
27 | + S_WARMUP: '3'} |
28 | |
29 | PJLINK_DEFAULT_SOURCES = {'1': translate('OpenLP.DB', 'RGB'), |
30 | '2': translate('OpenLP.DB', 'Video'), |
31 | |
32 | === modified file 'openlp/core/lib/projector/pjlink1.py' |
33 | --- openlp/core/lib/projector/pjlink1.py 2016-03-03 17:15:58 +0000 |
34 | +++ openlp/core/lib/projector/pjlink1.py 2016-06-18 03:24:30 +0000 |
35 | @@ -49,7 +49,7 @@ |
36 | from PyQt5.QtCore import pyqtSignal, pyqtSlot |
37 | from PyQt5.QtNetwork import QAbstractSocket, QTcpSocket |
38 | |
39 | -from openlp.core.common import translate, qmd5_hash |
40 | +from openlp.core.common import translate, md5_hash |
41 | from openlp.core.lib.projector.constants import * |
42 | |
43 | # Shortcuts |
44 | @@ -58,7 +58,7 @@ |
45 | |
46 | PJLINK_PREFIX = '%' |
47 | PJLINK_CLASS = '1' |
48 | -PJLINK_HEADER = '%s%s' % (PJLINK_PREFIX, PJLINK_CLASS) |
49 | +PJLINK_HEADER = '{prefix}{linkclass}'.format(prefix=PJLINK_PREFIX, linkclass=PJLINK_CLASS) |
50 | PJLINK_SUFFIX = CR |
51 | |
52 | |
53 | @@ -91,7 +91,7 @@ |
54 | :param poll_time: Time (in seconds) to poll connected projector |
55 | :param socket_timeout: Time (in seconds) to abort the connection if no response |
56 | """ |
57 | - log.debug('PJlink(args="%s" kwargs="%s")' % (args, kwargs)) |
58 | + log.debug('PJlink(args={args} kwargs={kwargs})'.format(args=args, kwargs=kwargs)) |
59 | self.name = name |
60 | self.ip = ip |
61 | self.port = port |
62 | @@ -147,7 +147,7 @@ |
63 | """ |
64 | Reset projector-specific information to default |
65 | """ |
66 | - log.debug('(%s) reset_information() connect status is %s' % (self.ip, self.state())) |
67 | + log.debug('({ip}) reset_information() connect status is {state}'.format(ip=self.ip, state=self.state())) |
68 | self.power = S_OFF |
69 | self.pjlink_name = None |
70 | self.manufacturer = None |
71 | @@ -160,8 +160,10 @@ |
72 | self.source = None |
73 | self.other_info = None |
74 | if hasattr(self, 'timer'): |
75 | + log.debug('({ip}): Calling timer.stop()'.format(ip=self.ip)) |
76 | self.timer.stop() |
77 | if hasattr(self, 'socket_timer'): |
78 | + log.debug('({ip}): Calling socket_timer.stop()'.format(ip=self.ip)) |
79 | self.socket_timer.stop() |
80 | self.send_queue = [] |
81 | self.send_busy = False |
82 | @@ -170,7 +172,7 @@ |
83 | """ |
84 | Connects signals to methods when thread is started. |
85 | """ |
86 | - log.debug('(%s) Thread starting' % self.ip) |
87 | + log.debug('({ip}) Thread starting'.format(ip=self.ip)) |
88 | self.i_am_running = True |
89 | self.connected.connect(self.check_login) |
90 | self.disconnected.connect(self.disconnect_from_host) |
91 | @@ -180,7 +182,7 @@ |
92 | """ |
93 | Cleanups when thread is stopped. |
94 | """ |
95 | - log.debug('(%s) Thread stopped' % self.ip) |
96 | + log.debug('({ip}) Thread stopped'.format(ip=self.ip)) |
97 | try: |
98 | self.connected.disconnect(self.check_login) |
99 | except TypeError: |
100 | @@ -206,7 +208,7 @@ |
101 | Aborts connection and closes socket in case of brain-dead projectors. |
102 | Should normally be called by socket_timer(). |
103 | """ |
104 | - log.debug('(%s) socket_abort() - Killing connection' % self.ip) |
105 | + log.debug('({ip}) socket_abort() - Killing connection'.format(ip=self.ip)) |
106 | self.disconnect_from_host(abort=True) |
107 | |
108 | def poll_loop(self): |
109 | @@ -216,7 +218,7 @@ |
110 | """ |
111 | if self.state() != self.ConnectedState: |
112 | return |
113 | - log.debug('(%s) Updating projector status' % self.ip) |
114 | + log.debug('({ip}) Updating projector status'.format(ip=self.ip)) |
115 | # Reset timer in case we were called from a set command |
116 | if self.timer.interval() < self.poll_time: |
117 | # Reset timer to 5 seconds |
118 | @@ -276,11 +278,17 @@ |
119 | self.status_connect = S_CONNECTED |
120 | self.projector_status = status |
121 | (status_code, status_message) = self._get_status(self.status_connect) |
122 | - log.debug('(%s) status_connect: %s: %s' % (self.ip, status_code, status_message if msg is None else msg)) |
123 | + log.debug('({ip}) status_connect: {code}: "{message}"'.format(ip=self.ip, |
124 | + code=status_code, |
125 | + message=status_message if msg is None else msg)) |
126 | (status_code, status_message) = self._get_status(self.projector_status) |
127 | - log.debug('(%s) projector_status: %s: %s' % (self.ip, status_code, status_message if msg is None else msg)) |
128 | + log.debug('({ip}) projector_status: {code}: "{message}"'.format(ip=self.ip, |
129 | + code=status_code, |
130 | + message=status_message if msg is None else msg)) |
131 | (status_code, status_message) = self._get_status(self.error_status) |
132 | - log.debug('(%s) error_status: %s: %s' % (self.ip, status_code, status_message if msg is None else msg)) |
133 | + log.debug('({ip}) error_status: {code}: "{message}"'.format(ip=self.ip, |
134 | + code=status_code, |
135 | + message=status_message if msg is None else msg)) |
136 | self.changeStatus.emit(self.ip, status, message) |
137 | |
138 | @pyqtSlot() |
139 | @@ -289,29 +297,31 @@ |
140 | Processes the initial connection and authentication (if needed). |
141 | Starts poll timer if connection is established. |
142 | |
143 | + NOTE: Qt md5 hash function doesn't work with projector authentication. Use the python md5 hash function. |
144 | + |
145 | :param data: Optional data if called from another routine |
146 | """ |
147 | - log.debug('(%s) check_login(data="%s")' % (self.ip, data)) |
148 | + log.debug('({ip}) check_login(data="{data}")'.format(ip=self.ip, data=data)) |
149 | if data is None: |
150 | # Reconnected setup? |
151 | if not self.waitForReadyRead(2000): |
152 | # Possible timeout issue |
153 | - log.error('(%s) Socket timeout waiting for login' % self.ip) |
154 | + log.error('({ip}) Socket timeout waiting for login'.format(ip=self.ip)) |
155 | self.change_status(E_SOCKET_TIMEOUT) |
156 | return |
157 | read = self.readLine(self.maxSize) |
158 | dontcare = self.readLine(self.maxSize) # Clean out the trailing \r\n |
159 | if read is None: |
160 | - log.warn('(%s) read is None - socket error?' % self.ip) |
161 | + log.warn('({ip}) read is None - socket error?'.format(ip=self.ip)) |
162 | return |
163 | elif len(read) < 8: |
164 | - log.warn('(%s) Not enough data read)' % self.ip) |
165 | + log.warn('({ip}) Not enough data read)'.format(ip=self.ip)) |
166 | return |
167 | data = decode(read, 'ascii') |
168 | # Possibility of extraneous data on input when reading. |
169 | # Clean out extraneous characters in buffer. |
170 | dontcare = self.readLine(self.maxSize) |
171 | - log.debug('(%s) check_login() read "%s"' % (self.ip, data.strip())) |
172 | + log.debug('({ip}) check_login() read "{data}"'.format(ip=self.ip, data=data.strip())) |
173 | # At this point, we should only have the initial login prompt with |
174 | # possible authentication |
175 | # PJLink initial login will be: |
176 | @@ -326,26 +336,35 @@ |
177 | else: |
178 | # Process initial connection |
179 | data_check = data.strip().split(' ') |
180 | - log.debug('(%s) data_check="%s"' % (self.ip, data_check)) |
181 | + log.debug('({ip}) data_check="{data}"'.format(ip=self.ip, data=data_check)) |
182 | # Check for projector reporting an error |
183 | if data_check[1].upper() == 'ERRA': |
184 | # Authentication error |
185 | self.disconnect_from_host() |
186 | self.change_status(E_AUTHENTICATION) |
187 | - log.debug('(%s) emitting projectorAuthentication() signal' % self.name) |
188 | + log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.name)) |
189 | return |
190 | elif data_check[1] == '0' and self.pin is not None: |
191 | # Pin set and no authentication needed |
192 | + log.warning('({ip}) Regular connection but PIN set'.format(ip=self.name)) |
193 | self.disconnect_from_host() |
194 | self.change_status(E_AUTHENTICATION) |
195 | - log.debug('(%s) emitting projectorNoAuthentication() signal' % self.name) |
196 | + log.debug('({ip}) Emitting projectorNoAuthentication() signal'.format(ip=self.name)) |
197 | self.projectorNoAuthentication.emit(self.name) |
198 | return |
199 | elif data_check[1] == '1': |
200 | # Authenticated login with salt |
201 | - log.debug('(%s) Setting hash with salt="%s"' % (self.ip, data_check[2])) |
202 | - log.debug('(%s) pin="%s"' % (self.ip, self.pin)) |
203 | - salt = qmd5_hash(salt=data_check[2].encode('ascii'), data=self.pin.encode('ascii')) |
204 | + if self.pin is None: |
205 | + log.warning('({ip}) Authenticated connection but no pin set'.format(ip=self.name)) |
206 | + self.disconnect_from_host() |
207 | + self.change_status(E_AUTHENTICATION) |
208 | + log.debug('({ip}) Emitting projectorAuthentication() signal'.format(ip=self.name)) |
209 | + self.projectorAuthentication.emit(self.name) |
210 | + return |
211 | + else: |
212 | + log.debug('({ip}) Setting hash with salt="{data}"'.format(ip=self.ip, data=data_check[2])) |
213 | + log.debug('({ip}) pin="{data}"'.format(ip=self.ip, data=self.pin)) |
214 | + salt = md5_hash(salt=data_check[2].encode('ascii'), data=self.pin.encode('ascii')) |
215 | else: |
216 | salt = None |
217 | # We're connected at this point, so go ahead and do regular I/O |
218 | @@ -355,7 +374,7 @@ |
219 | self.send_command(cmd='CLSS', salt=salt) |
220 | self.waitForReadyRead() |
221 | if (not self.no_poll) and (self.state() == self.ConnectedState): |
222 | - log.debug('(%s) Starting timer' % self.ip) |
223 | + log.debug('({ip}) Starting timer'.format(ip=self.ip)) |
224 | self.timer.setInterval(2000) # Set 2 seconds for initial information |
225 | self.timer.start() |
226 | |
227 | @@ -364,15 +383,15 @@ |
228 | """ |
229 | Socket interface to retrieve data. |
230 | """ |
231 | - log.debug('(%s) get_data(): Reading data' % self.ip) |
232 | + log.debug('({ip}) get_data(): Reading data'.format(ip=self.ip)) |
233 | if self.state() != self.ConnectedState: |
234 | - log.debug('(%s) get_data(): Not connected - returning' % self.ip) |
235 | + log.debug('({ip}) get_data(): Not connected - returning'.format(ip=self.ip)) |
236 | self.send_busy = False |
237 | return |
238 | read = self.readLine(self.maxSize) |
239 | if read == -1: |
240 | # No data available |
241 | - log.debug('(%s) get_data(): No data available (-1)' % self.ip) |
242 | + log.debug('({ip}) get_data(): No data available (-1)'.format(ip=self.ip)) |
243 | self.send_busy = False |
244 | self.projectorReceivedData.emit() |
245 | return |
246 | @@ -382,11 +401,11 @@ |
247 | data = data_in.strip() |
248 | if len(data) < 7: |
249 | # Not enough data for a packet |
250 | - log.debug('(%s) get_data(): Packet length < 7: "%s"' % (self.ip, data)) |
251 | + log.debug('({ip}) get_data(): Packet length < 7: "{data}"'.format(ip=self.ip, data=data)) |
252 | self.send_busy = False |
253 | self.projectorReceivedData.emit() |
254 | return |
255 | - log.debug('(%s) get_data(): Checking new data "%s"' % (self.ip, data)) |
256 | + log.debug('({ip}) get_data(): Checking new data "{data}"'.format(ip=self.ip, data=data)) |
257 | if data.upper().startswith('PJLINK'): |
258 | # Reconnected from remote host disconnect ? |
259 | self.check_login(data) |
260 | @@ -394,7 +413,7 @@ |
261 | self.projectorReceivedData.emit() |
262 | return |
263 | elif '=' not in data: |
264 | - log.warn('(%s) get_data(): Invalid packet received' % self.ip) |
265 | + log.warn('({ip}) get_data(): Invalid packet received'.format(ip=self.ip)) |
266 | self.send_busy = False |
267 | self.projectorReceivedData.emit() |
268 | return |
269 | @@ -402,15 +421,15 @@ |
270 | try: |
271 | (prefix, class_, cmd, data) = (data_split[0][0], data_split[0][1], data_split[0][2:], data_split[1]) |
272 | except ValueError as e: |
273 | - log.warn('(%s) get_data(): Invalid packet - expected header + command + data' % self.ip) |
274 | - log.warn('(%s) get_data(): Received data: "%s"' % (self.ip, read)) |
275 | + log.warn('({ip}) get_data(): Invalid packet - expected header + command + data'.format(ip=self.ip)) |
276 | + log.warn('({ip}) get_data(): Received data: "{data}"'.format(ip=self.ip, data=data_in.strip())) |
277 | self.change_status(E_INVALID_DATA) |
278 | self.send_busy = False |
279 | self.projectorReceivedData.emit() |
280 | return |
281 | |
282 | if not (self.pjlink_class in PJLINK_VALID_CMD and cmd in PJLINK_VALID_CMD[self.pjlink_class]): |
283 | - log.warn('(%s) get_data(): Invalid packet - unknown command "%s"' % (self.ip, cmd)) |
284 | + log.warn('({ip}) get_data(): Invalid packet - unknown command "{data}"'.format(ip=self.ip, data=cmd)) |
285 | self.send_busy = False |
286 | self.projectorReceivedData.emit() |
287 | return |
288 | @@ -424,7 +443,7 @@ |
289 | |
290 | :param err: Error code |
291 | """ |
292 | - log.debug('(%s) get_error(err=%s): %s' % (self.ip, err, self.errorString())) |
293 | + log.debug('({ip}) get_error(err={error}): {data}'.format(ip=self.ip, error=err, data=self.errorString())) |
294 | if err <= 18: |
295 | # QSocket errors. Redefined in projector.constants so we don't mistake |
296 | # them for system errors |
297 | @@ -453,32 +472,35 @@ |
298 | :param queue: Option to force add to queue rather than sending directly |
299 | """ |
300 | if self.state() != self.ConnectedState: |
301 | - log.warn('(%s) send_command(): Not connected - returning' % self.ip) |
302 | + log.warn('({ip}) send_command(): Not connected - returning'.format(ip=self.ip)) |
303 | self.send_queue = [] |
304 | return |
305 | self.projectorNetwork.emit(S_NETWORK_SENDING) |
306 | - log.debug('(%s) send_command(): Building cmd="%s" opts="%s" %s' % (self.ip, |
307 | - cmd, |
308 | - opts, |
309 | - '' if salt is None else 'with hash')) |
310 | - if salt is None: |
311 | - out = '%s%s %s%s' % (PJLINK_HEADER, cmd, opts, CR) |
312 | - else: |
313 | - out = '%s%s%s %s%s' % (salt, PJLINK_HEADER, cmd, opts, CR) |
314 | + log.debug('({ip}) send_command(): Building cmd="{command}" opts="{data}"{salt}'.format(ip=self.ip, |
315 | + command=cmd, |
316 | + data=opts, |
317 | + salt='' if salt is None |
318 | + else ' with hash')) |
319 | + out = '{salt}{header}{command} {options}{suffix}'.format(salt="" if salt is None else salt, |
320 | + header=PJLINK_HEADER, |
321 | + command=cmd, |
322 | + options=opts, |
323 | + suffix=CR) |
324 | if out in self.send_queue: |
325 | # Already there, so don't add |
326 | - log.debug('(%s) send_command(out="%s") Already in queue - skipping' % (self.ip, out.strip())) |
327 | + log.debug('({ip}) send_command(out="{data}") Already in queue - skipping'.format(ip=self.ip, |
328 | + data=out.strip())) |
329 | elif not queue and len(self.send_queue) == 0: |
330 | # Nothing waiting to send, so just send it |
331 | - log.debug('(%s) send_command(out="%s") Sending data' % (self.ip, out.strip())) |
332 | + log.debug('({ip}) send_command(out="{data}") Sending data'.format(ip=self.ip, data=out.strip())) |
333 | return self._send_command(data=out) |
334 | else: |
335 | - log.debug('(%s) send_command(out="%s") adding to queue' % (self.ip, out.strip())) |
336 | + log.debug('({ip}) send_command(out="{data}") adding to queue'.format(ip=self.ip, data=out.strip())) |
337 | self.send_queue.append(out) |
338 | self.projectorReceivedData.emit() |
339 | - log.debug('(%s) send_command(): send_busy is %s' % (self.ip, self.send_busy)) |
340 | + log.debug('({ip}) send_command(): send_busy is {data}'.format(ip=self.ip, data=self.send_busy)) |
341 | if not self.send_busy: |
342 | - log.debug('(%s) send_command() calling _send_string()') |
343 | + log.debug('({ip}) send_command() calling _send_string()'.format(ip=self.ip)) |
344 | self._send_command() |
345 | |
346 | @pyqtSlot() |
347 | @@ -488,10 +510,10 @@ |
348 | |
349 | :param data: Immediate data to send |
350 | """ |
351 | - log.debug('(%s) _send_string()' % self.ip) |
352 | - log.debug('(%s) _send_string(): Connection status: %s' % (self.ip, self.state())) |
353 | + log.debug('({ip}) _send_string()'.format(ip=self.ip)) |
354 | + log.debug('({ip}) _send_string(): Connection status: {data}'.format(ip=self.ip, data=self.state())) |
355 | if self.state() != self.ConnectedState: |
356 | - log.debug('(%s) _send_string() Not connected - abort' % self.ip) |
357 | + log.debug('({ip}) _send_string() Not connected - abort'.format(ip=self.ip)) |
358 | self.send_queue = [] |
359 | self.send_busy = False |
360 | return |
361 | @@ -500,30 +522,26 @@ |
362 | return |
363 | if data is not None: |
364 | out = data |
365 | - log.debug('(%s) _send_string(data=%s)' % (self.ip, out.strip())) |
366 | + log.debug('({ip}) _send_string(data="{data}")'.format(ip=self.ip, data=out.strip())) |
367 | elif len(self.send_queue) != 0: |
368 | out = self.send_queue.pop(0) |
369 | - log.debug('(%s) _send_string(queued data=%s)' % (self.ip, out.strip())) |
370 | + log.debug('({ip}) _send_string(queued data="{data}"%s)'.format(ip=self.ip, data=out.strip())) |
371 | else: |
372 | # No data to send |
373 | - log.debug('(%s) _send_string(): No data to send' % self.ip) |
374 | + log.debug('({ip}) _send_string(): No data to send'.format(ip=self.ip)) |
375 | self.send_busy = False |
376 | return |
377 | self.send_busy = True |
378 | - log.debug('(%s) _send_string(): Sending "%s"' % (self.ip, out.strip())) |
379 | - log.debug('(%s) _send_string(): Queue = %s' % (self.ip, self.send_queue)) |
380 | + log.debug('({ip}) _send_string(): Sending "{data}"'.format(ip=self.ip, data=out.strip())) |
381 | + log.debug('({ip}) _send_string(): Queue = {data}'.format(ip=self.ip, data=self.send_queue)) |
382 | self.socket_timer.start() |
383 | - try: |
384 | - self.projectorNetwork.emit(S_NETWORK_SENDING) |
385 | - sent = self.write(out.encode('ascii')) |
386 | - self.waitForBytesWritten(2000) # 2 seconds should be enough |
387 | - if sent == -1: |
388 | - # Network error? |
389 | - self.change_status(E_NETWORK, |
390 | - translate('OpenLP.PJLink1', 'Error while sending data to projector')) |
391 | - except SocketError as e: |
392 | - self.disconnect_from_host(abort=True) |
393 | - self.changeStatus(E_NETWORK, '%s : %s' % (e.error(), e.errorString())) |
394 | + self.projectorNetwork.emit(S_NETWORK_SENDING) |
395 | + sent = self.write(out.encode('ascii')) |
396 | + self.waitForBytesWritten(2000) # 2 seconds should be enough |
397 | + if sent == -1: |
398 | + # Network error? |
399 | + self.change_status(E_NETWORK, |
400 | + translate('OpenLP.PJLink1', 'Error while sending data to projector')) |
401 | |
402 | def process_command(self, cmd, data): |
403 | """ |
404 | @@ -532,19 +550,21 @@ |
405 | :param cmd: Command to process |
406 | :param data: Data being processed |
407 | """ |
408 | - log.debug('(%s) Processing command "%s"' % (self.ip, cmd)) |
409 | + log.debug('({ip}) Processing command "{data}"'.format(ip=self.ip, data=cmd)) |
410 | if data in PJLINK_ERRORS: |
411 | # Oops - projector error |
412 | + log.error('({ip}) Projector returned error "{data}"'.format(ip=self.ip, data=data)) |
413 | if data.upper() == 'ERRA': |
414 | # Authentication error |
415 | self.disconnect_from_host() |
416 | self.change_status(E_AUTHENTICATION) |
417 | - log.debug('(%s) emitting projectorAuthentication() signal' % self.ip) |
418 | + log.debug('({ip}) emitting projectorAuthentication() signal'.format(ip=self.ip)) |
419 | self.projectorAuthentication.emit(self.name) |
420 | elif data.upper() == 'ERR1': |
421 | # Undefined command |
422 | - self.change_status(E_UNDEFINED, '%s "%s"' % |
423 | - (translate('OpenLP.PJLink1', 'Undefined command:'), cmd)) |
424 | + self.change_status(E_UNDEFINED, '{error} "{data}"'.format(error=translate('OpenLP.PJLink1', |
425 | + 'Undefined command:'), |
426 | + data=cmd)) |
427 | elif data.upper() == 'ERR2': |
428 | # Invalid parameter |
429 | self.change_status(E_PARAMETER) |
430 | @@ -559,7 +579,7 @@ |
431 | return |
432 | # Command succeeded - no extra information |
433 | elif data.upper() == 'OK': |
434 | - log.debug('(%s) Command returned OK' % self.ip) |
435 | + log.debug('({ip}) Command returned OK'.format(ip=self.ip)) |
436 | # A command returned successfully, recheck data |
437 | self.send_busy = False |
438 | self.projectorReceivedData.emit() |
439 | @@ -568,7 +588,7 @@ |
440 | if cmd in self.PJLINK1_FUNC: |
441 | self.PJLINK1_FUNC[cmd](data) |
442 | else: |
443 | - log.warn('(%s) Invalid command %s' % (self.ip, cmd)) |
444 | + log.warn('({ip}) Invalid command {data}'.format(ip=self.ip, data=cmd)) |
445 | self.send_busy = False |
446 | self.projectorReceivedData.emit() |
447 | |
448 | @@ -587,7 +607,7 @@ |
449 | fill = {'Hours': int(data_dict[0]), 'On': False if data_dict[1] == '0' else True} |
450 | except ValueError: |
451 | # In case of invalid entry |
452 | - log.warn('(%s) process_lamp(): Invalid data "%s"' % (self.ip, data)) |
453 | + log.warn('({ip}) process_lamp(): Invalid data "{data}"'.format(ip=self.ip, data=data)) |
454 | return |
455 | lamps.append(fill) |
456 | data_dict.pop(0) # Remove lamp hours |
457 | @@ -614,7 +634,7 @@ |
458 | self.send_command('INST') |
459 | else: |
460 | # Log unknown status response |
461 | - log.warn('Unknown power response: %s' % data) |
462 | + log.warn('({ip}) Unknown power response: {data}'.format(ip=self.ip, data=data)) |
463 | return |
464 | |
465 | def process_avmt(self, data): |
466 | @@ -639,7 +659,7 @@ |
467 | shutter = True |
468 | mute = True |
469 | else: |
470 | - log.warn('Unknown shutter response: %s' % data) |
471 | + log.warn('({ip}) Unknown shutter response: {data}'.format(ip=self.ip, data=data)) |
472 | update_icons = shutter != self.shutter |
473 | update_icons = update_icons or mute != self.mute |
474 | self.shutter = shutter |
475 | @@ -656,6 +676,7 @@ |
476 | :param data: Currently selected source |
477 | """ |
478 | self.source = data |
479 | + log.info('({ip}) Setting data source to "{data}"'.format(ip=self.ip, data=self.source)) |
480 | return |
481 | |
482 | def process_clss(self, data): |
483 | @@ -674,7 +695,8 @@ |
484 | else: |
485 | clss = data |
486 | self.pjlink_class = clss |
487 | - log.debug('(%s) Setting pjlink_class for this projector to "%s"' % (self.ip, self.pjlink_class)) |
488 | + log.debug('({ip}) Setting pjlink_class for this projector to "{data}"'.format(ip=self.ip, |
489 | + data=self.pjlink_class)) |
490 | return |
491 | |
492 | def process_name(self, data): |
493 | @@ -685,6 +707,7 @@ |
494 | :param data: Projector name |
495 | """ |
496 | self.pjlink_name = data |
497 | + log.debug('({ip}) Setting projector PJLink name to "{data}"'.format(ip=self.ip, data=self.pjlink_name)) |
498 | return |
499 | |
500 | def process_inf1(self, data): |
501 | @@ -695,6 +718,7 @@ |
502 | :param data: Projector manufacturer |
503 | """ |
504 | self.manufacturer = data |
505 | + log.debug('({ip}) Setting projector manufacturer data to "{data}"'.format(ip=self.ip, data=self.manufacturer)) |
506 | return |
507 | |
508 | def process_inf2(self, data): |
509 | @@ -705,6 +729,7 @@ |
510 | :param data: Model name |
511 | """ |
512 | self.model = data |
513 | + log.debug('({ip}) Setting projector model to "{data}"'.format(ip=self.ip, data=self.model)) |
514 | return |
515 | |
516 | def process_info(self, data): |
517 | @@ -715,6 +740,7 @@ |
518 | :param data: Projector other info |
519 | """ |
520 | self.other_info = data |
521 | + log.debug('({ip}) Setting projector other_info to "{data}"'.format(ip=self.ip, data=self.other_info)) |
522 | return |
523 | |
524 | def process_inst(self, data): |
525 | @@ -731,6 +757,8 @@ |
526 | sources.sort() |
527 | self.source_available = sources |
528 | self.projectorUpdateIcons.emit() |
529 | + log.debug('({ip}) Setting projector sources_available to "{data}"'.format(ip=self.ip, |
530 | + data=self.source_available)) |
531 | return |
532 | |
533 | def process_erst(self, data): |
534 | @@ -780,7 +808,7 @@ |
535 | Initiate connection to projector. |
536 | """ |
537 | if self.state() == self.ConnectedState: |
538 | - log.warn('(%s) connect_to_host(): Already connected - returning' % self.ip) |
539 | + log.warn('({ip}) connect_to_host(): Already connected - returning'.format(ip=self.ip)) |
540 | return |
541 | self.change_status(S_CONNECTING) |
542 | self.connectToHost(self.ip, self.port if type(self.port) is int else int(self.port)) |
543 | @@ -792,9 +820,9 @@ |
544 | """ |
545 | if abort or self.state() != self.ConnectedState: |
546 | if abort: |
547 | - log.warn('(%s) disconnect_from_host(): Aborting connection' % self.ip) |
548 | + log.warn('({ip}) disconnect_from_host(): Aborting connection'.format(ip=self.ip)) |
549 | else: |
550 | - log.warn('(%s) disconnect_from_host(): Not connected - returning' % self.ip) |
551 | + log.warn('({ip}) disconnect_from_host(): Not connected - returning'.format(ip=self.ip)) |
552 | self.reset_information() |
553 | self.disconnectFromHost() |
554 | try: |
555 | @@ -804,8 +832,8 @@ |
556 | if abort: |
557 | self.change_status(E_NOT_CONNECTED) |
558 | else: |
559 | - log.debug('(%s) disconnect_from_host() Current status %s' % (self.ip, |
560 | - self._get_status(self.status_connect)[0])) |
561 | + log.debug('({ip}) disconnect_from_host() ' |
562 | + 'Current status {data}'.format(ip=self.ip, data=self._get_status(self.status_connect)[0])) |
563 | if self.status_connect != E_NOT_CONNECTED: |
564 | self.change_status(S_NOT_CONNECTED) |
565 | self.reset_information() |
566 | @@ -815,60 +843,70 @@ |
567 | """ |
568 | Send command to retrieve available source inputs. |
569 | """ |
570 | + log.debug('({ip}) Sending INST command'.format(ip=self.ip)) |
571 | return self.send_command(cmd='INST') |
572 | |
573 | def get_error_status(self): |
574 | """ |
575 | Send command to retrieve currently known errors. |
576 | """ |
577 | + log.debug('({ip}) Sending ERST command'.format(ip=self.ip)) |
578 | return self.send_command(cmd='ERST') |
579 | |
580 | def get_input_source(self): |
581 | """ |
582 | Send command to retrieve currently selected source input. |
583 | """ |
584 | + log.debug('({ip}) Sending INPT command'.format(ip=self.ip)) |
585 | return self.send_command(cmd='INPT') |
586 | |
587 | def get_lamp_status(self): |
588 | """ |
589 | Send command to return the lap status. |
590 | """ |
591 | + log.debug('({ip}) Sending LAMP command'.format(ip=self.ip)) |
592 | return self.send_command(cmd='LAMP') |
593 | |
594 | def get_manufacturer(self): |
595 | """ |
596 | Send command to retrieve manufacturer name. |
597 | """ |
598 | + log.debug('({ip}) Sending INF1 command'.format(ip=self.ip)) |
599 | return self.send_command(cmd='INF1') |
600 | |
601 | def get_model(self): |
602 | """ |
603 | Send command to retrieve the model name. |
604 | """ |
605 | + log.debug('({ip}) Sending INF2 command'.format(ip=self.ip)) |
606 | return self.send_command(cmd='INF2') |
607 | |
608 | def get_name(self): |
609 | """ |
610 | Send command to retrieve name as set by end-user (if set). |
611 | """ |
612 | + log.debug('({ip}) Sending NAME command'.format(ip=self.ip)) |
613 | return self.send_command(cmd='NAME') |
614 | |
615 | def get_other_info(self): |
616 | """ |
617 | Send command to retrieve extra info set by manufacturer. |
618 | """ |
619 | + log.debug('({ip}) Sending INFO command'.format(ip=self.ip)) |
620 | return self.send_command(cmd='INFO') |
621 | |
622 | def get_power_status(self): |
623 | """ |
624 | Send command to retrieve power status. |
625 | """ |
626 | + log.debug('({ip}) Sending POWR command'.format(ip=self.ip)) |
627 | return self.send_command(cmd='POWR') |
628 | |
629 | def get_shutter_status(self): |
630 | """ |
631 | Send command to retrieve shutter status. |
632 | """ |
633 | + log.debug('({ip}) Sending AVMT command'.format(ip=self.ip)) |
634 | return self.send_command(cmd='AVMT') |
635 | |
636 | def set_input_source(self, src=None): |
637 | @@ -878,12 +916,12 @@ |
638 | |
639 | :param src: Video source to select in projector |
640 | """ |
641 | - log.debug('(%s) set_input_source(src=%s)' % (self.ip, src)) |
642 | + log.debug('({ip}) set_input_source(src="{data}")'.format(ip=self.ip, data=src)) |
643 | if self.source_available is None: |
644 | return |
645 | elif src not in self.source_available: |
646 | return |
647 | - log.debug('(%s) Setting input source to %s' % (self.ip, src)) |
648 | + log.debug('({ip}) Setting input source to "{data}"'.format(ip=self.ip, data=src)) |
649 | self.send_command(cmd='INPT', opts=src) |
650 | self.poll_loop() |
651 | |
652 | @@ -891,6 +929,7 @@ |
653 | """ |
654 | Send command to turn power to on. |
655 | """ |
656 | + log.debug('({ip}) Setting POWR to 1 (on)'.format(ip=self.ip)) |
657 | self.send_command(cmd='POWR', opts='1') |
658 | self.poll_loop() |
659 | |
660 | @@ -898,6 +937,7 @@ |
661 | """ |
662 | Send command to turn power to standby. |
663 | """ |
664 | + log.debug('({ip}) Setting POWR to 0 (standby)'.format(ip=self.ip)) |
665 | self.send_command(cmd='POWR', opts='0') |
666 | self.poll_loop() |
667 | |
668 | @@ -905,6 +945,7 @@ |
669 | """ |
670 | Send command to set shutter to closed position. |
671 | """ |
672 | + log.debug('({ip}) Setting AVMT to 11 (shutter closed)'.format(ip=self.ip)) |
673 | self.send_command(cmd='AVMT', opts='11') |
674 | self.poll_loop() |
675 | |
676 | @@ -912,5 +953,6 @@ |
677 | """ |
678 | Send command to set shutter to open position. |
679 | """ |
680 | + log.debug('({ip}) Setting AVMT to "10" (shutter open)'.format(ip=self.ip)) |
681 | self.send_command(cmd='AVMT', opts='10') |
682 | self.poll_loop() |
683 | |
684 | === modified file 'tests/functional/openlp_core_common/test_projector_utilities.py' |
685 | --- tests/functional/openlp_core_common/test_projector_utilities.py 2016-01-09 17:21:20 +0000 |
686 | +++ tests/functional/openlp_core_common/test_projector_utilities.py 2016-06-18 03:24:30 +0000 |
687 | @@ -23,13 +23,12 @@ |
688 | Package to test the openlp.core.ui.projector.networkutils package. |
689 | """ |
690 | |
691 | -import os |
692 | - |
693 | from unittest import TestCase |
694 | |
695 | from openlp.core.common import verify_ip_address, md5_hash, qmd5_hash |
696 | |
697 | from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_HASH |
698 | + |
699 | salt = TEST_SALT |
700 | pin = TEST_PIN |
701 | test_hash = TEST_HASH |
702 | @@ -148,7 +147,7 @@ |
703 | hash_ = qmd5_hash(salt=salt.encode('ascii'), data=pin.encode('ascii')) |
704 | |
705 | # THEN: Validate return has is same |
706 | - self.assertEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a good hash') |
707 | + self.assertEquals(hash_, test_hash.encode('ascii'), 'Qt-MD5 should have returned a good hash') |
708 | |
709 | def test_qmd5_hash_bad(self): |
710 | """ |
711 | @@ -158,7 +157,7 @@ |
712 | hash_ = qmd5_hash(salt=pin.encode('ascii'), data=salt.encode('ascii')) |
713 | |
714 | # THEN: return data is different |
715 | - self.assertNotEquals(hash_.decode('ascii'), test_hash, 'Qt-MD5 should have returned a bad hash') |
716 | + self.assertNotEquals(hash_, test_hash, 'Qt-MD5 should have returned a bad hash') |
717 | |
718 | def test_md5_non_ascii_string(self): |
719 | """ |
720 | |
721 | === modified file 'tests/functional/openlp_core_lib/test_projector_pjlink1.py' |
722 | --- tests/functional/openlp_core_lib/test_projector_pjlink1.py 2016-03-03 17:15:58 +0000 |
723 | +++ tests/functional/openlp_core_lib/test_projector_pjlink1.py 2016-06-18 03:24:30 +0000 |
724 | @@ -26,13 +26,29 @@ |
725 | from unittest import TestCase |
726 | |
727 | from openlp.core.lib.projector.pjlink1 import PJLink1 |
728 | +from openlp.core.lib.projector.constants import E_PARAMETER, ERROR_STRING, S_OFF, S_STANDBY, S_WARMUP, S_ON, \ |
729 | + S_COOLDOWN, PJLINK_POWR_STATUS |
730 | |
731 | from tests.functional import patch |
732 | -from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE |
733 | +from tests.resources.projector.data import TEST_PIN, TEST_SALT, TEST_CONNECT_AUTHENTICATE, TEST_HASH |
734 | |
735 | pjlink_test = PJLink1(name='test', ip='127.0.0.1', pin=TEST_PIN, no_poll=True) |
736 | |
737 | |
738 | +class DummyTimer(object): |
739 | + ''' |
740 | + Dummy class to fake timers |
741 | + ''' |
742 | + def __init__(self, *args, **kwargs): |
743 | + pass |
744 | + |
745 | + def start(self, *args, **kwargs): |
746 | + pass |
747 | + |
748 | + def stop(self, *args, **kwargs): |
749 | + pass |
750 | + |
751 | + |
752 | class TestPJLink(TestCase): |
753 | """ |
754 | Tests for the PJLink module |
755 | @@ -41,13 +57,10 @@ |
756 | @patch.object(pjlink_test, 'send_command') |
757 | @patch.object(pjlink_test, 'waitForReadyRead') |
758 | @patch('openlp.core.common.qmd5_hash') |
759 | - def authenticated_connection_call_test(self, |
760 | - mock_qmd5_hash, |
761 | - mock_waitForReadyRead, |
762 | - mock_send_command, |
763 | + def test_authenticated_connection_call(self, mock_qmd5_hash, mock_waitForReadyRead, mock_send_command, |
764 | mock_readyRead): |
765 | """ |
766 | - Fix for projector connect with PJLink authentication exception. Ticket 92187. |
767 | + Ticket 92187: Fix for projector connect with PJLink authentication exception. |
768 | """ |
769 | # GIVEN: Test object |
770 | pjlink = pjlink_test |
771 | @@ -61,9 +74,23 @@ |
772 | self.assertTrue(mock_qmd5_hash.called_with(TEST_PIN, |
773 | "Connection request should have been called with TEST_PIN")) |
774 | |
775 | - def non_standard_class_reply_test(self): |
776 | - """ |
777 | - bugfix 1550891 - CLSS request returns non-standard 'Class N' reply |
778 | + def test_projector_class(self): |
779 | + """ |
780 | + Test class version from projector |
781 | + """ |
782 | + # GIVEN: Test object |
783 | + pjlink = pjlink_test |
784 | + |
785 | + # WHEN: Process class response |
786 | + pjlink.process_clss('1') |
787 | + |
788 | + # THEN: Projector class should be set to 1 |
789 | + self.assertEquals(pjlink.pjlink_class, '1', |
790 | + 'Projector should have returned class=1') |
791 | + |
792 | + def test_non_standard_class_reply(self): |
793 | + """ |
794 | + Bugfix 1550891: CLSS request returns non-standard 'Class N' reply |
795 | """ |
796 | # GIVEN: Test object |
797 | pjlink = pjlink_test |
798 | @@ -74,3 +101,284 @@ |
799 | # THEN: Projector class should be set with proper value |
800 | self.assertEquals(pjlink.pjlink_class, '1', |
801 | 'Non-standard class reply should have set proper class') |
802 | + |
803 | + @patch.object(pjlink_test, 'change_status') |
804 | + def test_status_change(self, mock_change_status): |
805 | + """ |
806 | + Test process_command call with ERR2 (Parameter) status |
807 | + """ |
808 | + # GIVEN: Test object |
809 | + pjlink = pjlink_test |
810 | + |
811 | + # WHEN: process_command is called with "ERR2" status from projector |
812 | + pjlink.process_command('POWR', 'ERR2') |
813 | + |
814 | + # THEN: change_status should have called change_status with E_UNDEFINED |
815 | + # as first parameter |
816 | + mock_change_status.called_with(E_PARAMETER, |
817 | + 'change_status should have been called with "{}"'.format( |
818 | + ERROR_STRING[E_PARAMETER])) |
819 | + |
820 | + @patch.object(pjlink_test, 'process_inpt') |
821 | + def test_projector_return_ok(self, mock_process_inpt): |
822 | + """ |
823 | + Test projector calls process_inpt command when process_command is called with INPT option |
824 | + """ |
825 | + # GIVEN: Test object |
826 | + pjlink = pjlink_test |
827 | + |
828 | + # WHEN: process_command is called with INST command and 31 input: |
829 | + pjlink.process_command('INPT', '31') |
830 | + |
831 | + # THEN: process_inpt method should have been called with 31 |
832 | + mock_process_inpt.called_with('31', |
833 | + "process_inpt should have been called with 31") |
834 | + |
835 | + @patch.object(pjlink_test, 'projectorReceivedData') |
836 | + def test_projector_process_lamp(self, mock_projectorReceivedData): |
837 | + """ |
838 | + Test status lamp on/off and hours |
839 | + """ |
840 | + # GIVEN: Test object |
841 | + pjlink = pjlink_test |
842 | + |
843 | + # WHEN: Call process_command with lamp data |
844 | + pjlink.process_command('LAMP', '22222 1') |
845 | + |
846 | + # THEN: Lamp should have been set with status=ON and hours=22222 |
847 | + self.assertEquals(pjlink.lamp[0]['On'], True, |
848 | + 'Lamp power status should have been set to TRUE') |
849 | + self.assertEquals(pjlink.lamp[0]['Hours'], 22222, |
850 | + 'Lamp hours should have been set to 22222') |
851 | + |
852 | + @patch.object(pjlink_test, 'projectorReceivedData') |
853 | + def test_projector_process_multiple_lamp(self, mock_projectorReceivedData): |
854 | + """ |
855 | + Test status multiple lamp on/off and hours |
856 | + """ |
857 | + # GIVEN: Test object |
858 | + pjlink = pjlink_test |
859 | + |
860 | + # WHEN: Call process_command with lamp data |
861 | + pjlink.process_command('LAMP', '11111 1 22222 0 33333 1') |
862 | + |
863 | + # THEN: Lamp should have been set with proper lamp status |
864 | + self.assertEquals(len(pjlink.lamp), 3, |
865 | + 'Projector should have 3 lamps specified') |
866 | + self.assertEquals(pjlink.lamp[0]['On'], True, |
867 | + 'Lamp 1 power status should have been set to TRUE') |
868 | + self.assertEquals(pjlink.lamp[0]['Hours'], 11111, |
869 | + 'Lamp 1 hours should have been set to 11111') |
870 | + self.assertEquals(pjlink.lamp[1]['On'], False, |
871 | + 'Lamp 2 power status should have been set to FALSE') |
872 | + self.assertEquals(pjlink.lamp[1]['Hours'], 22222, |
873 | + 'Lamp 2 hours should have been set to 22222') |
874 | + self.assertEquals(pjlink.lamp[2]['On'], True, |
875 | + 'Lamp 3 power status should have been set to TRUE') |
876 | + self.assertEquals(pjlink.lamp[2]['Hours'], 33333, |
877 | + 'Lamp 3 hours should have been set to 33333') |
878 | + |
879 | + @patch.object(pjlink_test, 'projectorReceivedData') |
880 | + def test_projector_process_power_on(self, mock_projectorReceivedData): |
881 | + """ |
882 | + Test status power to ON |
883 | + """ |
884 | + # GIVEN: Test object and preset |
885 | + pjlink = pjlink_test |
886 | + pjlink.power = S_STANDBY |
887 | + |
888 | + # WHEN: Call process_command with turn power on command |
889 | + pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_ON]) |
890 | + |
891 | + # THEN: Power should be set to ON |
892 | + self.assertEquals(pjlink.power, S_ON, 'Power should have been set to ON') |
893 | + |
894 | + @patch.object(pjlink_test, 'projectorReceivedData') |
895 | + def test_projector_process_power_off(self, mock_projectorReceivedData): |
896 | + """ |
897 | + Test status power to STANDBY |
898 | + """ |
899 | + # GIVEN: Test object and preset |
900 | + pjlink = pjlink_test |
901 | + pjlink.power = S_ON |
902 | + |
903 | + # WHEN: Call process_command with turn power on command |
904 | + pjlink.process_command('POWR', PJLINK_POWR_STATUS[S_STANDBY]) |
905 | + |
906 | + # THEN: Power should be set to STANDBY |
907 | + self.assertEquals(pjlink.power, S_STANDBY, 'Power should have been set to STANDBY') |
908 | + |
909 | + @patch.object(pjlink_test, 'projectorUpdateIcons') |
910 | + def test_projector_process_avmt_closed_unmuted(self, mock_projectorReceivedData): |
911 | + """ |
912 | + Test avmt status shutter closed and audio muted |
913 | + """ |
914 | + # GIVEN: Test object |
915 | + pjlink = pjlink_test |
916 | + pjlink.shutter = False |
917 | + pjlink.mute = True |
918 | + |
919 | + # WHEN: Called with setting shutter closed and mute off |
920 | + pjlink.process_avmt('11') |
921 | + |
922 | + # THEN: Shutter should be True and mute should be False |
923 | + self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed') |
924 | + self.assertFalse(pjlink.mute, 'Audio should be off') |
925 | + |
926 | + @patch.object(pjlink_test, 'projectorUpdateIcons') |
927 | + def test_projector_process_avmt_open_muted(self, mock_projectorReceivedData): |
928 | + """ |
929 | + Test avmt status shutter open and mute on |
930 | + """ |
931 | + # GIVEN: Test object |
932 | + pjlink = pjlink_test |
933 | + pjlink.shutter = True |
934 | + pjlink.mute = False |
935 | + |
936 | + # WHEN: Called with setting shutter closed and mute on |
937 | + pjlink.process_avmt('21') |
938 | + |
939 | + # THEN: Shutter should be closed and mute should be True |
940 | + self.assertFalse(pjlink.shutter, 'Shutter should have been set to closed') |
941 | + self.assertTrue(pjlink.mute, 'Audio should be off') |
942 | + |
943 | + @patch.object(pjlink_test, 'projectorUpdateIcons') |
944 | + def test_projector_process_avmt_open_unmuted(self, mock_projectorReceivedData): |
945 | + """ |
946 | + Test avmt status shutter open and mute off off |
947 | + """ |
948 | + # GIVEN: Test object |
949 | + pjlink = pjlink_test |
950 | + pjlink.shutter = True |
951 | + pjlink.mute = True |
952 | + |
953 | + # WHEN: Called with setting shutter to closed and mute on |
954 | + pjlink.process_avmt('30') |
955 | + |
956 | + # THEN: Shutter should be closed and mute should be True |
957 | + self.assertFalse(pjlink.shutter, 'Shutter should have been set to open') |
958 | + self.assertFalse(pjlink.mute, 'Audio should be on') |
959 | + |
960 | + @patch.object(pjlink_test, 'projectorUpdateIcons') |
961 | + def test_projector_process_avmt_closed_muted(self, mock_projectorReceivedData): |
962 | + """ |
963 | + Test avmt status shutter closed and mute off |
964 | + """ |
965 | + # GIVEN: Test object |
966 | + pjlink = pjlink_test |
967 | + pjlink.shutter = False |
968 | + pjlink.mute = False |
969 | + |
970 | + # WHEN: Called with setting shutter to closed and mute on |
971 | + pjlink.process_avmt('31') |
972 | + |
973 | + # THEN: Shutter should be closed and mute should be True |
974 | + self.assertTrue(pjlink.shutter, 'Shutter should have been set to closed') |
975 | + self.assertTrue(pjlink.mute, 'Audio should be on') |
976 | + |
977 | + def test_projector_process_input(self): |
978 | + """ |
979 | + Test input source status shows current input |
980 | + """ |
981 | + # GIVEN: Test object |
982 | + pjlink = pjlink_test |
983 | + pjlink.source = '0' |
984 | + |
985 | + # WHEN: Called with input source |
986 | + pjlink.process_inpt('1') |
987 | + |
988 | + # THEN: Input selected should reflect current input |
989 | + self.assertEquals(pjlink.source, '1', 'Input source should be set to "1"') |
990 | + |
991 | + def test_projector_reset_information(self): |
992 | + """ |
993 | + Test reset_information() resets all information and stops timers |
994 | + """ |
995 | + # GIVEN: Test object and test data |
996 | + pjlink = pjlink_test |
997 | + pjlink.power = S_ON |
998 | + pjlink.pjlink_name = 'OPENLPTEST' |
999 | + pjlink.manufacturer = 'PJLINK' |
1000 | + pjlink.model = '1' |
1001 | + pjlink.shutter = True |
1002 | + pjlink.mute = True |
1003 | + pjlink.lamp = True |
1004 | + pjlink.fan = True |
1005 | + pjlink.source_available = True |
1006 | + pjlink.other_info = 'ANOTHER TEST' |
1007 | + pjlink.send_queue = True |
1008 | + pjlink.send_busy = True |
1009 | + pjlink.timer = DummyTimer() |
1010 | + pjlink.socket_timer = DummyTimer() |
1011 | + |
1012 | + # WHEN: reset_information() is called |
1013 | + with patch.object(pjlink.timer, 'stop') as mock_timer: |
1014 | + with patch.object(pjlink.socket_timer, 'stop') as mock_socket_timer: |
1015 | + pjlink.reset_information() |
1016 | + |
1017 | + # THEN: All information should be reset and timers stopped |
1018 | + self.assertEquals(pjlink.power, S_OFF, 'Projector power should be OFF') |
1019 | + self.assertIsNone(pjlink.pjlink_name, 'Projector pjlink_name should be None') |
1020 | + self.assertIsNone(pjlink.manufacturer, 'Projector manufacturer should be None') |
1021 | + self.assertIsNone(pjlink.model, 'Projector model should be None') |
1022 | + self.assertIsNone(pjlink.shutter, 'Projector shutter should be None') |
1023 | + self.assertIsNone(pjlink.mute, 'Projector shuttter should be None') |
1024 | + self.assertIsNone(pjlink.lamp, 'Projector lamp should be None') |
1025 | + self.assertIsNone(pjlink.fan, 'Projector fan should be None') |
1026 | + self.assertIsNone(pjlink.source_available, 'Projector source_available should be None') |
1027 | + self.assertIsNone(pjlink.source, 'Projector source should be None') |
1028 | + self.assertIsNone(pjlink.other_info, 'Projector other_info should be None') |
1029 | + self.assertEquals(pjlink.send_queue, [], 'Projector send_queue should be an empty list') |
1030 | + self.assertFalse(pjlink.send_busy, 'Projector send_busy should be False') |
1031 | + self.assertTrue(mock_timer.called, 'Projector timer.stop() should have been called') |
1032 | + self.assertTrue(mock_socket_timer.called, 'Projector socket_timer.stop() should have been called') |
1033 | + |
1034 | + @patch.object(pjlink_test, 'send_command') |
1035 | + @patch.object(pjlink_test, 'waitForReadyRead') |
1036 | + @patch.object(pjlink_test, 'projectorAuthentication') |
1037 | + @patch.object(pjlink_test, 'timer') |
1038 | + @patch.object(pjlink_test, 'socket_timer') |
1039 | + def test_bug_1593882_no_pin_authenticated_connection(self, mock_socket_timer, |
1040 | + mock_timer, |
1041 | + mock_authentication, |
1042 | + mock_ready_read, |
1043 | + mock_send_command): |
1044 | + """ |
1045 | + Test bug 1593882 no pin and authenticated request exception |
1046 | + """ |
1047 | + # GIVEN: Test object and mocks |
1048 | + pjlink = pjlink_test |
1049 | + pjlink.pin = None |
1050 | + mock_ready_read.return_value = True |
1051 | + |
1052 | + # WHEN: call with authentication request and pin not set |
1053 | + pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) |
1054 | + |
1055 | + # THEN: No Authentication signal should have been sent |
1056 | + mock_authentication.emit.assert_called_with(pjlink.name) |
1057 | + |
1058 | + @patch.object(pjlink_test, 'waitForReadyRead') |
1059 | + @patch.object(pjlink_test, 'state') |
1060 | + @patch.object(pjlink_test, '_send_command') |
1061 | + @patch.object(pjlink_test, 'timer') |
1062 | + @patch.object(pjlink_test, 'socket_timer') |
1063 | + def test_bug_1593883_pjlink_authentication(self, mock_socket_timer, |
1064 | + mock_timer, |
1065 | + mock_send_command, |
1066 | + mock_state, |
1067 | + mock_waitForReadyRead): |
1068 | + """ |
1069 | + Test bugfix 1593883 pjlink authentication |
1070 | + """ |
1071 | + # GIVEN: Test object and data |
1072 | + pjlink = pjlink_test |
1073 | + pjlink.pin = TEST_PIN |
1074 | + mock_state.return_value = pjlink.ConnectedState |
1075 | + mock_waitForReadyRead.return_value = True |
1076 | + |
1077 | + # WHEN: Athenticated connection is called |
1078 | + pjlink.check_login(data=TEST_CONNECT_AUTHENTICATE) |
1079 | + |
1080 | + # THEN: send_command should have the proper authentication |
1081 | + self.assertEquals("{test}".format(test=mock_send_command.call_args), |
1082 | + "call(data='{hash}%1CLSS ?\\r')".format(hash=TEST_HASH)) |
Given the problems we're seeing in trunk, I'm not too happy about all thw string-format changes... Are you sure they all work? Have they all been triggered/tested?