blob: 9c56ea74557e4c7f732a9f8cb9f8707b360e2e60 [file] [log] [blame]
Ruben Brunk370e2432014-10-14 18:33:23 -07001# Copyright 2013 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import its.error
16import os
17import os.path
18import sys
19import re
20import json
21import time
22import unittest
23import socket
24import subprocess
25import hashlib
26import numpy
Sam Linf2f66002017-02-27 21:08:11 -080027import string
Shuzhen Wang80822a92018-01-29 09:27:42 -080028import unicodedata
Ruben Brunk370e2432014-10-14 18:33:23 -070029
Clemenz Portmanna2815fa2017-05-23 08:31:19 -070030CMD_DELAY = 1 # seconds
31
32
Ruben Brunk370e2432014-10-14 18:33:23 -070033class ItsSession(object):
34 """Controls a device over adb to run ITS scripts.
35
36 The script importing this module (on the host machine) prepares JSON
37 objects encoding CaptureRequests, specifying sets of parameters to use
Chien-Yu Chen682faa22014-10-22 17:34:44 -070038 when capturing an image using the Camera2 APIs. This class encapsulates
Ruben Brunk370e2432014-10-14 18:33:23 -070039 sending the requests to the device, monitoring the device's progress, and
40 copying the resultant captures back to the host machine when done. TCP
41 forwarded over adb is the transport mechanism used.
42
43 The device must have CtsVerifier.apk installed.
44
45 Attributes:
46 sock: The open socket.
47 """
48
Chien-Yu Chen1da23132015-07-22 15:24:41 -070049 # Open a connection to localhost:<host_port>, forwarded to port 6000 on the
50 # device. <host_port> is determined at run-time to support multiple
51 # connected devices.
Ruben Brunk370e2432014-10-14 18:33:23 -070052 IPADDR = '127.0.0.1'
Chien-Yu Chen1da23132015-07-22 15:24:41 -070053 REMOTE_PORT = 6000
Ruben Brunk370e2432014-10-14 18:33:23 -070054 BUFFER_SIZE = 4096
55
Chien-Yu Chen1da23132015-07-22 15:24:41 -070056 # LOCK_PORT is used as a mutex lock to protect the list of forwarded ports
57 # among all processes. The script assumes LOCK_PORT is available and will
58 # try to use ports between CLIENT_PORT_START and
59 # CLIENT_PORT_START+MAX_NUM_PORTS-1 on host for ITS sessions.
60 CLIENT_PORT_START = 6000
61 MAX_NUM_PORTS = 100
62 LOCK_PORT = CLIENT_PORT_START + MAX_NUM_PORTS
63
Ruben Brunk370e2432014-10-14 18:33:23 -070064 # Seconds timeout on each socket operation.
Yin-Chia Yehea1c1a62016-07-12 15:29:30 -070065 SOCK_TIMEOUT = 20.0
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -070066 # Additional timeout in seconds when ITS service is doing more complicated
67 # operations, for example: issuing warmup requests before actual capture.
68 EXTRA_SOCK_TIMEOUT = 5.0
69
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -070070 SEC_TO_NSEC = 1000*1000*1000.0
Ruben Brunk370e2432014-10-14 18:33:23 -070071
72 PACKAGE = 'com.android.cts.verifier.camera.its'
73 INTENT_START = 'com.android.cts.verifier.camera.its.START'
74 ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070075 EXTRA_VERSION = 'camera.its.extra.VERSION'
76 CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
Yin-Chia Yehab98ada2015-03-05 13:28:53 -080077 EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070078 EXTRA_RESULTS = 'camera.its.extra.RESULTS'
Sam Linf2f66002017-02-27 21:08:11 -080079 ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -070080
81 RESULT_PASS = 'PASS'
82 RESULT_FAIL = 'FAIL'
83 RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
84 RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
85 RESULT_KEY = 'result'
86 SUMMARY_KEY = 'summary'
Ruben Brunk370e2432014-10-14 18:33:23 -070087
Chien-Yu Chen1da23132015-07-22 15:24:41 -070088 adb = "adb -d"
89 device_id = ""
Ruben Brunk370e2432014-10-14 18:33:23 -070090
91 # Definitions for some of the common output format options for do_capture().
92 # Each gets images of full resolution for each requested format.
93 CAP_RAW = {"format":"raw"}
94 CAP_DNG = {"format":"dng"}
95 CAP_YUV = {"format":"yuv"}
96 CAP_JPEG = {"format":"jpeg"}
97 CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}]
98 CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}]
99 CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}]
100 CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}]
101 CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}]
102 CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}]
103 CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}]
104
Lu75f22fc2016-02-19 10:54:07 -0800105 # Predefine camera props. Save props extracted from the function,
106 # "get_camera_properties".
107 props = None
108
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700109 # Initialize the socket port for the host to forward requests to the device.
110 # This method assumes localhost's LOCK_PORT is available and will try to
111 # use ports between CLIENT_PORT_START and CLIENT_PORT_START+MAX_NUM_PORTS-1
112 def __init_socket_port(self):
113 NUM_RETRIES = 100
114 RETRY_WAIT_TIME_SEC = 0.05
Ruben Brunk370e2432014-10-14 18:33:23 -0700115
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700116 # Bind a socket to use as mutex lock
117 socket_lock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
118 for i in range(NUM_RETRIES):
119 try:
120 socket_lock.bind((ItsSession.IPADDR, ItsSession.LOCK_PORT))
121 break
Clemenz Portmannb0e76152018-02-01 09:12:57 -0800122 except socket.error or socket.timeout:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700123 if i == NUM_RETRIES - 1:
124 raise its.error.Error(self.device_id,
Clemenz Portmannb0e76152018-02-01 09:12:57 -0800125 "socket lock returns error")
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700126 else:
127 time.sleep(RETRY_WAIT_TIME_SEC)
128
129 # Check if a port is already assigned to the device.
130 command = "adb forward --list"
131 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
132 output, error = proc.communicate()
133
134 port = None
135 used_ports = []
136 for line in output.split(os.linesep):
137 # each line should be formatted as:
138 # "<device_id> tcp:<host_port> tcp:<remote_port>"
139 forward_info = line.split()
140 if len(forward_info) >= 3 and \
141 len(forward_info[1]) > 4 and forward_info[1][:4] == "tcp:" and \
142 len(forward_info[2]) > 4 and forward_info[2][:4] == "tcp:":
143 local_p = int(forward_info[1][4:])
144 remote_p = int(forward_info[2][4:])
145 if forward_info[0] == self.device_id and \
146 remote_p == ItsSession.REMOTE_PORT:
147 port = local_p
148 break;
149 else:
150 used_ports.append(local_p)
151
152 # Find the first available port if no port is assigned to the device.
153 if port is None:
154 for p in range(ItsSession.CLIENT_PORT_START,
155 ItsSession.CLIENT_PORT_START +
156 ItsSession.MAX_NUM_PORTS):
157 if p not in used_ports:
158 # Try to run "adb forward" with the port
159 command = "%s forward tcp:%d tcp:%d" % \
160 (self.adb, p, self.REMOTE_PORT)
161 proc = subprocess.Popen(command.split(),
162 stdout=subprocess.PIPE,
163 stderr=subprocess.PIPE)
164 output, error = proc.communicate()
165
166 # Check if there is no error
167 if error is None or error.find("error") < 0:
168 port = p
169 break
170
171 if port is None:
172 raise its.error.Error(self.device_id, " cannot find an available " +
173 "port")
174
175 # Release the socket as mutex unlock
176 socket_lock.close()
177
178 # Connect to the socket
179 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
180 self.sock.connect((self.IPADDR, port))
181 self.sock.settimeout(self.SOCK_TIMEOUT)
182
183 # Reboot the device if needed and wait for the service to be ready for
184 # connection.
185 def __wait_for_service(self):
Ruben Brunk370e2432014-10-14 18:33:23 -0700186 # This also includes the optional reboot handling: if the user
187 # provides a "reboot" or "reboot=N" arg, then reboot the device,
188 # waiting for N seconds (default 30) before returning.
189 for s in sys.argv[1:]:
190 if s[:6] == "reboot":
191 duration = 30
192 if len(s) > 7 and s[6] == "=":
193 duration = int(s[7:])
194 print "Rebooting device"
Lu75f22fc2016-02-19 10:54:07 -0800195 _run("%s reboot" % (self.adb))
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700196 _run("%s wait-for-device" % (self.adb))
Ruben Brunk370e2432014-10-14 18:33:23 -0700197 time.sleep(duration)
198 print "Reboot complete"
199
Yin-Chia Yeh23a7b422016-03-25 14:48:47 -0700200 # Flush logcat so following code won't be misled by previous
201 # 'ItsService ready' log.
202 _run('%s logcat -c' % (self.adb))
203 time.sleep(1)
204
Ruben Brunk370e2432014-10-14 18:33:23 -0700205 # TODO: Figure out why "--user 0" is needed, and fix the problem.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700206 _run('%s shell am force-stop --user 0 %s' % (self.adb, self.PACKAGE))
Clemenz Portmanna2815fa2017-05-23 08:31:19 -0700207 _run(('%s shell am start --user 0 '
208 'com.android.cts.verifier/.camera.its.ItsTestActivity '
209 '--activity-brought-to-front') % self.adb)
210 time.sleep(CMD_DELAY)
Yin-Chia Yeh6bdae902018-01-02 15:17:13 -0800211 _run(('%s shell am start-foreground-service --user 0 -t text/plain '
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700212 '-a %s') % (self.adb, self.INTENT_START))
Ruben Brunk370e2432014-10-14 18:33:23 -0700213
214 # Wait until the socket is ready to accept a connection.
215 proc = subprocess.Popen(
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700216 self.adb.split() + ["logcat"],
Ruben Brunk370e2432014-10-14 18:33:23 -0700217 stdout=subprocess.PIPE)
218 logcat = proc.stdout
219 while True:
220 line = logcat.readline().strip()
221 if line.find('ItsService ready') >= 0:
222 break
223 proc.kill()
224
Ruben Brunk370e2432014-10-14 18:33:23 -0700225 def __init__(self):
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700226 # Initialize device id and adb command.
227 self.device_id = get_device_id()
228 self.adb = "adb -s " + self.device_id
229
230 self.__wait_for_service()
231 self.__init_socket_port()
232
Ruben Brunk370e2432014-10-14 18:33:23 -0700233 self.__close_camera()
234 self.__open_camera()
235
236 def __del__(self):
237 if hasattr(self, 'sock') and self.sock:
238 self.__close_camera()
239 self.sock.close()
240
241 def __enter__(self):
242 return self
243
244 def __exit__(self, type, value, traceback):
245 return False
246
247 def __read_response_from_socket(self):
248 # Read a line (newline-terminated) string serialization of JSON object.
249 chars = []
250 while len(chars) == 0 or chars[-1] != '\n':
251 ch = self.sock.recv(1)
252 if len(ch) == 0:
253 # Socket was probably closed; otherwise don't get empty strings
254 raise its.error.Error('Problem with socket on device side')
255 chars.append(ch)
256 line = ''.join(chars)
257 jobj = json.loads(line)
258 # Optionally read a binary buffer of a fixed size.
259 buf = None
260 if jobj.has_key("bufValueSize"):
261 n = jobj["bufValueSize"]
262 buf = bytearray(n)
263 view = memoryview(buf)
264 while n > 0:
265 nbytes = self.sock.recv_into(view, n)
266 view = view[nbytes:]
267 n -= nbytes
268 buf = numpy.frombuffer(buf, dtype=numpy.uint8)
269 return jobj, buf
270
271 def __open_camera(self):
272 # Get the camera ID to open as an argument.
273 camera_id = 0
274 for s in sys.argv[1:]:
275 if s[:7] == "camera=" and len(s) > 7:
276 camera_id = int(s[7:])
277 cmd = {"cmdName":"open", "cameraId":camera_id}
278 self.sock.send(json.dumps(cmd) + "\n")
279 data,_ = self.__read_response_from_socket()
280 if data['tag'] != 'cameraOpened':
281 raise its.error.Error('Invalid command response')
282
283 def __close_camera(self):
284 cmd = {"cmdName":"close"}
285 self.sock.send(json.dumps(cmd) + "\n")
286 data,_ = self.__read_response_from_socket()
287 if data['tag'] != 'cameraClosed':
288 raise its.error.Error('Invalid command response')
289
290 def do_vibrate(self, pattern):
291 """Cause the device to vibrate to a specific pattern.
292
293 Args:
294 pattern: Durations (ms) for which to turn on or off the vibrator.
295 The first value indicates the number of milliseconds to wait
296 before turning the vibrator on. The next value indicates the
297 number of milliseconds for which to keep the vibrator on
298 before turning it off. Subsequent values alternate between
299 durations in milliseconds to turn the vibrator off or to turn
300 the vibrator on.
301
302 Returns:
303 Nothing.
304 """
305 cmd = {}
306 cmd["cmdName"] = "doVibrate"
307 cmd["pattern"] = pattern
308 self.sock.send(json.dumps(cmd) + "\n")
309 data,_ = self.__read_response_from_socket()
310 if data['tag'] != 'vibrationStarted':
311 raise its.error.Error('Invalid command response')
312
313 def start_sensor_events(self):
314 """Start collecting sensor events on the device.
315
316 See get_sensor_events for more info.
317
318 Returns:
319 Nothing.
320 """
321 cmd = {}
322 cmd["cmdName"] = "startSensorEvents"
323 self.sock.send(json.dumps(cmd) + "\n")
324 data,_ = self.__read_response_from_socket()
325 if data['tag'] != 'sensorEventsStarted':
326 raise its.error.Error('Invalid command response')
327
328 def get_sensor_events(self):
329 """Get a trace of all sensor events on the device.
330
331 The trace starts when the start_sensor_events function is called. If
332 the test runs for a long time after this call, then the device's
333 internal memory can fill up. Calling get_sensor_events gets all events
334 from the device, and then stops the device from collecting events and
335 clears the internal buffer; to start again, the start_sensor_events
336 call must be used again.
337
338 Events from the accelerometer, compass, and gyro are returned; each
339 has a timestamp and x,y,z values.
340
341 Note that sensor events are only produced if the device isn't in its
342 standby mode (i.e.) if the screen is on.
343
344 Returns:
345 A Python dictionary with three keys ("accel", "mag", "gyro") each
346 of which maps to a list of objects containing "time","x","y","z"
347 keys.
348 """
349 cmd = {}
350 cmd["cmdName"] = "getSensorEvents"
351 self.sock.send(json.dumps(cmd) + "\n")
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700352 timeout = self.SOCK_TIMEOUT + self.EXTRA_SOCK_TIMEOUT
353 self.sock.settimeout(timeout)
Ruben Brunk370e2432014-10-14 18:33:23 -0700354 data,_ = self.__read_response_from_socket()
355 if data['tag'] != 'sensorEvents':
356 raise its.error.Error('Invalid command response')
Yin-Chia Yehf16d6872016-06-06 13:54:42 -0700357 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700358 return data['objValue']
359
Yin-Chia Yehab98ada2015-03-05 13:28:53 -0800360 def get_camera_ids(self):
361 """Get a list of camera device Ids that can be opened.
362
363 Returns:
364 a list of camera ID string
365 """
366 cmd = {}
367 cmd["cmdName"] = "getCameraIds"
368 self.sock.send(json.dumps(cmd) + "\n")
369 data,_ = self.__read_response_from_socket()
370 if data['tag'] != 'cameraIds':
371 raise its.error.Error('Invalid command response')
372 return data['objValue']['cameraIdArray']
373
Ruben Brunk370e2432014-10-14 18:33:23 -0700374 def get_camera_properties(self):
375 """Get the camera properties object for the device.
376
377 Returns:
378 The Python dictionary object for the CameraProperties object.
379 """
380 cmd = {}
381 cmd["cmdName"] = "getCameraProperties"
382 self.sock.send(json.dumps(cmd) + "\n")
383 data,_ = self.__read_response_from_socket()
384 if data['tag'] != 'cameraProperties':
385 raise its.error.Error('Invalid command response')
Lu75f22fc2016-02-19 10:54:07 -0800386 self.props = data['objValue']['cameraProperties']
Ruben Brunk370e2432014-10-14 18:33:23 -0700387 return data['objValue']['cameraProperties']
388
Yin-Chia Yehb1801f92018-02-14 16:08:12 -0800389 def get_camera_properties_by_id(self, camera_id):
390 """Get the camera properties object for device with camera_id
391
392 Args:
393 camera_id: The ID string of the camera
394
395 Returns:
396 The Python dictionary object for the CameraProperties object. Empty
397 if no such device exists.
398
399 """
400 cmd = {}
401 cmd["cmdName"] = "getCameraPropertiesById"
402 cmd["cameraId"] = camera_id
403 self.sock.send(json.dumps(cmd) + "\n")
404 data,_ = self.__read_response_from_socket()
405 if data['tag'] != 'cameraProperties':
406 raise its.error.Error('Invalid command response')
407 return data['objValue']['cameraProperties']
408
Ruben Brunk370e2432014-10-14 18:33:23 -0700409 def do_3a(self, regions_ae=[[0,0,1,1,1]],
410 regions_awb=[[0,0,1,1,1]],
411 regions_af=[[0,0,1,1,1]],
412 do_ae=True, do_awb=True, do_af=True,
413 lock_ae=False, lock_awb=False,
Zhijun Heeb3ff472014-11-20 13:47:11 -0800414 get_results=False,
Clemenz Portmann4c869052018-03-27 13:31:09 -0700415 ev_comp=0, mono_camera=False):
Ruben Brunk370e2432014-10-14 18:33:23 -0700416 """Perform a 3A operation on the device.
417
418 Triggers some or all of AE, AWB, and AF, and returns once they have
419 converged. Uses the vendor 3A that is implemented inside the HAL.
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700420 Note: do_awb is always enabled regardless of do_awb flag
Ruben Brunk370e2432014-10-14 18:33:23 -0700421
422 Throws an assertion if 3A fails to converge.
423
424 Args:
425 regions_ae: List of weighted AE regions.
426 regions_awb: List of weighted AWB regions.
427 regions_af: List of weighted AF regions.
428 do_ae: Trigger AE and wait for it to converge.
429 do_awb: Wait for AWB to converge.
430 do_af: Trigger AF and wait for it to converge.
431 lock_ae: Request AE lock after convergence, and wait for it.
432 lock_awb: Request AWB lock after convergence, and wait for it.
433 get_results: Return the 3A results from this function.
Zhijun Heeb3ff472014-11-20 13:47:11 -0800434 ev_comp: An EV compensation value to use when running AE.
Clemenz Portmann4c869052018-03-27 13:31:09 -0700435 mono_camera: Boolean for monochrome camera.
Ruben Brunk370e2432014-10-14 18:33:23 -0700436
437 Region format in args:
438 Arguments are lists of weighted regions; each weighted region is a
439 list of 5 values, [x,y,w,h, wgt], and each argument is a list of
440 these 5-value lists. The coordinates are given as normalized
441 rectangles (x,y,w,h) specifying the region. For example:
442 [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
443 Weights are non-negative integers.
444
445 Returns:
446 Five values are returned if get_results is true::
447 * AE sensitivity; None if do_ae is False
448 * AE exposure time; None if do_ae is False
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700449 * AWB gains (list);
450 * AWB transform (list);
Ruben Brunk370e2432014-10-14 18:33:23 -0700451 * AF focus position; None if do_af is false
452 Otherwise, it returns five None values.
453 """
454 print "Running vendor 3A on device"
455 cmd = {}
456 cmd["cmdName"] = "do3A"
457 cmd["regions"] = {"ae": sum(regions_ae, []),
458 "awb": sum(regions_awb, []),
459 "af": sum(regions_af, [])}
460 cmd["triggers"] = {"ae": do_ae, "af": do_af}
461 if lock_ae:
462 cmd["aeLock"] = True
463 if lock_awb:
464 cmd["awbLock"] = True
Zhijun Heeb3ff472014-11-20 13:47:11 -0800465 if ev_comp != 0:
466 cmd["evComp"] = ev_comp
Ruben Brunk370e2432014-10-14 18:33:23 -0700467 self.sock.send(json.dumps(cmd) + "\n")
468
469 # Wait for each specified 3A to converge.
470 ae_sens = None
471 ae_exp = None
472 awb_gains = None
473 awb_transform = None
474 af_dist = None
475 converged = False
476 while True:
477 data,_ = self.__read_response_from_socket()
478 vals = data['strValue'].split()
479 if data['tag'] == 'aeResult':
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700480 if do_ae:
481 ae_sens, ae_exp = [int(i) for i in vals]
Ruben Brunk370e2432014-10-14 18:33:23 -0700482 elif data['tag'] == 'afResult':
Clemenz Portmann3c97e772017-03-23 13:22:06 -0700483 if do_af:
484 af_dist = float(vals[0])
Ruben Brunk370e2432014-10-14 18:33:23 -0700485 elif data['tag'] == 'awbResult':
486 awb_gains = [float(f) for f in vals[:4]]
487 awb_transform = [float(f) for f in vals[4:]]
488 elif data['tag'] == '3aConverged':
489 converged = True
490 elif data['tag'] == '3aDone':
491 break
492 else:
493 raise its.error.Error('Invalid command response')
494 if converged and not get_results:
495 return None,None,None,None,None
Clemenz Portmann4c869052018-03-27 13:31:09 -0700496 if (do_ae and ae_sens == None or (not mono_camera and do_awb and awb_gains == None)
Ruben Brunk370e2432014-10-14 18:33:23 -0700497 or do_af and af_dist == None or not converged):
Clemenz Portmann4c869052018-03-27 13:31:09 -0700498
Ruben Brunk370e2432014-10-14 18:33:23 -0700499 raise its.error.Error('3A failed to converge')
500 return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
501
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700502 def do_capture(self, cap_request,
503 out_surfaces=None, reprocess_format=None, repeat_request=None):
Ruben Brunk370e2432014-10-14 18:33:23 -0700504 """Issue capture request(s), and read back the image(s) and metadata.
505
506 The main top-level function for capturing one or more images using the
507 device. Captures a single image if cap_request is a single object, and
508 captures a burst if it is a list of objects.
509
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700510 The optional repeat_request field can be used to assign a repeating
511 request list ran in background for 3 seconds to warm up the capturing
512 pipeline before start capturing. The repeat_requests will be ran on a
513 640x480 YUV surface without sending any data back. The caller needs to
514 make sure the stream configuration defined by out_surfaces and
515 repeat_request are valid or do_capture may fail because device does not
516 support such stream configuration.
517
Ruben Brunk370e2432014-10-14 18:33:23 -0700518 The out_surfaces field can specify the width(s), height(s), and
519 format(s) of the captured image. The formats may be "yuv", "jpeg",
Timothy Knight67d8ec92015-08-31 13:14:46 -0700520 "dng", "raw", "raw10", "raw12", or "rawStats". The default is a YUV420
521 frame ("yuv") corresponding to a full sensor frame.
Ruben Brunk370e2432014-10-14 18:33:23 -0700522
Shuzhen Wang80822a92018-01-29 09:27:42 -0800523 Optionally the out_surfaces field can specify physical camera id(s) if the
524 current camera device is a logical multi-camera. The physical camera id
525 must refer to a physical camera backing this logical camera device. And
526 only "yuv", "raw", "raw10", "raw12" support the physical camera id field.
527
528 Currently only 2 physical streams with the same format are supported, one
529 from each physical camera:
530 - yuv physical streams of the same size.
531 - raw physical streams with the same or different sizes, depending on
532 device capability. (Different physical cameras may have different raw sizes).
533
Ruben Brunk370e2432014-10-14 18:33:23 -0700534 Note that one or more surfaces can be specified, allowing a capture to
535 request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
536 yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
537 default is the largest resolution available for the format of that
538 surface. At most one output surface can be specified for a given format,
539 and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
540
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700541 If reprocess_format is not None, for each request, an intermediate
542 buffer of the given reprocess_format will be captured from camera and
543 the intermediate buffer will be reprocessed to the output surfaces. The
544 following settings will be turned off when capturing the intermediate
545 buffer and will be applied when reprocessing the intermediate buffer.
546 1. android.noiseReduction.mode
547 2. android.edge.mode
548 3. android.reprocess.effectiveExposureFactor
549
550 Supported reprocess format are "yuv" and "private". Supported output
551 surface formats when reprocessing is enabled are "yuv" and "jpeg".
552
Ruben Brunk370e2432014-10-14 18:33:23 -0700553 Example of a single capture request:
554
555 {
556 "android.sensor.exposureTime": 100*1000*1000,
557 "android.sensor.sensitivity": 100
558 }
559
560 Example of a list of capture requests:
561
562 [
563 {
564 "android.sensor.exposureTime": 100*1000*1000,
565 "android.sensor.sensitivity": 100
566 },
567 {
568 "android.sensor.exposureTime": 100*1000*1000,
569 "android.sensor.sensitivity": 200
570 }
571 ]
572
573 Examples of output surface specifications:
574
575 {
576 "width": 640,
577 "height": 480,
578 "format": "yuv"
579 }
580
581 [
582 {
583 "format": "jpeg"
584 },
585 {
586 "format": "raw"
587 }
588 ]
589
590 The following variables defined in this class are shortcuts for
591 specifying one or more formats where each output is the full size for
592 that format; they can be used as values for the out_surfaces arguments:
593
594 CAP_RAW
595 CAP_DNG
596 CAP_YUV
597 CAP_JPEG
598 CAP_RAW_YUV
599 CAP_DNG_YUV
600 CAP_RAW_JPEG
601 CAP_DNG_JPEG
602 CAP_YUV_JPEG
603 CAP_RAW_YUV_JPEG
604 CAP_DNG_YUV_JPEG
605
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700606 If multiple formats are specified, then this function returns multiple
Ruben Brunk370e2432014-10-14 18:33:23 -0700607 capture objects, one for each requested format. If multiple formats and
608 multiple captures (i.e. a burst) are specified, then this function
609 returns multiple lists of capture objects. In both cases, the order of
610 the returned objects matches the order of the requested formats in the
611 out_surfaces parameter. For example:
612
613 yuv_cap = do_capture( req1 )
614 yuv_cap = do_capture( req1, yuv_fmt )
615 yuv_cap, raw_cap = do_capture( req1, [yuv_fmt,raw_fmt] )
616 yuv_caps = do_capture( [req1,req2], yuv_fmt )
617 yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
618
Timothy Knight67d8ec92015-08-31 13:14:46 -0700619 The "rawStats" format processes the raw image and returns a new image
620 of statistics from the raw image. The format takes additional keys,
621 "gridWidth" and "gridHeight" which are size of grid cells in a 2D grid
622 of the raw image. For each grid cell, the mean and variance of each raw
623 channel is computed, and the do_capture call returns two 4-element float
624 images of dimensions (rawWidth / gridWidth, rawHeight / gridHeight),
625 concatenated back-to-back, where the first iamge contains the 4-channel
Timothy Knightff3032b2017-02-01 15:10:51 -0800626 means and the second contains the 4-channel variances. Note that only
627 pixels in the active array crop region are used; pixels outside this
628 region (for example optical black rows) are cropped out before the
629 gridding and statistics computation is performed.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700630
631 For the rawStats format, if the gridWidth is not provided then the raw
632 image width is used as the default, and similarly for gridHeight. With
633 this, the following is an example of a output description that computes
634 the mean and variance across each image row:
635
636 {
637 "gridHeight": 1,
638 "format": "rawStats"
639 }
640
Ruben Brunk370e2432014-10-14 18:33:23 -0700641 Args:
642 cap_request: The Python dict/list specifying the capture(s), which
643 will be converted to JSON and sent to the device.
644 out_surfaces: (Optional) specifications of the output image formats
645 and sizes to use for each capture.
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700646 reprocess_format: (Optional) The reprocessing format. If not None,
647 reprocessing will be enabled.
Ruben Brunk370e2432014-10-14 18:33:23 -0700648
649 Returns:
650 An object, list of objects, or list of lists of objects, where each
651 object contains the following fields:
652 * data: the image data as a numpy array of bytes.
653 * width: the width of the captured image.
654 * height: the height of the captured image.
Timothy Knight67d8ec92015-08-31 13:14:46 -0700655 * format: image the format, in [
656 "yuv","jpeg","raw","raw10","raw12","rawStats","dng"].
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700657 * metadata: the capture result object (Python dictionary).
Ruben Brunk370e2432014-10-14 18:33:23 -0700658 """
659 cmd = {}
Chien-Yu Chen4e1e2cc2015-06-08 17:46:52 -0700660 if reprocess_format != None:
661 cmd["cmdName"] = "doReprocessCapture"
662 cmd["reprocessFormat"] = reprocess_format
663 else:
664 cmd["cmdName"] = "doCapture"
Yin-Chia Yeh9e5d9ac2016-03-30 12:04:12 -0700665
666 if repeat_request is not None and reprocess_format is not None:
667 raise its.error.Error('repeating request + reprocessing is not supported')
668
669 if repeat_request is None:
670 cmd["repeatRequests"] = []
671 elif not isinstance(repeat_request, list):
672 cmd["repeatRequests"] = [repeat_request]
673 else:
674 cmd["repeatRequests"] = repeat_request
675
Ruben Brunk370e2432014-10-14 18:33:23 -0700676 if not isinstance(cap_request, list):
677 cmd["captureRequests"] = [cap_request]
678 else:
679 cmd["captureRequests"] = cap_request
680 if out_surfaces is not None:
681 if not isinstance(out_surfaces, list):
682 cmd["outputSurfaces"] = [out_surfaces]
683 else:
684 cmd["outputSurfaces"] = out_surfaces
Lu75f22fc2016-02-19 10:54:07 -0800685 formats = [c["format"] if "format" in c else "yuv"
Ruben Brunk370e2432014-10-14 18:33:23 -0700686 for c in cmd["outputSurfaces"]]
687 formats = [s if s != "jpg" else "jpeg" for s in formats]
688 else:
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700689 max_yuv_size = its.objects.get_available_output_sizes(
690 "yuv", self.props)[0]
Ruben Brunk370e2432014-10-14 18:33:23 -0700691 formats = ['yuv']
Yin-Chia Yeh409f2e92016-03-16 18:55:31 -0700692 cmd["outputSurfaces"] = [{"format": "yuv",
693 "width" : max_yuv_size[0],
694 "height": max_yuv_size[1]}]
Shuzhen Wang80822a92018-01-29 09:27:42 -0800695
696 # Figure out requested physical camera ids, physical and logical
697 # streams.
698 physical_cam_ids = {}
699 physical_buffers = {}
700 physical_cam_format = None
701 logical_cam_formats = []
702 for i,s in enumerate(cmd["outputSurfaces"]):
703 if "format" in s and s["format"] in ["yuv", "raw", "raw10", "raw12"]:
704 if "physicalCamera" in s:
705 if physical_cam_format is not None and s["format"] != physical_cam_format:
706 raise its.error.Error('ITS does not support capturing multiple ' +
707 'physical formats yet')
708 physical_cam_ids[i] = s["physicalCamera"]
709 physical_buffers[s["physicalCamera"]] = []
710 physical_cam_format = s["format"]
711 else:
712 logical_cam_formats.append(s["format"])
713 else:
714 logical_cam_formats.append(s["format"])
715
Ruben Brunk370e2432014-10-14 18:33:23 -0700716 ncap = len(cmd["captureRequests"])
717 nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
Lu7c6f52e2015-12-15 14:42:18 -0800718 # Only allow yuv output to multiple targets
Shuzhen Wang80822a92018-01-29 09:27:42 -0800719 logical_yuv_surfaces = [s for s in cmd["outputSurfaces"] if s["format"]=="yuv"\
720 and "physicalCamera" not in s]
721 n_yuv = len(logical_yuv_surfaces)
Lu7c6f52e2015-12-15 14:42:18 -0800722 # Compute the buffer size of YUV targets
Lu75f22fc2016-02-19 10:54:07 -0800723 yuv_maxsize_1d = 0
Shuzhen Wang80822a92018-01-29 09:27:42 -0800724 for s in logical_yuv_surfaces:
Lu75f22fc2016-02-19 10:54:07 -0800725 if not ("width" in s and "height" in s):
726 if self.props is None:
727 raise its.error.Error('Camera props are unavailable')
728 yuv_maxsize_2d = its.objects.get_available_output_sizes(
729 "yuv", self.props)[0]
730 yuv_maxsize_1d = yuv_maxsize_2d[0] * yuv_maxsize_2d[1] * 3 / 2
731 break
732 yuv_sizes = [c["width"]*c["height"]*3/2
733 if "width" in c and "height" in c
734 else yuv_maxsize_1d
Shuzhen Wang80822a92018-01-29 09:27:42 -0800735 for c in logical_yuv_surfaces]
Lu7c6f52e2015-12-15 14:42:18 -0800736 # Currently we don't pass enough metadta from ItsService to distinguish
737 # different yuv stream of same buffer size
738 if len(yuv_sizes) != len(set(yuv_sizes)):
739 raise its.error.Error(
740 'ITS does not support yuv outputs of same buffer size')
Shuzhen Wang80822a92018-01-29 09:27:42 -0800741 if len(logical_cam_formats) > len(set(logical_cam_formats)):
742 if n_yuv != len(logical_cam_formats) - len(set(logical_cam_formats)) + 1:
Lu7c6f52e2015-12-15 14:42:18 -0800743 raise its.error.Error('Duplicate format requested')
744
Timothy Knight67d8ec92015-08-31 13:14:46 -0700745 raw_formats = 0;
746 raw_formats += 1 if "dng" in formats else 0
747 raw_formats += 1 if "raw" in formats else 0
748 raw_formats += 1 if "raw10" in formats else 0
749 raw_formats += 1 if "raw12" in formats else 0
750 raw_formats += 1 if "rawStats" in formats else 0
751 if raw_formats > 1:
Ruben Brunk370e2432014-10-14 18:33:23 -0700752 raise its.error.Error('Different raw formats not supported')
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700753
754 # Detect long exposure time and set timeout accordingly
755 longest_exp_time = 0
756 for req in cmd["captureRequests"]:
757 if "android.sensor.exposureTime" in req and \
758 req["android.sensor.exposureTime"] > longest_exp_time:
759 longest_exp_time = req["android.sensor.exposureTime"]
760
761 extended_timeout = longest_exp_time / self.SEC_TO_NSEC + \
762 self.SOCK_TIMEOUT
Yin-Chia Yehc9d554d2016-06-01 17:30:32 -0700763 if repeat_request:
764 extended_timeout += self.EXTRA_SOCK_TIMEOUT
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700765 self.sock.settimeout(extended_timeout)
766
Ruben Brunk370e2432014-10-14 18:33:23 -0700767 print "Capturing %d frame%s with %d format%s [%s]" % (
768 ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "",
769 ",".join(formats))
770 self.sock.send(json.dumps(cmd) + "\n")
771
772 # Wait for ncap*nsurf images and ncap metadata responses.
773 # Assume that captures come out in the same order as requested in
Chien-Yu Chen682faa22014-10-22 17:34:44 -0700774 # the burst, however individual images of different formats can come
Ruben Brunk370e2432014-10-14 18:33:23 -0700775 # out in any order for that capture.
776 nbufs = 0
Lu7c6f52e2015-12-15 14:42:18 -0800777 bufs = {"raw":[], "raw10":[], "raw12":[],
Timothy Knight67d8ec92015-08-31 13:14:46 -0700778 "rawStats":[], "dng":[], "jpeg":[]}
Lu7c6f52e2015-12-15 14:42:18 -0800779 yuv_bufs = {size:[] for size in yuv_sizes}
Ruben Brunk370e2432014-10-14 18:33:23 -0700780 mds = []
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800781 physical_mds = []
Ruben Brunk370e2432014-10-14 18:33:23 -0700782 widths = None
783 heights = None
784 while nbufs < ncap*nsurf or len(mds) < ncap:
785 jsonObj,buf = self.__read_response_from_socket()
Lu7c6f52e2015-12-15 14:42:18 -0800786 if jsonObj['tag'] in ['jpegImage', 'rawImage', \
Timothy Knight67d8ec92015-08-31 13:14:46 -0700787 'raw10Image', 'raw12Image', 'rawStatsImage', 'dngImage'] \
788 and buf is not None:
Ruben Brunk370e2432014-10-14 18:33:23 -0700789 fmt = jsonObj['tag'][:-5]
790 bufs[fmt].append(buf)
791 nbufs += 1
Lu7c6f52e2015-12-15 14:42:18 -0800792 elif jsonObj['tag'] == 'yuvImage':
793 buf_size = numpy.product(buf.shape)
794 yuv_bufs[buf_size].append(buf)
795 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700796 elif jsonObj['tag'] == 'captureResults':
797 mds.append(jsonObj['objValue']['captureResult'])
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800798 physical_mds.append(jsonObj['objValue']['physicalResults'])
Ruben Brunk370e2432014-10-14 18:33:23 -0700799 outputs = jsonObj['objValue']['outputs']
800 widths = [out['width'] for out in outputs]
801 heights = [out['height'] for out in outputs]
802 else:
Shuzhen Wang80822a92018-01-29 09:27:42 -0800803 tagString = unicodedata.normalize('NFKD', jsonObj['tag']).encode('ascii', 'ignore');
804 for x in ['rawImage', 'raw10Image', 'raw12Image', 'yuvImage']:
805 if (tagString.startswith(x)):
806 physicalId = jsonObj['tag'][len(x):];
807 if physicalId in physical_cam_ids.values():
808 physical_buffers[physicalId].append(buf)
809 nbufs += 1
Ruben Brunk370e2432014-10-14 18:33:23 -0700810 rets = []
811 for j,fmt in enumerate(formats):
812 objs = []
813 for i in range(ncap):
814 obj = {}
Ruben Brunk370e2432014-10-14 18:33:23 -0700815 obj["width"] = widths[j]
816 obj["height"] = heights[j]
817 obj["format"] = fmt
Shuzhen Wang96ecf752018-01-30 10:33:31 -0800818 if j in physical_cam_ids:
819 for physical_md in physical_mds[i]:
820 if physical_cam_ids[j] in physical_md:
821 obj["metadata"] = physical_md[physical_cam_ids[j]]
822 break
823 else:
824 obj["metadata"] = mds[i]
Shuzhen Wang80822a92018-01-29 09:27:42 -0800825
826 if j in physical_cam_ids:
827 obj["data"] = physical_buffers[physical_cam_ids[j]][i]
828 elif fmt == 'yuv':
Lu7c6f52e2015-12-15 14:42:18 -0800829 buf_size = widths[j] * heights[j] * 3 / 2
830 obj["data"] = yuv_bufs[buf_size][i]
831 else:
832 obj["data"] = bufs[fmt][i]
Ruben Brunk370e2432014-10-14 18:33:23 -0700833 objs.append(obj)
834 rets.append(objs if ncap>1 else objs[0])
Yin-Chia Yeh39f8f4b2015-07-17 12:29:31 -0700835 self.sock.settimeout(self.SOCK_TIMEOUT)
Ruben Brunk370e2432014-10-14 18:33:23 -0700836 return rets if len(rets)>1 else rets[0]
837
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700838def get_device_id():
839 """ Return the ID of the device that the test is running on.
840
841 Return the device ID provided in the command line if it's connected. If no
842 device ID is provided in the command line and there is only one device
843 connected, return the device ID by parsing the result of "adb devices".
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700844 Also, if the environment variable ANDROID_SERIAL is set, use it as device
845 id. When both ANDROID_SERIAL and device argument present, device argument
846 takes priority.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700847
848 Raise an exception if no device is connected; or the device ID provided in
849 the command line is not connected; or no device ID is provided in the
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700850 command line or environment variable and there are more than 1 device
851 connected.
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700852
853 Returns:
854 Device ID string.
855 """
856 device_id = None
Clemenz Portmannf7f23ee2016-08-18 13:00:59 -0700857
858 # Check if device id is set in env
859 if "ANDROID_SERIAL" in os.environ:
860 device_id = os.environ["ANDROID_SERIAL"]
861
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700862 for s in sys.argv[1:]:
863 if s[:7] == "device=" and len(s) > 7:
864 device_id = str(s[7:])
865
866 # Get a list of connected devices
867 devices = []
868 command = "adb devices"
869 proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
870 output, error = proc.communicate()
871 for line in output.split(os.linesep):
872 device_info = line.split()
873 if len(device_info) == 2 and device_info[1] == "device":
874 devices.append(device_info[0])
875
876 if len(devices) == 0:
877 raise its.error.Error("No device is connected!")
878 elif device_id is not None and device_id not in devices:
879 raise its.error.Error(device_id + " is not connected!")
880 elif device_id is None and len(devices) >= 2:
881 raise its.error.Error("More than 1 device are connected. " +
882 "Use device=<device_id> to specify a device to test.")
883 elif len(devices) == 1:
884 device_id = devices[0]
885
886 return device_id
887
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700888def report_result(device_id, camera_id, results):
Timothy Knighted076002014-10-23 16:12:26 -0700889 """Send a pass/fail result to the device, via an intent.
890
891 Args:
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700892 device_id: The ID string of the device to report the results to.
Timothy Knighted076002014-10-23 16:12:26 -0700893 camera_id: The ID string of the camera for which to report pass/fail.
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700894 results: a dictionary contains all ITS scenes as key and result/summary
895 of current ITS run. See test_report_result unit test for
896 an example.
Timothy Knighted076002014-10-23 16:12:26 -0700897 Returns:
898 Nothing.
899 """
Chien-Yu Chen1da23132015-07-22 15:24:41 -0700900 adb = "adb -s " + device_id
Sam Linf2f66002017-02-27 21:08:11 -0800901
902 # Start ItsTestActivity to prevent flaky
903 cmd = "%s shell am start %s --activity-brought-to-front" % (adb, ItsSession.ITS_TEST_ACTIVITY)
904 _run(cmd)
905
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700906 # Validate/process results argument
907 for scene in results:
908 result_key = ItsSession.RESULT_KEY
909 summary_key = ItsSession.SUMMARY_KEY
910 if result_key not in results[scene]:
911 raise its.error.Error('ITS result not found for ' + scene)
912 if results[scene][result_key] not in ItsSession.RESULT_VALUES:
913 raise its.error.Error('Unknown ITS result for %s: %s' % (
914 scene, results[result_key]))
915 if summary_key in results[scene]:
916 device_summary_path = "/sdcard/its_camera%s_%s.txt" % (
917 camera_id, scene)
918 _run("%s push %s %s" % (
919 adb, results[scene][summary_key], device_summary_path))
920 results[scene][summary_key] = device_summary_path
Sam Linf2f66002017-02-27 21:08:11 -0800921
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700922 json_results = json.dumps(results)
923 cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % (
924 adb, ItsSession.ACTION_ITS_RESULT,
925 ItsSession.EXTRA_VERSION, ItsSession.CURRENT_ITS_VERSION,
926 ItsSession.EXTRA_CAMERA_ID, camera_id,
927 ItsSession.EXTRA_RESULTS, json_results)
928 if len(cmd) > 4095:
929 print "ITS command string might be too long! len:", len(cmd)
930 _run(cmd)
Ruben Brunk370e2432014-10-14 18:33:23 -0700931
Sam Linf2f66002017-02-27 21:08:11 -0800932def get_device_fingerprint(device_id):
933 """ Return the Build FingerPrint of the device that the test is running on.
934
935 Returns:
936 Device Build Fingerprint string.
937 """
938 device_bfp = None
939
940 # Get a list of connected devices
941
942 com = ('adb -s %s shell getprop | grep ro.build.fingerprint' % device_id)
943 proc = subprocess.Popen(com.split(), stdout=subprocess.PIPE)
944 output, error = proc.communicate()
945 assert error is None
946
947 lst = string.split( \
948 string.replace( \
949 string.replace( \
950 string.replace(output,
951 '\n', ''), '[', ''), ']', ''), \
952 ' ')
953
954 if lst[0].find('ro.build.fingerprint') != -1:
955 device_bfp = lst[1]
956
957 return device_bfp
958
Ruben Brunk370e2432014-10-14 18:33:23 -0700959def _run(cmd):
960 """Replacement for os.system, with hiding of stdout+stderr messages.
961 """
962 with open(os.devnull, 'wb') as devnull:
963 subprocess.check_call(
964 cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
965
966class __UnitTest(unittest.TestCase):
967 """Run a suite of unit tests on this module.
968 """
969
Yin-Chia Yeh02eeffb2016-08-16 15:28:03 -0700970 """
971 # TODO: this test currently needs connected device to pass
972 # Need to remove that dependency before enabling the test
973 def test_report_result(self):
974 device_id = get_device_id()
975 camera_id = "1"
976 result_key = ItsSession.RESULT_KEY
977 results = {"scene0":{result_key:"PASS"},
978 "scene1":{result_key:"PASS"},
979 "scene2":{result_key:"PASS"},
980 "scene3":{result_key:"PASS"},
981 "sceneNotExist":{result_key:"FAIL"}}
982 report_result(device_id, camera_id, results)
983 """
Ruben Brunk370e2432014-10-14 18:33:23 -0700984
985if __name__ == '__main__':
986 unittest.main()
987