blob: 7c37bde835b53677566e78fe66fdaf4fdd0a1300 [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:
Cyrill Gorcunovf2710ca2018-11-04 20:15:42 +030065 return False
66 if desc['description'] == "":
67 return False
Cyrill Gorcunov54533882018-04-25 00:31:21 +030068 return True
69
70#
71# Expand ref/id in descriptors array
72def expand_templates(desc_array):
73 desc_ids = { }
74 for d in desc_array:
75 if 'id' in d:
76 desc_ids[d['id']] = d
77 for i, d in enumerate(desc_array):
78 if 'ref' in d and d['ref'] in desc_ids:
79 ref = desc_ids[d['ref']]
80 own = d.copy()
81 desc_array[i] = ref.copy()
82 for k, v in own.items():
83 desc_array[i][k] = v
84 del desc_array[i]['id']
85 return desc_array
86
87def prepare_desc(desc, basedir, name, path):
88 if not is_valid_desc(desc):
89 return False
90 #
91 # Put private fields
92 desc['_base-dir'] = basedir
93 desc['_json-file'] = name
94 desc['_json-path'] = path
95 desc['_test-name'] = basedir + os.sep + name[:-5]
96 #
97 # If no target provided never update
98 if 'target' not in desc:
99 desc['target'] = []
100 desc['update'] = 'false'
101 #
102 # Which code to expect when nasm finishes
103 desc['_wait'] = 0
104 if 'error' in desc:
105 if desc['error'] == 'expected':
106 desc['_wait'] = 1
107 #
108 # Walk over targets and generate match templates
109 # if were not provided yet
110 for d in desc['target']:
111 if 'output' in d and not 'match' in d:
112 d['match'] = d['output'] + ".t"
113 return True
114
115def read_json(path):
116 desc = None
117 try:
118 with open(path, "rb") as f:
119 try:
120 desc = json.loads(f.read().decode("utf-8").strip("\n"))
121 except:
122 desc = None
123 finally:
124 f.close()
125 except:
126 pass
127 return desc
128
129def read_desc(basedir, name):
130 path = basedir + os.sep + name
131 desc = read_json(path)
132 desc_array = []
133 if type(desc) == dict:
134 if prepare_desc(desc, basedir, name, path) == True:
135 desc_array += [desc]
136 elif type(desc) == list:
137 expand_templates(desc)
138 for de in desc:
139 if prepare_desc(de, basedir, name, path) == True:
140 desc_array += [de]
141 return desc_array
142
143def collect_test_desc_from_file(path):
144 if not fnmatch.fnmatch(path, '*.json'):
145 path += '.json'
146 basedir = os.path.dirname(path)
147 filename = os.path.basename(path)
148 return read_desc(basedir, filename)
149
150def collect_test_desc_from_dir(basedir):
151 desc_array = []
152 if os.path.isdir(basedir):
153 for filename in os.listdir(basedir):
154 if os.path.isdir(basedir + os.sep + filename):
155 desc_array += collect_test_desc_from_dir(basedir + os.sep + filename)
156 elif fnmatch.fnmatch(filename, '*.json'):
157 desc = read_desc(basedir, filename)
158 if desc == None:
159 continue
160 desc_array += desc
Cyrill Gorcunov502556c2018-11-03 18:05:53 +0300161 desc_array.sort(key=lambda x: x['_test-name'])
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300162 return desc_array
163
164if args.cmd == 'list':
165 fmt_entry = '%-32s %s'
166 desc_array = collect_test_desc_from_dir(args.dir)
167 print(fmt_entry % ('Name', 'Description'))
168 for desc in desc_array:
169 print(fmt_entry % (desc['_test-name'], desc['description']))
170
171def test_abort(test, message):
172 print("\t%s: %s" % (test, message))
173 print("=== Test %s ABORT ===" % (test))
174 sys.exit(1)
175 return False
176
177def test_fail(test, message):
178 print("\t%s: %s" % (test, message))
179 print("=== Test %s FAIL ===" % (test))
180 return False
181
182def test_skip(test, message):
183 print("\t%s: %s" % (test, message))
184 print("=== Test %s SKIP ===" % (test))
185 return True
186
187def test_over(test):
188 print("=== Test %s ERROR OVER ===" % (test))
189 return True
190
191def test_pass(test):
192 print("=== Test %s PASS ===" % (test))
193 return True
194
195def test_updated(test):
196 print("=== Test %s UPDATED ===" % (test))
197 return True
198
199def run_hexdump(path):
200 p = subprocess.Popen([args.hexdump, "-C", path],
201 stdout = subprocess.PIPE,
202 close_fds = True)
203 if p.wait() == 0:
204 return p
205 return None
206
207def show_std(stdname, data):
208 print("\t--- %s" % (stdname))
209 for i in data.split("\n"):
210 print("\t%s" % i)
211 print("\t---")
212
213def cmp_std(test, data_name, data, match):
214 match_data = read_stdfile(match)
215 if match_data == None:
216 return test_fail(test, "Can't read " + match)
217 if data != match_data:
218 print("\t--- %s" % (data_name))
219 for i in data.split("\n"):
220 print("\t%s" % i)
221 print("\t--- %s" % (match))
222 for i in match_data.split("\n"):
223 print("\t%s" % i)
224
225 diff = difflib.unified_diff(data.split("\n"), match_data.split("\n"),
226 fromfile = data_name, tofile = match)
227 for i in diff:
228 print("\t%s" % i.strip("\n"))
229 print("\t---")
230 return False
231 return True
232
233def show_diff(test, patha, pathb):
234 pa = run_hexdump(patha)
235 pb = run_hexdump(pathb)
236 if pa == None or pb == None:
237 return test_fail(test, "Can't create dumps")
238 sa = pa.stdout.read().decode("utf-8").strip("\n")
239 sb = pb.stdout.read().decode("utf-8").strip("\n")
240 print("\t--- hexdump %s" % (patha))
241 for i in sa.split("\n"):
242 print("\t%s" % i)
243 print("\t--- hexdump %s" % (pathb))
244 for i in sb.split("\n"):
245 print("\t%s" % i)
246 pa.stdout.close()
247 pb.stdout.close()
248
249 diff = difflib.unified_diff(sa.split("\n"), sb.split("\n"),
250 fromfile = patha, tofile = pathb)
251 for i in diff:
252 print("\t%s" % i.strip("\n"))
253 print("\t---")
254 return True
255
256def prepare_run_opts(desc):
257 opts = []
258
259 if 'format' in desc:
260 opts += ['-f', desc['format']]
261 if 'option' in desc:
262 opts += desc['option'].split(" ")
263 for t in desc['target']:
264 if 'output' in t:
265 if 'option' in t:
266 opts += t['option'].split(" ") + [desc['_base-dir'] + os.sep + t['output']]
267 else:
268 opts += ['-o', desc['_base-dir'] + os.sep + t['output']]
269 if 'stdout' in t or 'stderr' in t:
270 if 'option' in t:
271 opts += t['option'].split(" ")
272 if 'source' in desc:
273 opts += [desc['_base-dir'] + os.sep + desc['source']]
274 return opts
275
276def exec_nasm(desc):
277 print("\tProcessing %s" % (desc['_test-name']))
278 opts = [args.nasm] + prepare_run_opts(desc)
279
Cyrill Gorcunov85261a22018-11-24 17:06:57 +0300280 nasm_env = os.environ.copy()
281 nasm_env['NASM_TEST_RUN'] = 'y'
282
Cyrill Gorcunov0f26c1e2018-11-11 17:58:49 +0300283 desc_env = desc.get('environ')
284 if desc_env:
Cyrill Gorcunov0f26c1e2018-11-11 17:58:49 +0300285 for i in desc_env:
286 v = i.split('=')
287 if len(v) == 2:
288 nasm_env[v[0]] = v[1]
289 else:
290 nasm_env[v[0]] = None
Cyrill Gorcunov0f26c1e2018-11-11 17:58:49 +0300291
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300292 print("\tExecuting %s" % (" ".join(opts)))
293 pnasm = subprocess.Popen(opts,
294 stdout = subprocess.PIPE,
295 stderr = subprocess.PIPE,
Cyrill Gorcunov0f26c1e2018-11-11 17:58:49 +0300296 close_fds = True,
297 env = nasm_env)
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300298 if pnasm == None:
299 test_fail(desc['_test-name'], "Unable to execute test")
300 return None
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300301
Cyrill Gorcunov7bb272d2018-11-04 16:12:42 +0300302 stderr = pnasm.stderr.read(1048576).decode("utf-8").strip("\n")
303 stdout = pnasm.stdout.read(1048576).decode("utf-8").strip("\n")
304
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300305 pnasm.stdout.close()
306 pnasm.stderr.close()
307
Cyrill Gorcunov7bb272d2018-11-04 16:12:42 +0300308 wait_rc = pnasm.wait();
309
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300310 if desc['_wait'] != wait_rc:
311 if stdout != "":
312 show_std("stdout", stdout)
313 if stderr != "":
314 show_std("stderr", stderr)
315 test_fail(desc['_test-name'],
316 "Unexpected ret code: " + str(wait_rc))
317 return None, None, None
318 return pnasm, stdout, stderr
319
320def test_run(desc):
321 print("=== Running %s ===" % (desc['_test-name']))
322
323 pnasm, stdout, stderr = exec_nasm(desc)
324 if pnasm == None:
325 return False
326
327 for t in desc['target']:
328 if 'output' in t:
329 output = desc['_base-dir'] + os.sep + t['output']
330 match = desc['_base-dir'] + os.sep + t['match']
331 if desc['_wait'] == 1:
332 continue
333 print("\tComparing %s %s" % (output, match))
334 if filecmp.cmp(match, output) == False:
335 show_diff(desc['_test-name'], match, output)
336 return test_fail(desc['_test-name'], match + " and " + output + " files are different")
337 elif 'stdout' in t:
338 print("\tComparing stdout")
339 match = desc['_base-dir'] + os.sep + t['stdout']
340 if cmp_std(desc['_test-name'], 'stdout', stdout, match) == False:
341 return test_fail(desc['_test-name'], "Stdout mismatch")
342 else:
343 stdout = ""
344 elif 'stderr' in t:
345 print("\tComparing stderr")
346 match = desc['_base-dir'] + os.sep + t['stderr']
347 if cmp_std(desc['_test-name'], 'stderr', stderr, match) == False:
348 return test_fail(desc['_test-name'], "Stderr mismatch")
349 else:
350 stderr = ""
351
352 if stdout != "":
353 show_std("stdout", stdout)
354 return test_fail(desc['_test-name'], "Stdout is not empty")
355
356 if stderr != "":
357 show_std("stderr", stderr)
358 return test_fail(desc['_test-name'], "Stderr is not empty")
359
360 return test_pass(desc['_test-name'])
361
362#
363# Compile sources and generate new targets
364def test_update(desc):
365 print("=== Updating %s ===" % (desc['_test-name']))
366
367 if 'update' in desc and desc['update'] == 'false':
368 return test_skip(desc['_test-name'], "No output provided")
369
Cyrill Gorcunovdcf39372018-10-17 23:36:43 +0300370 pnasm, stdout, stderr = exec_nasm(desc)
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300371 if pnasm == None:
372 return False
373
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300374 for t in desc['target']:
375 if 'output' in t:
376 output = desc['_base-dir'] + os.sep + t['output']
377 match = desc['_base-dir'] + os.sep + t['match']
378 print("\tMoving %s to %s" % (output, match))
379 os.rename(output, match)
380 if 'stdout' in t:
381 match = desc['_base-dir'] + os.sep + t['stdout']
382 print("\tMoving %s to %s" % ('stdout', match))
383 with open(match, "wb") as f:
Cyrill Gorcunov750bc502018-11-03 14:50:09 +0300384 f.write(stdout.encode("utf-8"))
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300385 f.close()
386 if 'stderr' in t:
387 match = desc['_base-dir'] + os.sep + t['stderr']
388 print("\tMoving %s to %s" % ('stderr', match))
389 with open(match, "wb") as f:
Cyrill Gorcunov750bc502018-11-03 14:50:09 +0300390 f.write(stderr.encode("utf-8"))
Cyrill Gorcunov54533882018-04-25 00:31:21 +0300391 f.close()
392
393 return test_updated(desc['_test-name'])
394
395if args.cmd == 'run':
396 desc_array = []
397 if args.test == None:
398 desc_array = collect_test_desc_from_dir(args.dir)
399 else:
400 desc_array = collect_test_desc_from_file(args.test)
401 if len(desc_array) == 0:
402 test_abort(args.test, "Can't obtain test descriptors")
403
404 for desc in desc_array:
405 if test_run(desc) == False:
406 if 'error' in desc and desc['error'] == 'over':
407 test_over(desc['_test-name'])
408 else:
409 test_abort(desc['_test-name'], "Error detected")
410
411if args.cmd == 'update':
412 desc_array = []
413 if args.test == None:
414 desc_array = collect_test_desc_from_dir(args.dir)
415 else:
416 desc_array = collect_test_desc_from_file(args.test)
417 if len(desc_array) == 0:
418 test_abort(args.test, "Can't obtain a test descriptors")
419
420 for desc in desc_array:
421 if test_update(desc) == False:
422 if 'error' in desc and desc['error'] == 'over':
423 test_over(desc['_test-name'])
424 else:
425 test_abort(desc['_test-name'], "Error detected")