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