forked from raspberrypi/rpicam-apps
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.py
More file actions
executable file
·338 lines (295 loc) · 14.1 KB
/
test.py
File metadata and controls
executable file
·338 lines (295 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#!/usr/bin/env python3
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (C) 2020, Raspberry Pi (Trading) Limited
#
# test.py - some automated testing for libcamera-apps
# These tests are very far from exhaustive, for which I apologise
# profusely, but still it's better than nothing. The rule going
# forward is that any new feature, or feature that needs a fix, must
# get a test in here.
import argparse
import os
import os.path
import subprocess
from timeit import default_timer as timer
class TestFailure(Exception):
def __init__(self, message):
self.message = message
super().__init__(self.message)
def check_exists(file, preamble):
if not os.path.isfile(file):
raise TestFailure(preamble + ": " + file + " not found")
def clean_dir(dir, exts = ('.jpg', '.png', '.bmp', '.dng', '.h264', '.mjpeg', '.raw', '.txt')):
for file in os.listdir(dir):
if file.endswith(exts):
os.remove(os.path.join(dir, file))
def run_executable(args, logfile):
start_time = timer()
with open(logfile, 'w') as logfile:
p = subprocess.Popen(args, stdout = logfile, stderr = subprocess.STDOUT)
p.communicate()
time_taken = timer() - start_time
return p.returncode, time_taken
def check_retcode(retcode, preamble):
if retcode:
raise TestFailure(preamble + " failed, return code " + str(retcode))
def check_time(time_taken, low, high, preamble):
if time_taken < low or time_taken > high:
raise TestFailure(preamble + " failed, time taken " + str(time_taken) + " seconds")
def check_jpeg(file, preamble):
# I haven't found a Python exif library that actually reads all the multiple image
# tags (pyexiv2 does, but only runs on 64-bit systems... I mean, really??).
# So we'll just use exiftool if it appears to be installed.
try:
p = subprocess.Popen(['exiftool', file], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
stdout = p.communicate()[0].decode('utf-8')
except FileNotFoundError as e:
print("WARNING:", preamble, "- exiftool not found")
return
if p.returncode:
raise TestFailure(preamble + "- exiftool failed")
if "Image Width" not in stdout:
raise TestFailure(preamble + "- bad EXIF data")
def test_hello(dir):
executable = os.path.join(dir, 'libcamera-hello')
logfile = os.path.join(dir, 'log.txt')
print("Testing", executable)
check_exists(executable, 'test_hello')
clean_dir(dir)
# "run test". Just see if the executable appeared to run.
print(" run test")
retcode, time_taken = run_executable([executable, '-t', '2000'], logfile)
check_retcode(retcode, "test_hello: run test")
check_time(time_taken, 2, 6, "test_hello: run test")
# "roi test". Specify an roi and see if it blows up.
print(" roi test")
retcode, time_taken = run_executable(
[executable, '-t', '2000', '--roi', '0.25,0.25,0.5,0.5'], logfile)
check_retcode(retcode, "test_hello: roi test")
check_time(time_taken, 2, 6, "test_hello: roi test")
# "controls test". Specify some image controls and see if it blows up.
print(" controls test")
retcode, time_taken = run_executable(
[executable, '-t', '2000', '--brightness', '0.2', '--contrast', '1.2',
'--saturation', '1.3', '--sharpness', '1.5'], logfile)
check_retcode(retcode, "test_hello: controls test")
check_time(time_taken, 2, 6, "test_hello: controls test")
print("libcamera-hello tests passed")
def check_size(file, limit, presamble):
if os.path.getsize(file) < limit:
raise TestFailure(preamble + " failed, file " + file + " too small")
def test_still(dir):
executable = os.path.join(dir, 'libcamera-still')
output_jpg = os.path.join(dir, 'test.jpg')
output_png = os.path.join(dir, 'test.png')
output_bmp = os.path.join(dir, 'test.bmp')
output_dng = os.path.join(dir, 'test.dng')
logfile = os.path.join(dir, 'log.txt')
print("Testing", executable)
check_exists(executable, 'test_still')
clean_dir(dir)
# "jpg test". See if the executable appears to run and write an jpg output file.
print(" jpg test")
retcode, time_taken = run_executable([executable, '-t', '1000', '-o', output_jpg], logfile)
check_retcode(retcode, "test_still: jpg test")
check_time(time_taken, 2, 8, "test_still: jpg test")
check_size(output_jpg, 1024, "test_still: jpg test")
# "png test". As above, but write a png.
print(" png test")
retcode, time_taken = run_executable(
[executable, '-t', '1000', '-e', 'png', '-o', output_png], logfile)
check_retcode(retcode, "test_still: png test")
check_time(time_taken, 3, 8, "test_still: png test")
check_size(output_png, 1024, "test_still: png test")
# "bmp test". As above, but write a bmp.
print(" bmp test")
retcode, time_taken = run_executable(
[executable, '-t', '1000', '-e', 'bmp', '-o', output_bmp], logfile)
check_retcode(retcode, "test_still: bmp test")
check_time(time_taken, 2, 8, "test_still: bmp test")
check_size(output_png, 1024, "test_still: bmp test")
# "dng test". Write a dng along with the jpg.
print(" dng test")
retcode, time_taken = run_executable(
[executable, '-t', '1000', '-o', output_jpg, '-r'], logfile)
check_retcode(retcode, "test_still: dng test")
check_time(time_taken, 2, 8, "test_still: dng test")
check_size(output_jpg, 1024, "test_still: dng test")
check_size(output_dng, 1024 * 1024, "test_still: dng test")
# "timelapse test". Check that a timelapse sequence captures more than one jpg.
print(" timelapse test")
retcode, time_taken = run_executable(
[executable, '-t', '10000', '--timelapse', '3500', '-o', os.path.join(dir, 'test%03d.jpg')],
logfile)
check_retcode(retcode, "test_still: timelapse test")
check_time(time_taken, 9, 20, "test_still: timelapse test")
check_size(os.path.join(dir, 'test000.jpg'), 1024, "test_still: timelapse test")
check_size(os.path.join(dir, 'test001.jpg'), 1024, "test_still: timelapse test")
if os.path.isfile(os.path.join(dir, 'test002.jpg')):
raise("test_still: timelapse test, unexpected output file")
print("libcamera-still tests passed")
def check_jpeg_shutter(file, shutter_string, iso_string, preamble):
# Verify that the expected shutter_string and iso_string are in the exif.
try:
p = subprocess.Popen(['exiftool', file], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
stdout = p.communicate()[0].decode('utf-8').split('\n')
except FileNotFoundError as e:
print("WARNING:", preamble, "- exiftool not found")
return
shutter_line = [line for line in stdout if "Shutter Speed" in line]
if len(shutter_line) != 1:
print("WARNING:", preamble, "- shutter speed not matched")
elif shutter_string not in shutter_line[0]:
raise(preamble + " - bad shutter value")
iso_line = [line for line in stdout if "ISO" in line]
if len(iso_line) != 1:
print("WARNING:", preamble, "- ISO not matched")
elif iso_string not in iso_line[0]:
raise(preamble + " - bad ISO value")
def test_jpeg(dir):
executable = os.path.join(dir, 'libcamera-jpeg')
output_jpg = os.path.join(dir, 'test.jpg')
output_shutter = os.path.join(dir, 'shutter.jpg')
logfile = os.path.join(dir, 'log.txt')
print("Testing", executable)
check_exists(executable, 'test_jpeg')
clean_dir(dir)
# "jpg test". See if the executable appears to run and write an jpg output file.
print(" jpg test")
retcode, time_taken = run_executable([executable, '-t', '1000', '-o', output_jpg],
logfile)
check_retcode(retcode, "test_jpeg: jpg test")
check_time(time_taken, 2, 8, "test_jpeg: jpg test")
check_size(output_jpg, 1024, "test_jpeg: jpg test")
# For this one, we're actually going to peak inside the jpeg.
check_jpeg(output_jpg, "test_jpeg: jpg test")
# "shutter test". See if we appear to get the shutter/gain we asked for.
print(" shutter test")
retcode, time_taken = run_executable(
[executable, '-t', '1000', '-o', output_shutter,
'--shutter', '20000', '--gain', '1.0', '--awbgains', '1.0,1.0'], logfile)
check_retcode(retcode, "test_jpeg: shutter test")
check_time(time_taken, 2, 8, "test_jpeg: shutter test")
check_size(output_shutter, 1024, "test_jpeg: shutter test")
check_jpeg_shutter(output_shutter, '1/50', '100', "test_jpeg: shutter test")
print("libcamera-jpeg tests passed")
def check_timestamps(file, preamble):
try:
with open(file) as f:
line1 = f.readline()
line2 = f.readline()
line3 = f.readline()
except:
raise TestFailure(preamble + " - could not read data from file")
if not line1.startswith("# timecode format"):
raise TestFailure(preamble + " - bad file header")
try:
t2 = float(line2)
t3 = float(line3)
except:
raise TestFailure(preamble + " - timestamp file contains non-numeric values")
if t2 >= t3:
raise TestFailure(preamble + " - timestamps not increasing")
def test_vid(dir):
executable = os.path.join(dir, 'libcamera-vid')
output_h264 = os.path.join(dir, 'test.h264')
output_mjpeg = os.path.join(dir, 'test.mjpeg')
output_circular = os.path.join(dir, 'circular.h264')
output_pause = os.path.join(dir, 'pause.h264')
output_timestamps = os.path.join(dir, 'timestamps.txt')
logfile = os.path.join(dir, 'log.txt')
print("Testing", executable)
check_exists(executable, 'test_vid')
clean_dir(dir)
# "h264 test". See if the executable appears to run and write an h264 output file.
print(" h264 test")
retcode, time_taken = run_executable([executable, '-t', '2000', '-o', output_h264],
logfile)
check_retcode(retcode, "test_vid: h264 test")
check_time(time_taken, 2, 6, "test_vid: h264 test")
check_size(output_h264, 1024, "test_vid: h264 test")
# "mjpeg test". As above, but write an mjpeg file.
print(" mjpeg test")
retcode, time_taken = run_executable([executable, '-t', '2000', '--codec', 'mjpeg',
'-o', output_mjpeg],
logfile)
check_retcode(retcode, "test_vid: mjpeg test")
check_time(time_taken, 2, 6, "test_vid: mjpeg test")
check_size(output_mjpeg, 1024, "test_vid: mjpeg test")
# "segment test". As above, write the output in single frame segements.
print(" segment test")
retcode, time_taken = run_executable([executable, '-t', '2000', '--codec', 'mjpeg',
'--segment', '1', '-o', os.path.join(dir, 'test%03d.jpg')],
logfile)
check_retcode(retcode, "test_vid: segment test")
check_time(time_taken, 2, 6, "test_vid: segment test")
# A bug in commit b20dc097621a trunctated each jpg to 4096 bytes, so check against 4100:
check_size(os.path.join(dir, 'test035.jpg'), 4100, "test_vid: segment test")
# "circular test". Test circular buffer (really we should wait for it to wrap...)
print(" circular test")
retcode, time_taken = run_executable([executable, '-t', '2000', '--inline', '--circular',
'-o', output_circular], logfile)
check_retcode(retcode, "test_vid: circular test")
check_time(time_taken, 2, 6, "test_vid: circular test")
check_size(output_circular, 1024, "test_vid: circular test")
# "pause test". Should be no output file if we start 'paused'.
print(" pause test")
retcode, time_taken = run_executable([executable, '-t', '2000', '--inline',
'--initial', 'pause', '-o', output_pause], logfile)
check_retcode(retcode, "test_vid: pause test")
check_time(time_taken, 2, 6, "test_vid: pause test")
if os.path.isfile(output_pause):
raise TestFailure("test_vid: pause test - output file was not expected")
# "timestamp test". Check that the timestamp file is written and looks sensible.
print(" timestamp test")
retcode, time_taken = run_executable([executable, '-t', '2000', '-o', output_h264,
'--save-pts', output_timestamps], logfile)
check_retcode(retcode, "test_vid: timestamp test")
check_time(time_taken, 2, 6, "test_vid: timestamp test")
check_size(output_h264, 1024, "test_vid: timestamp test")
check_timestamps(output_timestamps, "test_vid: timestamp test")
print("libcamera-vid tests passed")
def test_raw(dir):
executable = os.path.join(dir, 'libcamera-raw')
output_raw = os.path.join(dir, 'test.raw')
logfile = os.path.join(dir, 'log.txt')
print("Testing", executable)
check_exists(executable, 'test_raw')
clean_dir(dir)
# "raw test". See if the executable appears to run and write an output file.
print(" raw test")
retcode, time_taken = run_executable([executable, '-t', '2000', '-o', output_raw],
logfile)
check_retcode(retcode, "test_vid: raw test")
check_time(time_taken, 2, 8, "test_vid: raw test")
check_size(output_raw, 1024, "test_vid: raw test")
print("libcamera-raw tests passed")
def test_all(apps, dir):
try:
if 'hello' in apps:
test_hello(dir)
if 'still' in apps:
test_still(dir)
if 'jpeg' in apps:
test_jpeg(dir)
if 'vid' in apps:
test_vid(dir)
if 'raw' in apps:
test_raw(dir)
print("All tests passed")
clean_dir(dir)
except TestFailure as e:
print("ERROR:", e)
return
if __name__ == '__main__':
parser = argparse.ArgumentParser(description = 'libcamera-apps automated tests')
parser.add_argument('--apps', '-a', action = 'store', default = 'hello,still,vid,jpeg,raw',
help = 'List of apps to test')
parser.add_argument('--dir', '-d', action = 'store', default = 'build',
help = 'Directory name for executables to test')
args = parser.parse_args()
apps = args.apps.split(',')
dir = args.dir.strip('/')
test_all(apps, dir)