Merge branch 'master' of https://github.com/kanaka/mal
[jackhill/mal.git] / runtest.py
1 #!/usr/bin/env python
2
3 import os, sys, re
4 import argparse, time
5
6 import pty
7 from subprocess import Popen, STDOUT, PIPE
8 from select import select
9
10 # TODO: do we need to support '\n' too
11 sep = "\r\n"
12 #sep = "\n"
13 rundir = None
14
15 parser = argparse.ArgumentParser(
16 description="Run a test file against a Mal implementation")
17 parser.add_argument('--rundir',
18 help="change to the directory before running tests")
19 parser.add_argument('--start-timeout', default=10, type=int,
20 help="default timeout for initial prompt")
21 parser.add_argument('--test-timeout', default=20, type=int,
22 help="default timeout for each individual test action")
23 parser.add_argument('--pre-eval', default=None, type=str,
24 help="Mal code to evaluate prior to running the test")
25 parser.add_argument('--redirect', action='store_true',
26 help="Run implementation in bash and redirect output to /dev/null")
27
28 parser.add_argument('test_file', type=argparse.FileType('r'),
29 help="a test file formatted as with mal test data")
30 parser.add_argument('mal_cmd', nargs="*",
31 help="Mal implementation command line. Use '--' to "
32 "specify a Mal command line with dashed options.")
33
34 class Runner():
35 def __init__(self, args, redirect=False):
36 print "args: %s" % repr(args)
37 if redirect:
38 print "using redirect"
39 self.p = Popen(args, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
40 self.stdin = self.p.stdin
41 self.stdout = self.p.stdout
42 else:
43 # provide tty to get 'interactive' readline to work
44 master, slave = pty.openpty()
45 self.p = Popen(args, bufsize=0, stdin=slave, stdout=slave, stderr=STDOUT)
46 self.stdin = os.fdopen(master, 'r+b', 0)
47 self.stdout = self.stdin
48
49 #print "started"
50 self.buf = ""
51 self.last_prompt = ""
52
53 def read_to_prompt(self, prompts, timeout):
54 end_time = time.time() + timeout
55 while time.time() < end_time:
56 [outs,_,_] = select([self.stdin], [], [], 1)
57 if self.stdin in outs:
58 new_data = self.stdin.read(1)
59 #print "new_data: '%s'" % new_data
60 self.buf += new_data
61 for prompt in prompts:
62 regexp = re.compile(prompt)
63 match = regexp.search(self.buf)
64 if match:
65 end = match.end()
66 buf = self.buf[0:end-len(prompt)]
67 self.buf = self.buf[end:]
68 self.last_prompt = prompt
69 return buf
70 return None
71
72 def write(self, str):
73 self.stdout.write(str)
74
75 def cleanup(self):
76 if self.p:
77 self.p.terminate()
78 self.p = None
79
80
81 args = parser.parse_args(sys.argv[1:])
82 test_data = args.test_file.read().split('\n')
83
84 if args.rundir: os.chdir(args.rundir)
85
86 r = Runner(args.mal_cmd, redirect=args.redirect)
87
88
89 test_idx = 0
90 def read_test(data):
91 global test_idx
92 form, output, ret = None, "", None
93 while data:
94 test_idx += 1
95 line = data.pop(0)
96 if re.match(r"^\s*$", line): # blank line
97 continue
98 elif line[0:3] == ";;;": # ignore comment
99 continue
100 elif line[0:2] == ";;": # output comment
101 print line[3:]
102 continue
103 elif line[0:2] == ";": # unexpected comment
104 print "Test data error at line %d:\n%s" % (test_idx, line)
105 return None, None, None, test_idx
106 form = line # the line is a form to send
107
108 # Now find the output and return value
109 while data:
110 line = data[0]
111 if line[0:3] == ";=>":
112 ret = line[3:].replace('\\r', '\r').replace('\\n', '\n')
113 test_idx += 1
114 data.pop(0)
115 break
116 elif line[0:2] == "; ":
117 output = output + line[2:] + sep
118 test_idx += 1
119 data.pop(0)
120 else:
121 ret = "*"
122 break
123 if ret: break
124
125 return form, output, ret, test_idx
126
127 def assert_prompt(timeout):
128 # Wait for the initial prompt
129 header = r.read_to_prompt(['user> ', 'mal-user> '], timeout=timeout)
130 if not header == None:
131 if header:
132 print "Started with:\n%s" % header
133 else:
134 print "Did not get 'user> ' or 'mal-user> ' prompt"
135 print " Got : %s" % repr(r.buf)
136 r.cleanup()
137 sys.exit(1)
138
139
140 # Wait for the initial prompt
141 assert_prompt(args.start_timeout)
142
143 # Send the pre-eval code if any
144 if args.pre_eval:
145 sys.stdout.write("RUNNING pre-eval: %s" % args.pre_eval)
146 p.write(args.pre_eval)
147 assert_prompt(args.test_timeout)
148
149 fail_cnt = 0
150
151 while test_data:
152 form, out, ret, line_num = read_test(test_data)
153 if form == None:
154 break
155 sys.stdout.write("TEST: %s -> [%s,%s]" % (form, repr(out), repr(ret)))
156 sys.stdout.flush()
157 expected = "%s%s%s%s" % (form, sep, out, ret)
158
159 r.write(form + "\n")
160 try:
161 res = r.read_to_prompt(['\r\nuser> ', '\nuser> ',
162 '\r\nmal-user> ', '\nmal-user> '],
163 timeout=args.test_timeout)
164 #print "%s,%s,%s" % (idx, repr(p.before), repr(p.after))
165 if ret == "*" or res == expected:
166 print " -> SUCCESS"
167 else:
168 print " -> FAIL (line %d):" % line_num
169 print " Expected : %s" % repr(expected)
170 print " Got : %s" % repr(res)
171 fail_cnt += 1
172 except:
173 print "Got Exception"
174 r.cleanup()
175 sys.exit(1)
176 r.cleanup()
177
178 if fail_cnt > 0:
179 print "FAILURES: %d" % fail_cnt
180 sys.exit(2)
181 sys.exit(0)