1 #!/usr/bin/env /usr/bin/python3
5 from getopt import getopt
6 from tempfile import mkstemp
7 from subprocess import Popen, PIPE
8 from difflib import Differ
14 class E_syntax(Exception):
16 class E_badline(Exception):
19 def INFO(msg, end='\n'):
31 print(msg, file=sys.stderr)
36 print(msg.strip() + '\n')
37 print("""Usage: testpd [-dvarslR] [test, ...]
44 -R <directory> Save test reports to <directory>
45 <test> Protocol decoder name ("i2c") and optionally test name ("i2c/icc")""")
50 if 'pdlist' not in tc or not tc['pdlist']:
51 return("No protocol decoders")
52 if 'input' not in tc or not tc['input']:
54 if 'output' not in tc or not tc['output']:
56 for op in tc['output']:
58 return("No match in output")
63 def parse_testfile(path, pd, tc, op_type, op_class):
64 DBG("Opening '%s'" % path)
66 for line in open(path).read().split('\n'):
69 if len(line) == 0 or line[0] == "#":
72 if not tclist and f[0] != "test":
86 elif key == 'protocol-decoder':
96 # Always needs <key> <value>
102 opt, val = b.split('=')
108 pd_spec['probes'].append([opt, val])
110 pd_spec['options'].append([opt, val])
113 tclist[-1]['pdlist'].append(pd_spec)
117 tclist[-1]['stack'] = f
121 tclist[-1]['input'] = f[0]
122 elif key == 'output':
129 # Always needs <key> <value>
139 tclist[-1]['output'].append(op_spec)
142 except E_badline as e:
143 ERR("Invalid syntax in %s: line '%s'" % (path, line))
145 except E_syntax as e:
146 ERR("Unable to parse %s: unknown line '%s'" % (path, line))
149 # If a specific testcase was requested, keep only that one.
156 # ...and a specific output type
157 if op_type is not None:
159 for op in target_tc['output']:
160 if op['type'] == op_type:
161 # ...and a specific output class
162 if op_class is None or ('class' in op and op['class'] == op_class):
163 target_oplist.append(op)
164 DBG("match on [%s]" % str(op))
165 target_tc['output'] = target_oplist
166 if target_tc is None:
171 error = check_tclist(t)
173 ERR("Error in %s: %s" % (path, error))
179 def get_tests(testnames):
181 for testspec in testnames:
182 # Optional testspec in the form i2c/rtc
183 tc = op_type = op_class = None
184 ts = testspec.strip("/").split("/")
192 path = os.path.join(decoders_dir, pd)
193 if not os.path.isdir(path):
194 # User specified non-existent PD
195 raise Exception("%s not found." % path)
196 path = os.path.join(decoders_dir, pd, "test/test.conf")
197 if not os.path.exists(path):
198 # PD doesn't have any tests yet
200 tests.append(parse_testfile(path, pd, tc, op_type, op_class))
205 def diff_files(f1, f2):
206 t1 = open(f1).readlines()
207 t2 = open(f2).readlines()
210 for line in d.compare(t1, t2):
211 if line[:2] in ('- ', '+ '):
212 diff.append(line.strip())
217 def run_tests(tests):
220 cmd = os.path.join(tests_dir, 'runtc')
226 for pd in tc['pdlist']:
227 args.extend(['-P', pd['name']])
228 for label, probe in pd['probes']:
229 args.extend(['-p', "%s=%d" % (label, probe)])
230 for option, value in pd['options']:
231 args.extend(['-o', "%s=%s" % (option, value)])
232 args.extend(['-i', os.path.join(dumps_dir, tc['input'])])
233 for op in tc['output']:
234 name = "%s/%s/%s" % (tc['pd'], tc['name'], op['type'])
235 opargs = ['-O', "%s:%s" % (op['pd'], op['type'])]
237 opargs[-1] += ":%s" % op['class']
238 name += "/%s" % op['class']
240 dots = '.' * (60 - len(name) - 2)
241 INFO("%s %s " % (name, dots), end='')
246 fd, outfile = mkstemp()
248 opargs.extend(['-f', outfile])
249 DBG("Running %s" % (' '.join(args + opargs)))
250 p = Popen(args + opargs, stdout=PIPE, stderr=PIPE)
251 stdout, stderr = p.communicate()
253 results[-1]['statistics'] = stdout.decode('utf-8').strip()
255 results[-1]['error'] = stderr.decode('utf-8').strip()
257 elif p.returncode != 0:
258 # runtc indicated an error, but didn't output a
259 # message on stderr about it
260 results[-1]['error'] = "Unknown error: runtc %d" % p.returncode
261 # Only bother with the diff if it all worked.
262 if 'error' not in results[-1]:
263 match = "%s/%s/test/%s" % (decoders_dir, op['pd'], op['match'])
264 diff = diff_files(match, outfile)
266 results[-1]['diff'] = diff
267 except Exception as e:
268 results[-1]['error'] = str(e)
272 if 'diff' in results[-1]:
273 INFO("Output mismatch")
274 elif 'error' in results[-1]:
275 error = results[-1]['error']
277 error = error[:17] + '...'
281 gen_report(results[-1])
283 return results, errors
286 def gen_report(result):
288 if 'error' in result:
290 out.append(result['error'])
293 out.append("Test output mismatch:")
294 out.extend(result['diff'])
296 if 'statistics' in result:
297 out.extend(["Statistics:", result['statistics']])
301 text = "Testcase: %s\n" % result['testcase']
302 text += '\n'.join(out)
307 filename = result['testcase'].replace('/', '_')
308 open(os.path.join(report_dir, filename), 'w').write(text)
313 def show_tests(tests):
316 print("Testcase: %s/%s" % (tc['pd'], tc['name']))
317 for pd in tc['pdlist']:
318 print(" Protocol decoder: %s" % pd['name'])
319 for label, probe in pd['probes']:
320 print(" Probe %s=%d" % (label, probe))
321 for option, value in pd['options']:
322 print(" Option %s=%d" % (option, value))
324 print(" Stack: %s" % ' '.join(tc['stack']))
325 print(" Input: %s" % tc['input'])
326 for op in tc['output']:
327 print(" Output:\n Protocol decoder: %s" % op['pd'])
328 print(" Type: %s" % op['type'])
330 print(" Class: %s" % op['class'])
331 print(" Match: %s" % op['match'])
335 def list_tests(tests):
338 for op in tc['output']:
339 line = "%s/%s/%s" % (tc['pd'], tc['name'], op['type'])
341 line += "/%s" % op['class']
350 tests_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
351 base_dir = os.path.abspath(os.path.join(os.curdir, tests_dir, os.path.pardir))
352 dumps_dir = os.path.abspath(os.path.join(base_dir, os.path.pardir, 'sigrok-dumps'))
353 decoders_dir = os.path.abspath(os.path.join(base_dir, 'decoders'))
355 if len(sys.argv) == 1:
358 opt_all = opt_run = opt_show = opt_list = False
360 opts, args = getopt(sys.argv[1:], "dvarslRS:")
361 for opt, arg in opts:
379 if opt_run and opt_show:
380 usage("Use either -s or -r, not both.")
382 usage("Specify either -a or tests, not both.")
383 if report_dir is not None and not os.path.isdir(report_dir):
384 usage("%s is not a directory" % report_dir)
389 testlist = get_tests(args)
391 testlist = get_tests(os.listdir(decoders_dir))
393 usage("Specify either -a or tests.")
396 if not os.path.isdir(dumps_dir):
397 ERR("Could not find sigrok-dumps repository at %s" % dumps_dir)
399 results, errors = run_tests(testlist)
407 except Exception as e:
408 print("Error: %s" % str(e))