]> git.cworth.org Git - apitrace-tests/blob - checker.py
Improve gl map buffer test.
[apitrace-tests] / checker.py
1 #!/usr/bin/env python
2 ##########################################################################
3 #
4 # Copyright 2008-2012 Jose Fonseca
5 # All Rights Reserved.
6 #
7 # Permission is hereby granted, free of charge, to any person obtaining a copy
8 # of this software and associated documentation files (the "Software"), to deal
9 # in the Software without restriction, including without limitation the rights
10 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 # copies of the Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # THE SOFTWARE.
24 #
25 ##########################################################################/
26
27
28 import sys
29 import optparse
30 import os
31 import re
32 import subprocess
33
34
35 class MatchObject:
36
37     def __init__(self):
38         self.params = {}
39
40
41 class Matcher:
42
43     def match(self, value, mo):
44         raise NotImplementedError
45
46     def _matchSequence(self, refValues, srcValues, mo):
47         if not isinstance(srcValues, (list, tuple)):
48             return False
49
50         if len(refValues) != len(srcValues):
51             return False
52
53         for refValue, srcValue in zip(refValues, srcValues):
54             if not refValue.match(srcValue, mo):
55                 return False
56         return True
57
58     def __str__(self):
59         raise NotImplementerError
60
61     def __repr__(self):
62         return str(self)
63
64
65 class WildcardMatcher(Matcher):
66
67     def __init__(self, name = ''):
68         self.name = name
69
70     def match(self, value, mo):
71         if self.name:
72             try:
73                 refValue = mo.params[self.name]
74             except KeyError:
75                 mo.params[self.name] = value
76             else:
77                 return refValue == value
78         return True
79
80     def __str__(self):
81         return '<' + self.name + '>'
82
83
84 class LiteralMatcher(Matcher):
85
86     def __init__(self, refValue):
87         self.refValue = refValue
88
89     def match(self, value, mo):
90         return self.refValue == value
91
92     def __str__(self):
93         return repr(self.refValue)
94
95
96 class ApproxMatcher(Matcher):
97
98     def __init__(self, refValue, tolerance = 2**-23):
99         self.refValue = refValue
100         self.tolerance = tolerance
101
102     def match(self, value, mo):
103         if not isinstance(value, float):
104             return 
105
106         error = abs(self.refValue - value)
107         if self.refValue:
108             error = error / self.refValue
109         return error <= self.tolerance
110
111     def __str__(self):
112         return repr(self.refValue)
113
114
115 class BitmaskMatcher(Matcher):
116
117     def __init__(self, refElements):
118         self.refElements = refElements
119
120     def match(self, value, mo):
121         return self._matchSequence(self.refElements, value, mo)
122
123     def __str__(self):
124         return ' | '.join(map(str, self.refElements))
125
126
127 class OffsetMatcher(Matcher):
128
129     def __init__(self, refValue, offset):
130         self.refValue = refValue
131         self.offset = offset
132
133     def match(self, value, mo):
134         return self.refValue.match(value - self.offset, mo)
135
136     def __str__(self):
137         return '%s + %i' % (self.refValue, self.offset)
138
139
140 class ArrayMatcher(Matcher):
141
142     def __init__(self, refElements):
143         self.refElements = refElements
144
145     def match(self, value, mo):
146         return self._matchSequence(self.refElements, value, mo)
147
148     def __str__(self):
149         return '{' + ', '.join(map(str, self.refElements)) + '}'
150
151
152 class StructMatcher(Matcher):
153
154     def __init__(self, refMembers):
155         self.refMembers = refMembers
156
157     def match(self, value, mo):
158         if not isinstance(value, dict):
159             return False
160
161         if len(value) != len(self.refMembers):
162             return False
163
164         for name, refMember in self.refMembers.iteritems():
165             try:
166                 member = value[name]
167             except KeyError:
168                 return False
169             else:
170                 if not refMember.match(member, mo):
171                     return False
172
173         return True
174
175     def __str__(self):
176         return '{' + ', '.join(['%s = %s' % refMember for refMember in self.refMembers.iteritems()]) + '}'
177
178
179 class CallMatcher(Matcher):
180
181     def __init__(self, callNo, functionName, args, ret):
182         self.callNo = callNo
183         self.functionName = functionName
184         self.args = args
185         self.ret = ret
186
187     def match(self, call, mo):
188         callNo, srcFunctionName, srcArgs, srcRet = call
189
190         if self.functionName != srcFunctionName:
191             return False
192
193         refArgs = [value for name, value in self.args]
194         srcArgs = [value for name, value in srcArgs]
195
196         if not self._matchSequence(refArgs, srcArgs, mo):
197             return False
198
199         if self.ret is None:
200             if srcRet is not None:
201                 return False
202         else:
203             if not self.ret.match(srcRet, mo):
204                 return False
205
206         if self.callNo is not None:
207             if not self.callNo.match(callNo, mo):
208                 return False
209
210         return True
211
212     def __str__(self):
213         s = self.functionName
214         s += '(' + ', '.join(['%s = %s' % refArg for refArg in self.args]) + ')'
215         if self.ret is not None:
216             s += ' = ' + str(self.ret)
217         return s
218
219
220 class TraceMismatch(Exception):
221
222     pass
223
224
225 class TraceMatcher:
226
227     def __init__(self, calls):
228         self.calls = calls
229
230     def match(self, calls, verbose = False):
231         mo = MatchObject()
232         srcCalls = iter(calls)
233         for refCall in self.calls:
234             if verbose:
235                 print refCall
236             skippedSrcCalls = []
237             while True:
238                 try:
239                     srcCall = srcCalls.next()
240                 except StopIteration:
241                     if skippedSrcCalls:
242                         raise TraceMismatch('missing call `%s` (found `%s`)' % (refCall, skippedSrcCalls[0]))
243                     else:
244                         raise TraceMismatch('missing call %s' % refCall)
245                 if verbose:
246                     print '\t%s %s%r = %r' % srcCall
247                 if refCall.match(srcCall, mo):
248                     break
249                 else:
250                     skippedSrcCalls.append(srcCall)
251         return mo
252
253     def __str__(self):
254         return ''.join(['%s\n' % call for call in self.calls])
255
256
257 #######################################################################
258
259 EOF = -1
260 SKIP = -2
261
262
263 class ParseError(Exception):
264
265     def __init__(self, msg=None, filename=None, line=None, col=None):
266         self.msg = msg
267         self.filename = filename
268         self.line = line
269         self.col = col
270
271     def __str__(self):
272         return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None])
273         
274
275 class Scanner:
276     """Stateless scanner."""
277
278     # should be overriden by derived classes
279     tokens = []
280     symbols = {}
281     literals = {}
282     ignorecase = False
283
284     def __init__(self):
285         flags = re.DOTALL
286         if self.ignorecase:
287             flags |= re.IGNORECASE
288         self.tokens_re = re.compile(
289             '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]),
290              flags
291         )
292
293     def next(self, buf, pos):
294         if pos >= len(buf):
295             return EOF, '', pos
296         mo = self.tokens_re.match(buf, pos)
297         if mo:
298             text = mo.group()
299             type, regexp, test_lit = self.tokens[mo.lastindex - 1]
300             pos = mo.end()
301             if test_lit:
302                 type = self.literals.get(text, type)
303             return type, text, pos
304         else:
305             c = buf[pos]
306             return self.symbols.get(c, None), c, pos + 1
307
308
309 class Token:
310
311     def __init__(self, type, text, line, col):
312         self.type = type
313         self.text = text
314         self.line = line
315         self.col = col
316
317
318 class Lexer:
319
320     # should be overriden by derived classes
321     scanner = None
322     tabsize = 8
323
324     newline_re = re.compile(r'\r\n?|\n')
325
326     def __init__(self, buf = None, pos = 0, filename = None, fp = None):
327         if fp is not None:
328             try:
329                 fileno = fp.fileno()
330                 length = os.path.getsize(fp.name)
331                 import mmap
332             except:
333                 # read whole file into memory
334                 buf = fp.read()
335                 pos = 0
336             else:
337                 # map the whole file into memory
338                 if length:
339                     # length must not be zero
340                     buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ)
341                     pos = os.lseek(fileno, 0, 1)
342                 else:
343                     buf = ''
344                     pos = 0
345
346             if filename is None:
347                 try:
348                     filename = fp.name
349                 except AttributeError:
350                     filename = None
351
352         self.buf = buf
353         self.pos = pos
354         self.line = 1
355         self.col = 1
356         self.filename = filename
357
358     def next(self):
359         while True:
360             # save state
361             pos = self.pos
362             line = self.line
363             col = self.col
364
365             type, text, endpos = self.scanner.next(self.buf, pos)
366             assert pos + len(text) == endpos
367             self.consume(text)
368             type, text = self.filter(type, text)
369             self.pos = endpos
370
371             if type == SKIP:
372                 continue
373             elif type is None:
374                 msg = 'unexpected char '
375                 if text >= ' ' and text <= '~':
376                     msg += "'%s'" % text
377                 else:
378                     msg += "0x%X" % ord(text)
379                 raise ParseError(msg, self.filename, line, col)
380             else:
381                 break
382         return Token(type = type, text = text, line = line, col = col)
383
384     def consume(self, text):
385         # update line number
386         pos = 0
387         for mo in self.newline_re.finditer(text, pos):
388             self.line += 1
389             self.col = 1
390             pos = mo.end()
391
392         # update column number
393         while True:
394             tabpos = text.find('\t', pos)
395             if tabpos == -1:
396                 break
397             self.col += tabpos - pos
398             self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1
399             pos = tabpos + 1
400         self.col += len(text) - pos
401
402
403 class Parser:
404
405     def __init__(self, lexer):
406         self.lexer = lexer
407         self.lookahead = self.lexer.next()
408
409     def match(self, type):
410         return self.lookahead.type == type
411
412     def skip(self, type):
413         while not self.match(type):
414             self.consume()
415
416     def error(self):
417         raise ParseError(
418             msg = 'unexpected token %r' % self.lookahead.text, 
419             filename = self.lexer.filename, 
420             line = self.lookahead.line, 
421             col = self.lookahead.col)
422
423     def consume(self, type = None):
424         if type is not None and not self.match(type):
425             self.error()
426         token = self.lookahead
427         self.lookahead = self.lexer.next()
428         return token
429
430
431 #######################################################################
432
433 ID, NUMBER, HEXNUM, STRING, WILDCARD, PRAGMA, LPAREN, RPAREN, LCURLY, RCURLY, COMMA, AMP, EQUAL, PLUS, VERT, BLOB = xrange(16)
434
435
436 class CallScanner(Scanner):
437
438     # token regular expression table
439     tokens = [
440         # whitespace
441         (SKIP, r'[ \t\f\r\n\v]+', False),
442
443         # Alphanumeric IDs
444         (ID, r'[a-zA-Z_][a-zA-Z0-9_]*(?:::[a-zA-Z_][a-zA-Z0-9_]*)?', True),
445
446         # Numeric IDs
447         (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
448         
449         # Numeric IDs
450         (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+][0-9]+)?', False),
451
452         # String IDs
453         (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
454         
455         # Wildcard
456         (WILDCARD, r'<[^>]*>', False),
457         
458         # Pragma
459         (PRAGMA, r'#[^\r\n]*', False),
460     ]
461
462     # symbol table
463     symbols = {
464         '(': LPAREN,
465         ')': RPAREN,
466         '{': LCURLY,
467         '}': RCURLY,
468         ',': COMMA,
469         '&': AMP,
470         '=': EQUAL,
471         '+': PLUS,
472         '|': VERT,
473     }
474
475     # literal table
476     literals = {
477         'blob': BLOB
478     }
479
480
481 class CallLexer(Lexer):
482
483     scanner = CallScanner()
484
485     def filter(self, type, text):
486         if type == STRING:
487             text = text[1:-1]
488
489             # line continuations
490             text = text.replace('\\\r\n', '')
491             text = text.replace('\\\r', '')
492             text = text.replace('\\\n', '')
493             
494             # quotes
495             text = text.replace('\\"', '"')
496
497             type = ID
498
499         return type, text
500
501
502 class TraceParser(Parser):
503
504     def __init__(self, stream):
505         lexer = CallLexer(fp = stream)
506         Parser.__init__(self, lexer)
507
508     def eof(self):
509         return self.match(EOF)
510
511     def parse(self):
512         while not self.eof():
513             self.parse_element()
514         return TraceMatcher(self.calls)
515
516     def parse_element(self):
517         if self.lookahead.type == PRAGMA:
518             token = self.consume()
519             self.handlePragma(token.text)
520         else:
521             self.parse_call()
522
523     def parse_call(self):
524         if self.lookahead.type == NUMBER:
525             token = self.consume()
526             callNo = self.handleInt(int(token.text))
527         elif self.lookahead.type == WILDCARD:
528             token = self.consume()
529             callNo = self.handleWildcard((token.text[1:-1]))
530         else:
531             callNo = None
532         
533         functionName = self.consume(ID).text
534
535         args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
536
537         if self.match(EQUAL):
538             self.consume(EQUAL)
539             ret = self.parse_value()
540         else:
541             ret = None
542
543         self.handleCall(callNo, functionName, args, ret)
544
545     def parse_pair(self):
546         '''Parse a `name = value` pair.'''
547         name = self.consume(ID).text
548         self.consume(EQUAL)
549         value = self.parse_value()
550         return name, value
551
552     def parse_opt_pair(self):
553         '''Parse an optional `name = value` pair.'''
554         if self.match(ID):
555             name = self.consume(ID).text
556             if self.match(EQUAL):
557                 self.consume(EQUAL)
558                 value = self.parse_value()
559             else:
560                 value = name
561                 name = None
562         else:
563             name = None
564             value = self.parse_value()
565         if name is None:
566             return value
567         else:
568             return name, value
569
570     def parse_value(self):
571         value = self._parse_value()
572         if self.match(VERT):
573             flags = [value]
574             while self.match(VERT):
575                 self.consume()
576                 value = self._parse_value()
577                 flags.append(value)
578             return self.handleBitmask(flags)
579         elif self.match(PLUS):
580             self.consume()
581             if self.match(NUMBER):
582                 token = self.consume()
583                 offset = int(token.text)
584             elif self.match(HEXNUM):
585                 token = self.consume()
586                 offset = int(token.text, 16)
587             else:
588                 assert 0
589             return self.handleOffset(value, offset)
590         else:
591             return value
592
593     def _parse_value(self):
594         if self.match(AMP):
595             self.consume()
596             value = [self.parse_value()]
597             return self.handleArray(value)
598         elif self.match(ID):
599             token = self.consume()
600             value = token.text
601             return self.handleID(value)
602         elif self.match(STRING):
603             token = self.consume()
604             value = token.text
605             return self.handleString(value)
606         elif self.match(NUMBER):
607             token = self.consume()
608             if '.' in token.text:
609                 value = float(token.text)
610                 return self.handleFloat(value)
611             else:
612                 value = int(token.text)
613                 return self.handleInt(value)
614         elif self.match(HEXNUM):
615             token = self.consume()
616             value = int(token.text, 16)
617             return self.handleInt(value)
618         elif self.match(LCURLY):
619             value = self.parse_sequence(LCURLY, RCURLY, self.parse_opt_pair)
620             if len(value) and isinstance(value[0], tuple):
621                 value = dict(value)
622                 return self.handleStruct(value)
623             else:
624                 return self.handleArray(value)
625         elif self.match(BLOB):
626             token = self.consume()
627             self.consume(LPAREN)
628             token = self.consume()
629             length = int(token.text)
630             self.consume(RPAREN)
631             return self.handleBlob(length)
632         elif self.match(WILDCARD):
633             token = self.consume()
634             return self.handleWildcard(token.text[1:-1])
635         else:
636             self.error()
637
638     def parse_sequence(self, ltype, rtype, elementParser):
639         '''Parse a comma separated list'''
640
641         elements = []
642
643         self.consume(ltype)
644         sep = None
645         while not self.match(rtype):
646             if sep is None:
647                 sep = COMMA
648             else:
649                 self.consume(sep)
650             element = elementParser()
651             elements.append(element)
652         self.consume(rtype)
653
654         return elements
655     
656     def handleID(self, value):
657         raise NotImplementedError
658
659     def handleInt(self, value):
660         raise NotImplementedError
661
662     def handleFloat(self, value):
663         raise NotImplementedError
664
665     def handleString(self, value):
666         raise NotImplementedError
667
668     def handleBitmask(self, value):
669         raise NotImplementedError
670
671     def handleOffset(self, value, offset):
672         raise NotImplementedError
673
674     def handleArray(self, value):
675         raise NotImplementedError
676
677     def handleStruct(self, value):
678         raise NotImplementedError
679
680     def handleBlob(self, length):
681         return self.handleID('blob(%u)' % length)
682
683     def handleWildcard(self, name):
684         raise NotImplementedError
685
686     def handleCall(self, callNo, functionName, args, ret):
687         raise NotImplementedError
688
689     def handlePragma(self, line):
690         raise NotImplementedError
691
692
693 class RefTraceParser(TraceParser):
694
695     def __init__(self, stream):
696         TraceParser.__init__(self, stream)
697         self.calls = []
698
699     def parse(self):
700         TraceParser.parse(self)
701         return TraceMatcher(self.calls)
702
703     def handleID(self, value):
704         return LiteralMatcher(value)
705
706     def handleInt(self, value):
707         return LiteralMatcher(value)
708
709     def handleFloat(self, value):
710         return ApproxMatcher(value)
711
712     def handleString(self, value):
713         return LiteralMatcher(value)
714
715     def handleBitmask(self, value):
716         return BitmaskMatcher(value)
717
718     def handleOffset(self, value, offset):
719         return OffsetMatcher(value, offset)
720
721     def handleArray(self, value):
722         return ArrayMatcher(value)
723
724     def handleStruct(self, value):
725         return StructMatcher(value)
726
727     def handleWildcard(self, name):
728         return WildcardMatcher(name)
729
730     def handleCall(self, callNo, functionName, args, ret):
731         call = CallMatcher(callNo, functionName, args, ret)
732         self.calls.append(call)
733     
734     def handlePragma(self, line):
735         pass
736
737
738 class SrcTraceParser(TraceParser):
739
740     def __init__(self, stream):
741         TraceParser.__init__(self, stream)
742         self.calls = []
743
744     def parse(self):
745         TraceParser.parse(self)
746         return self.calls
747
748     def handleID(self, value):
749         return value
750
751     def handleInt(self, value):
752         return int(value)
753
754     def handleFloat(self, value):
755         return float(value)
756
757     def handleString(self, value):
758         return value
759
760     def handleBitmask(self, value):
761         return value
762
763     def handleArray(self, elements):
764         return list(elements)
765
766     def handleStruct(self, members):
767         return dict(members)
768
769     def handleCall(self, callNo, functionName, args, ret):
770         call = (callNo, functionName, args, ret)
771         self.calls.append(call)
772
773
774 def main():
775     # Parse command line options
776     optparser = optparse.OptionParser(
777         usage='\n\t%prog [OPTIONS] REF_TXT SRC_TRACE',
778         version='%%prog')
779     optparser.add_option(
780         '--apitrace', metavar='PROGRAM',
781         type='string', dest='apitrace', default=os.environ.get('APITRACE', 'apitrace'),
782         help='path to apitrace executable')
783     optparser.add_option(
784         '-v', '--verbose',
785         action="store_true",
786         dest="verbose", default=True,
787         help="verbose output")
788     (options, args) = optparser.parse_args(sys.argv[1:])
789
790     if len(args) != 2:
791         optparser.error('wrong number of arguments')
792
793     refFileName, srcFileName = args
794
795     refStream = open(refFileName, 'rt')
796     refParser = RefTraceParser(refStream)
797     refTrace = refParser.parse()
798     if options.verbose:
799         sys.stdout.write('// Reference\n')
800         sys.stdout.write(str(refTrace))
801         sys.stdout.write('\n')
802
803     if srcFileName.endswith('.trace'):
804         cmd = [options.apitrace, 'dump', '--color=never', srcFileName]
805         p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
806         srcStream = p.stdout
807     else:
808         srcStream = open(srcFileName, 'rt')
809     srcParser = SrcTraceParser(srcStream)
810     srcTrace = srcParser.parse()
811     if options.verbose:
812         sys.stdout.write('// Source\n')
813         sys.stdout.write(''.join(['%s %s%r = %r\n' % call for call in srcTrace]))
814         sys.stdout.write('\n')
815
816     if options.verbose:
817         sys.stdout.write('// Matching\n')
818     mo = refTrace.match(srcTrace, options.verbose)
819     if options.verbose:
820         sys.stdout.write('\n')
821
822     if options.verbose:
823         sys.stdout.write('// Parameters\n')
824         paramNames = mo.params.keys()
825         paramNames.sort()
826         for paramName in paramNames:
827             print '%s = %r' % (paramName, mo.params[paramName])
828
829
830 if __name__ == '__main__':
831     main()