blob: 2a30733b86fc150c66f2de35a076c02cbd491c4d [file] [log] [blame]
Cyrill Gorcunov54533882018-04-25 00:31:21 +03001#!/usr/bin/python3
2
3import subprocess
4import argparse
5import difflib
6import filecmp
7import fnmatch
8import json
9import sys
10import re
11import os
12
13fmtr_class = argparse.ArgumentDefaultsHelpFormatter
14parser = argparse.ArgumentParser(prog = 'nasm-t.py',
15 formatter_class=fmtr_class)
16
17parser.add_argument('-d', '--directory',
18 dest = 'dir', default = './travis/test',
19 help = 'Directory with tests')
20
21parser.add_argument('--nasm',
22 dest = 'nasm', default = './nasm',
23 help = 'Nasm executable to use')
24
25parser.add_argument('--hexdump',
26 dest = 'hexdump', default = '/usr/bin/hexdump',
27 help = 'Hexdump executable to use')
28
29sp = parser.add_subparsers(dest = 'cmd')
30for cmd in ['run']:
31 spp = sp.add_parser(cmd, help = 'Run test cases')
32 spp.add_argument('-t', '--test',
33 dest = 'test',
34 help = 'Run the selected test only',
35 required = False)
36
37for cmd in ['list']:
38 spp = sp.add_parser(cmd, help = 'List test cases')
39
40for cmd in ['update']:
41 spp = sp.add_parser(cmd, help = 'Update test cases with new compiler')
42 spp.add_argument('-t', '--test',
43 dest = 'test',
44 help = 'Update the selected test only',
45 required = False)
46
47args = parser.parse_args()
48
49if args.cmd == None:
50 parser.print_help()
51 sys.exit(1)
52
53def read_stdfile(path):
54 with open(path, "rb") as f:
55 data = f.read().decode("utf-8").strip("\n")
56 f.close()
57 return data
58
59#
60# Check if descriptor has mandatory fields
61def is_valid_desc(desc):
62 if desc == None:
63 return False
64 if 'description' not in desc:
65 return false
66 return True
67
68#
69# Expand ref/id in descriptors array
70def expand_templates(desc_array):
71 desc_ids = { }
72 for d in desc_array:
73 if 'id' in d:
74 desc_ids[d['id']] = d
75 for i, d in enumerate(desc_array):
76 if 'ref' in d and d['ref'] in desc_ids:
77 ref = desc_ids[d['ref']]
78 own = d.copy()
79 desc_array[i] = ref.copy()
80 for k, v in own.items():
81 desc_array[i][k] = v
82 del desc_array[i]['id']
83 return desc_array
84
85def prepare_desc(desc, basedir, name, path):
86 if not is_valid_desc(desc):
87 return False
88 #
89 # Put private fields
90 desc['_base-dir'] = basedir
91 desc['_json-file'] = name
92 desc['_json-path'] = path
93 desc['_test-name'] = basedir + os.sep + name[:-5]
94 #
95 # If no target provided never update
96 if 'target' not in desc:
97 desc['target'] = []
98 desc['update'] = 'false'
99 #
100 # Which code to expect when nasm finishes
101 desc['_wait'] = 0
102 if 'error' in desc:
103 if desc['error'] == 'expected':
104 desc['_wait'] = 1
105 #
106 # Walk over targets and generate match templates
107 # if were not provided yet
108 for d in desc['target']:
109 if 'output' in d and not 'match' in d:
110 d['match'] = d['output'] + ".t"
111 return True
112
113def read_json(path):
114 desc = None
115 try:
116 with open(path, "rb") as f:
117 try:
118 desc = json.loads(f.read().decode("utf-8").strip("\n"))
119 except:
120 desc = None
121 finally:
122 f.close()
123 except:
124 pass
125 return desc
126
127def read_desc(basedir, name):
128 path = basedir + os.sep + name
129 desc = read_json(path)
130 desc_array = []
131 if type(desc) == dict:
132 if prepare_desc(desc, basedir, name, path) == True:
133 desc_array += [desc]
134 elif type(desc) == list:
135 expand_templates(desc)
136 for de in desc:
137 if prepare_desc(de, basedir, name, path) == True:
138 desc_array += [de]
139 return desc_array
140
141def collect_test_desc_from_file(path):
142 if not fnmatch.fnmatch(path, '*.json'):
143 path += '.json'
144 basedir = os.path.dirname(path)
145 filename = os.path.basename(path)
146 return read_desc(basedir, filename)
147
148def collect_test_desc_from_dir(basedir):
149 desc_array = []
150 if os.path.isdir(basedir):
151 for filename in os.listdir(basedir):
152 if os.path.isdir(basedir + os.sep + filename):
153 desc_array += collect_test_desc_from_dir(basedir + os.sep + filename)
154 elif fnmatch.fnmatch(filename, '*.json'):
155 desc = read_desc(basedir, filename)
156 if desc == None:
157 continue
158 desc_array += desc
Cyrill Gorcunov502556c2018-11-03 18:05:53 +0300159 desc_array.sort(key=lambda x: x['_test-name'])
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300160 return desc_array
161
162if args.cmd == 'list':
163 fmt_entry = '%-32s %s'
164 desc_array = collect_test_desc_from_dir(args.dir)
165 print(fmt_entry % ('Name', 'Description'))
166 for desc in desc_array:
167 print(fmt_entry % (desc['_test-name'], desc['description']))
168
169def test_abort(test, message):
170 print("\t%s: %s" % (test, message))
171 print("=== Test %s ABORT ===" % (test))
172 sys.exit(1)
173 return False
174
175def test_fail(test, message):
176 print("\t%s: %s" % (test, message))
177 print("=== Test %s FAIL ===" % (test))
178 return False
179
180def test_skip(test, message):
181 print("\t%s: %s" % (test, message))
182 print("=== Test %s SKIP ===" % (test))
183 return True
184
185def test_over(test):
186 print("=== Test %s ERROR OVER ===" % (test))
187 return True
188
189def test_pass(test):
190 print("=== Test %s PASS ===" % (test))
191 return True
192
193def test_updated(test):
194 print("=== Test %s UPDATED ===" % (test))
195 return True
196
197def run_hexdump(path):
198 p = subprocess.Popen([args.hexdump, "-C", path],
199 stdout = subprocess.PIPE,
200 close_fds = True)
201 if p.wait() == 0:
202 return p
203 return None
204
205def show_std(stdname, data):
206 print("\t--- %s" % (stdname))
207 for i in data.split("\n"):
208 print("\t%s" % i)
209 print("\t---")
210
211def cmp_std(test, data_name, data, match):
212 match_data = read_stdfile(match)
213 if match_data == None:
214 return test_fail(test, "Can't read " + match)
215 if data != match_data:
216 print("\t--- %s" % (data_name))
217 for i in data.split("\n"):
218 print("\t%s" % i)
219 print("\t--- %s" % (match))
220 for i in match_data.split("\n"):
221 print("\t%s" % i)
222
223 diff = difflib.unified_diff(data.split("\n"), match_data.split("\n"),
224 fromfile = data_name, tofile = match)
225 for i in diff:
226 print("\t%s" % i.strip("\n"))
227 print("\t---")
228 return False
229 return True
230
231def show_diff(test, patha, pathb):
232 pa = run_hexdump(patha)
233 pb = run_hexdump(pathb)
234 if pa == None or pb == None:
235 return test_fail(test, "Can't create dumps")
236 sa = pa.stdout.read().decode("utf-8").strip("\n")
237 sb = pb.stdout.read().decode("utf-8").strip("\n")
238 print("\t--- hexdump %s" % (patha))
239 for i in sa.split("\n"):
240 print("\t%s" % i)
241 print("\t--- hexdump %s" % (pathb))
242 for i in sb.split("\n"):
243 print("\t%s" % i)
244 pa.stdout.close()
245 pb.stdout.close()
246
247 diff = difflib.unified_diff(sa.split("\n"), sb.split("\n"),
248 fromfile = patha, tofile = pathb)
249 for i in diff:
250 print("\t%s" % i.strip("\n"))
251 print("\t---")
252 return True
253
254def prepare_run_opts(desc):
255 opts = []
256
257 if 'format' in desc:
258 opts += ['-f', desc['format']]
259 if 'option' in desc:
260 opts += desc['option'].split(" ")
261 for t in desc['target']:
262 if 'output' in t:
263 if 'option' in t:
264 opts += t['option'].split(" ") + [desc['_base-dir'] + os.sep + t['output']]
265 else:
266 opts += ['-o', desc['_base-dir'] + os.sep + t['output']]
267 if 'stdout' in t or 'stderr' in t:
268 if 'option' in t:
269 opts += t['option'].split(" ")
270 if 'source' in desc:
271 opts += [desc['_base-dir'] + os.sep + desc['source']]
272 return opts
273
274def exec_nasm(desc):
275 print("\tProcessing %s" % (desc['_test-name']))
276 opts = [args.nasm] + prepare_run_opts(desc)
277
278 print("\tExecuting %s" % (" ".join(opts)))
279 pnasm = subprocess.Popen(opts,
280 stdout = subprocess.PIPE,
281 stderr = subprocess.PIPE,
282 close_fds = True)
283 if pnasm == None:
284 test_fail(desc['_test-name'], "Unable to execute test")
285 return None
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300286
Cyrill Gorcunov7bb272d2018-11-04 16:12:42 +0300287 stderr = pnasm.stderr.read(1048576).decode("utf-8").strip("\n")
288 stdout = pnasm.stdout.read(1048576).decode("utf-8").strip("\n")
289
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300290 pnasm.stdout.close()
291 pnasm.stderr.close()
292
Cyrill Gorcunov7bb272d2018-11-04 16:12:42 +0300293 wait_rc = pnasm.wait();
294
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300295 if desc['_wait'] != wait_rc:
296 if stdout != "":
297 show_std("stdout", stdout)
298 if stderr != "":
299 show_std("stderr", stderr)
300 test_fail(desc['_test-name'],
301 "Unexpected ret code: " + str(wait_rc))
302 return None, None, None
303 return pnasm, stdout, stderr
304
305def test_run(desc):
306 print("=== Running %s ===" % (desc['_test-name']))
307
308 pnasm, stdout, stderr = exec_nasm(desc)
309 if pnasm == None:
310 return False
311
312 for t in desc['target']:
313 if 'output' in t:
314 output = desc['_base-dir'] + os.sep + t['output']
315 match = desc['_base-dir'] + os.sep + t['match']
316 if desc['_wait'] == 1:
317 continue
318 print("\tComparing %s %s" % (output, match))
319 if filecmp.cmp(match, output) == False:
320 show_diff(desc['_test-name'], match, output)
321 return test_fail(desc['_test-name'], match + " and " + output + " files are different")
322 elif 'stdout' in t:
323 print("\tComparing stdout")
324 match = desc['_base-dir'] + os.sep + t['stdout']
325 if cmp_std(desc['_test-name'], 'stdout', stdout, match) == False:
326 return test_fail(desc['_test-name'], "Stdout mismatch")
327 else:
328 stdout = ""
329 elif 'stderr' in t:
330 print("\tComparing stderr")
331 match = desc['_base-dir'] + os.sep + t['stderr']
332 if cmp_std(desc['_test-name'], 'stderr', stderr, match) == False:
333 return test_fail(desc['_test-name'], "Stderr mismatch")
334 else:
335 stderr = ""
336
337 if stdout != "":
338 show_std("stdout", stdout)
339 return test_fail(desc['_test-name'], "Stdout is not empty")
340
341 if stderr != "":
342 show_std("stderr", stderr)
343 return test_fail(desc['_test-name'], "Stderr is not empty")
344
345 return test_pass(desc['_test-name'])
346
347#
348# Compile sources and generate new targets
349def test_update(desc):
350 print("=== Updating %s ===" % (desc['_test-name']))
351
352 if 'update' in desc and desc['update'] == 'false':
353 return test_skip(desc['_test-name'], "No output provided")
354
Cyrill Gorcunovdcf39372018-10-17 23:36:43 +0300355 pnasm, stdout, stderr = exec_nasm(desc)
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300356 if pnasm == None:
357 return False
358
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300359 for t in desc['target']:
360 if 'output' in t:
361 output = desc['_base-dir'] + os.sep + t['output']
362 match = desc['_base-dir'] + os.sep + t['match']
363 print("\tMoving %s to %s" % (output, match))
364 os.rename(output, match)
365 if 'stdout' in t:
366 match = desc['_base-dir'] + os.sep + t['stdout']
367 print("\tMoving %s to %s" % ('stdout', match))
368 with open(match, "wb") as f:
Cyrill Gorcunov750bc502018-11-03 14:50:09 +0300369 f.write(stdout.encode("utf-8"))
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300370 f.close()
371 if 'stderr' in t:
372 match = desc['_base-dir'] + os.sep + t['stderr']
373 print("\tMoving %s to %s" % ('stderr', match))
374 with open(match, "wb") as f:
Cyrill Gorcunov750bc502018-11-03 14:50:09 +0300375 f.write(stderr.encode("utf-8"))
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300376 f.close()
377
378 return test_updated(desc['_test-name'])
379
380if args.cmd == 'run':
381 desc_array = []
382 if args.test == None:
383 desc_array = collect_test_desc_from_dir(args.dir)
384 else:
385 desc_array = collect_test_desc_from_file(args.test)
386 if len(desc_array) == 0:
387 test_abort(args.test, "Can't obtain test descriptors")
388
389 for desc in desc_array:
390 if test_run(desc) == False:
391 if 'error' in desc and desc['error'] == 'over':
392 test_over(desc['_test-name'])
393 else:
394 test_abort(desc['_test-name'], "Error detected")
395
396if args.cmd == 'update':
397 desc_array = []
398 if args.test == None:
399 desc_array = collect_test_desc_from_dir(args.dir)
400 else:
401 desc_array = collect_test_desc_from_file(args.test)
402 if len(desc_array) == 0:
403 test_abort(args.test, "Can't obtain a test descriptors")
404
405 for desc in desc_array:
406 if test_update(desc) == False:
407 if 'error' in desc and desc['error'] == 'over':
408 test_over(desc['_test-name'])
409 else:
410 test_abort(desc['_test-name'], "Error detected")