Merge branch 'master' into gfxpoly
[swftools.git] / lib / as3 / test
1 #!/usr/bin/python
2 #
3 # test.py
4 #
5 # Run compiler unit tests
6 #
7 # Copyright (c) 2008/2009 Matthias Kramm <kramm@quiss.org>
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
22
23 import sys
24 import os
25 import time
26 import subprocess
27 import marshal
28 import select
29 from optparse import OptionParser
30
31 CMD_ARGS=[]
32 CMD = "./parser"
33 #CMD="as3compile"
34 CMD_ARGS=["-o","abc.swf"]
35
36 def check(s):
37     row = None
38     ok = 0
39     for line in s.split("\n"):
40         if line.startswith("[") and line.endswith("]"):
41             continue
42         if not line.strip():
43             continue
44         if not line.startswith("ok"):
45             return 0
46         if line.startswith("ok "):
47             if "/" not in line:
48                 return 0
49             i = line.index('/')
50             try:
51                 nr,l = int(line[3:i]),int(line[i+1:])
52             except ValueError:
53                 return 0
54             if nr<1 or nr>l:
55                 return 0
56             if not row:
57                 row = [0]*l
58             elif l != len(row):
59                 return 0
60             if row[nr-1]:
61                 return 0
62             row[nr-1] = 1
63         elif line == "ok":
64             ok = 1
65     if ok:
66         return not row
67     if row:
68         return 0 not in row
69     return 0
70
71 def runcmd(cmd,args,wait):
72     #fo = os.tmpfile()
73     fi,fo = os.pipe()
74     fo = os.fdopen(fo, "wb")
75     p = subprocess.Popen([cmd] + args, executable=cmd, stdout=fo, stderr=fo)
76     ret = -1
77     output = ""
78     for i in range(wait*10):
79         if fi in select.select([fi],[],[], 0.01)[0]:
80             output += os.read(fi, 8192)
81             if "[exit]" in output:
82                 break
83             if "==    by" in output:
84                 ret = -33
85                 break
86         ret = p.poll()
87         if ret is not None:
88             if cmd == "valgrind":
89                 # valgrind never returns true
90                 ret = 0
91             break
92         time.sleep(0.1)
93     else:
94         os.kill(p.pid, 9)
95         os.system("killall -9 %s >/dev/null 2>/dev/null" % cmd)
96         ret = -1
97     fo.close()
98    
99     if fi in select.select([fi],[],[], 0.01)[0]:
100         output += os.read(fi, 8192)
101
102     os.close(fi)
103     return ret,output
104
105 class Cache:
106     def __init__(self, filename):
107         self.filename = filename
108         self.filename_milestone = filename+"_milestone"
109         try:
110             self.filename2status = marshal.load(open(self.filename, "rb"))
111         except IOError:
112             self.filename2status = {}
113         try:
114             self.milestone = marshal.load(open(self.filename_milestone, "rb"))
115         except IOError:
116             self.milestone = {}
117
118     def parse_args(self):
119         parser = OptionParser()
120         parser.add_option("-d", "--diff", dest="diff", help="Only run tests that failed the last time",action="store_true")
121         parser.add_option("-a", "--all", dest="all", help="Run all tests (also tests expected to fail)",action="store_true")
122         parser.add_option("-t", "--tag", dest="tag", help="Mark the current pass/fail statistic as milestone",action="store_true")
123         parser.add_option("-m", "--valgrind", dest="valgrind", help="Run compiler through valgrind",action="store_true")
124         (options, args) = parser.parse_args()
125
126         if args and args[0]=="add":
127             self.all = 1
128             self.tag = 1
129             self.milestone[args[1]] = "ok"
130             self.filename2status = self.milestone
131             self.save()
132             sys.exit(0)
133
134         self.__dict__.update(options.__dict__)
135         self.runtime = 1
136         if self.tag: 
137             self.all = 1
138             self.runtime = 3 # allow more time if we're tagging this state
139         
140         if self.valgrind:
141             global CMD,CMD_ARGS
142             CMD_ARGS = [CMD] + CMD_ARGS
143             CMD = "valgrind"
144             self.runtime = 20 # allow even more time for valgrind
145
146         self.checknum=-1
147         self.checkfile=None
148         if len(args):
149             try:
150                 self.checknum = int(args[0])
151             except ValueError:
152                 self.checkfile = args[0]
153
154     @staticmethod
155     def load(filename):
156         return Cache(filename)
157
158     def save(self):
159         fi = open(self.filename, "wb")
160         marshal.dump(self.filename2status, fi)
161         fi.close()
162         if self.tag:
163             assert(self.all)
164             fi = open(self.filename_milestone, "wb")
165             marshal.dump(self.filename2status, fi)
166             fi.close()
167
168     def highlight(self, nr, filename):
169         if self.checkfile and filename==self.checkfile:
170             return 1
171         return self.checknum==nr
172
173     def skip_file(self, nr, filename):
174         if self.checknum>=0 and nr!=self.checknum:
175             return 1
176         if self.checkfile and filename!=self.checkfile:
177             return 1
178         if not self.all and self.milestone.get(filename,"new")!="ok":
179             return 1
180         if self.diff and self.filename2status(filename,"new")=="ok":
181             return 1
182         return 0
183
184     def file_status(self, filename, status):
185         self.filename2status[filename] = status
186
187 class TestBase:
188     def __init__(self, cache, nr, file, run):
189         self.cache = cache
190         self.nr = nr
191         self.dorun = run
192         self.file = file
193         self.flash_output = None
194         self.flash_error = None
195         self.compile_output = None
196         self.compile_error = None
197
198     def compile(self):
199         try: os.unlink("abc.swf");
200         except: pass
201         ret,output = runcmd(CMD,CMD_ARGS+[self.file],wait=cache.runtime)
202         self.compile_error = 0
203         self.compile_output = output
204         self.exit_status = 0
205         if ret:
206             self.compile_output += "\nExit status %d" % (-ret)
207             self.exit_status = -ret
208             self.compile_error = 1
209             return 0
210         if not os.path.isfile("abc.swf"):
211             self.compile_error = 1
212             return 0
213         return 1
214
215     def run(self):
216         ret,output = runcmd("flashplayer",[os.path.join(os.getcwd(),"abc.swf")],wait=cache.runtime)
217         os.system("killall flashplayer")
218         self.flash_output = output
219         
220         if not check(self.flash_output):
221             self.flash_error = 1
222             return 0
223         return 1
224
225     def doprint(self):
226         print self.r(str(self.nr),3)," ",
227         if self.compile_error:
228             if self.dorun:
229                 if self.exit_status == 11:
230                     print "crash"," - ",
231                 else:
232                     print "err  "," - ",
233             else:
234                 print "err  ","   ",
235         else:
236             print "ok   ",
237             if self.dorun:
238                 if not self.flash_error:
239                     print "ok ",
240                 else:
241                     print "err",
242             else:
243                 print "   ",
244         print " ",
245         print self.file
246
247     def doprintlong(self):
248         print self.nr, self.file
249         print "================================"
250         print "compile:", (self.compile_error and "error" or "ok")
251         print self.compile_output
252         if not self.dorun:
253             return
254         print "================================"
255         print "run:", (self.flash_error and "error" or "ok")
256         print self.flash_output
257         print "================================"
258
259     def r(self,s,l):
260         if(len(s)>=l):
261             return s
262         return (" "*(l-len(s))) + s
263     def l(self,s,l):
264         if(len(s)>=l):
265             return s
266         return s + (" "*(l-len(s)))
267
268 class Test(TestBase):
269     def __init__(self, cache, nr, file):
270         TestBase.__init__(self, cache, nr, file, run=1)
271         if self.compile() and self.run():
272             cache.file_status(file, "ok")
273         else:
274             cache.file_status(file, "error")
275
276 class ErrTest(TestBase):
277     def __init__(self, cache, nr, file):
278         TestBase.__init__(self, cache, nr, file, run=0)
279         if self.compile():
280             cache.file_status(file, "error")
281             self.compile_error = True
282         else:
283             cache.file_status(file, "ok")
284             self.compile_error = False
285
286 class Suite:
287     def __init__(self, cache, dir):
288         self.dir = dir
289         self.cache = cache
290         self.errtest = "err" in dir
291     def run(self, nr):
292         print "-"*40,"tests \""+self.dir+"\"","-"*40
293         for file in sorted(os.listdir(self.dir)):
294             if not file.endswith(".as"):
295                 continue
296             nr = nr + 1
297             file = os.path.join(self.dir, file)
298
299             if cache.skip_file(nr, file):
300                 continue
301
302             if self.errtest:
303                 test = ErrTest(cache, nr, file)
304             else:
305                 test = Test(cache, nr, file)
306
307             if not cache.highlight(nr, file):
308                 test.doprint()
309             else:
310                 test.doprintlong()
311         return nr
312
313 cache = Cache.load(".tests.cache")
314 cache.parse_args()
315
316 nr = 0
317 nr = Suite(cache, "err").run(nr)
318 nr = Suite(cache, "ok").run(nr)
319
320 cache.save()