2 ##########################################################################
4 # Copyright 2008-2012 Jose Fonseca
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:
14 # The above copyright notice and this permission notice shall be included in
15 # all copies or substantial portions of the Software.
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
25 ##########################################################################/
43 def match(self, value, mo):
44 raise NotImplementedError
46 def _matchSequence(self, refValues, srcValues, mo):
47 if not isinstance(srcValues, (list, tuple)):
50 if len(refValues) != len(srcValues):
53 for refValue, srcValue in zip(refValues, srcValues):
54 if not refValue.match(srcValue, mo):
59 raise NotImplementerError
65 class WildcardMatcher(Matcher):
67 def __init__(self, name = ''):
70 def match(self, value, mo):
73 refValue = mo.params[self.name]
75 mo.params[self.name] = value
77 return refValue == value
81 return '<' + self.name + '>'
84 class LiteralMatcher(Matcher):
86 def __init__(self, refValue):
87 self.refValue = refValue
89 def match(self, value, mo):
90 return self.refValue == value
93 return repr(self.refValue)
96 class ApproxMatcher(Matcher):
98 def __init__(self, refValue, tolerance = 2**-23):
99 self.refValue = refValue
100 self.tolerance = tolerance
102 def match(self, value, mo):
103 if not isinstance(value, float):
106 error = abs(self.refValue - value)
108 error = error / self.refValue
109 return error <= self.tolerance
112 return repr(self.refValue)
115 class BitmaskMatcher(Matcher):
117 def __init__(self, refElements):
118 self.refElements = refElements
120 def match(self, value, mo):
121 return self._matchSequence(self.refElements, value, mo)
124 return ' | '.join(map(str, self.refElements))
127 class OffsetMatcher(Matcher):
129 def __init__(self, refValue, offset):
130 self.refValue = refValue
133 def match(self, value, mo):
134 return self.refValue.match(value - self.offset, mo)
137 return '%s + %i' % (self.refValue, self.offset)
140 class ArrayMatcher(Matcher):
142 def __init__(self, refElements):
143 self.refElements = refElements
145 def match(self, value, mo):
146 return self._matchSequence(self.refElements, value, mo)
149 return '{' + ', '.join(map(str, self.refElements)) + '}'
152 class StructMatcher(Matcher):
154 def __init__(self, refMembers):
155 self.refMembers = refMembers
157 def match(self, value, mo):
158 if not isinstance(value, dict):
161 if len(value) != len(self.refMembers):
164 for name, refMember in self.refMembers.iteritems():
170 if not refMember.match(member, mo):
176 return '{' + ', '.join(['%s = %s' % refMember for refMember in self.refMembers.iteritems()]) + '}'
179 class CallMatcher(Matcher):
181 def __init__(self, callNo, functionName, args, ret):
183 self.functionName = functionName
187 def match(self, call, mo):
188 callNo, srcFunctionName, srcArgs, srcRet = call
190 if self.functionName != srcFunctionName:
193 refArgs = [value for name, value in self.args]
194 srcArgs = [value for name, value in srcArgs]
196 if not self._matchSequence(refArgs, srcArgs, mo):
200 if srcRet is not None:
203 if not self.ret.match(srcRet, mo):
206 if self.callNo is not None:
207 if not self.callNo.match(callNo, mo):
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)
220 class TraceMismatch(Exception):
227 def __init__(self, calls):
230 def match(self, calls, verbose = False):
232 srcCalls = iter(calls)
233 for refCall in self.calls:
239 srcCall = srcCalls.next()
240 except StopIteration:
242 raise TraceMismatch('missing call `%s` (found `%s`)' % (refCall, skippedSrcCalls[0]))
244 raise TraceMismatch('missing call %s' % refCall)
246 print '\t%s %s%r = %r' % srcCall
247 if refCall.match(srcCall, mo):
250 skippedSrcCalls.append(srcCall)
254 return ''.join(['%s\n' % call for call in self.calls])
257 #######################################################################
263 class ParseError(Exception):
265 def __init__(self, msg=None, filename=None, line=None, col=None):
267 self.filename = filename
272 return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None])
276 """Stateless scanner."""
278 # should be overriden by derived classes
287 flags |= re.IGNORECASE
288 self.tokens_re = re.compile(
289 '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]),
293 def next(self, buf, pos):
296 mo = self.tokens_re.match(buf, pos)
299 type, regexp, test_lit = self.tokens[mo.lastindex - 1]
302 type = self.literals.get(text, type)
303 return type, text, pos
306 return self.symbols.get(c, None), c, pos + 1
311 def __init__(self, type, text, line, col):
320 # should be overriden by derived classes
324 newline_re = re.compile(r'\r\n?|\n')
326 def __init__(self, buf = None, pos = 0, filename = None, fp = None):
330 length = os.path.getsize(fp.name)
333 # read whole file into memory
337 # map the whole file into memory
339 # length must not be zero
340 buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ)
341 pos = os.lseek(fileno, 0, 1)
349 except AttributeError:
356 self.filename = filename
365 type, text, endpos = self.scanner.next(self.buf, pos)
366 assert pos + len(text) == endpos
368 type, text = self.filter(type, text)
374 msg = 'unexpected char '
375 if text >= ' ' and text <= '~':
378 msg += "0x%X" % ord(text)
379 raise ParseError(msg, self.filename, line, col)
382 return Token(type = type, text = text, line = line, col = col)
384 def consume(self, text):
387 for mo in self.newline_re.finditer(text, pos):
392 # update column number
394 tabpos = text.find('\t', pos)
397 self.col += tabpos - pos
398 self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1
400 self.col += len(text) - pos
405 def __init__(self, lexer):
407 self.lookahead = self.lexer.next()
409 def match(self, type):
410 return self.lookahead.type == type
412 def skip(self, type):
413 while not self.match(type):
418 msg = 'unexpected token %r' % self.lookahead.text,
419 filename = self.lexer.filename,
420 line = self.lookahead.line,
421 col = self.lookahead.col)
423 def consume(self, type = None):
424 if type is not None and not self.match(type):
426 token = self.lookahead
427 self.lookahead = self.lexer.next()
431 #######################################################################
433 ID, NUMBER, HEXNUM, STRING, WILDCARD, LPAREN, RPAREN, LCURLY, RCURLY, COMMA, AMP, EQUAL, PLUS, VERT, BLOB = xrange(15)
436 class CallScanner(Scanner):
438 # token regular expression table
441 (SKIP, r'[ \t\f\r\n\v]+', False),
444 (ID, r'[a-zA-Z_][a-zA-Z0-9_]*(?:::[a-zA-Z_][a-zA-Z0-9_]*)?', True),
447 (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
450 (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+][0-9]+)?', False),
453 (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
456 (WILDCARD, r'<[^>]*>', False),
478 class CallLexer(Lexer):
480 scanner = CallScanner()
482 def filter(self, type, text):
487 text = text.replace('\\\r\n', '')
488 text = text.replace('\\\r', '')
489 text = text.replace('\\\n', '')
492 text = text.replace('\\"', '"')
499 class TraceParser(Parser):
501 def __init__(self, stream):
502 lexer = CallLexer(fp = stream)
503 Parser.__init__(self, lexer)
506 return self.match(EOF)
509 while not self.eof():
511 return TraceMatcher(self.calls)
513 def parse_call(self):
514 if self.lookahead.type == NUMBER:
515 token = self.consume()
516 callNo = self.handleInt(int(token.text))
517 elif self.lookahead.type == WILDCARD:
518 token = self.consume()
519 callNo = self.handleWildcard((token.text[1:-1]))
523 functionName = self.consume(ID).text
525 args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
527 if self.match(EQUAL):
529 ret = self.parse_value()
533 self.handleCall(callNo, functionName, args, ret)
535 def parse_pair(self):
536 '''Parse a `name = value` pair.'''
537 name = self.consume(ID).text
539 value = self.parse_value()
542 def parse_opt_pair(self):
543 '''Parse an optional `name = value` pair.'''
545 name = self.consume(ID).text
546 if self.match(EQUAL):
548 value = self.parse_value()
554 value = self.parse_value()
560 def parse_value(self):
561 value = self._parse_value()
564 while self.match(VERT):
566 value = self._parse_value()
568 return self.handleBitmask(flags)
569 elif self.match(PLUS):
571 if self.match(NUMBER):
572 token = self.consume()
573 offset = int(token.text)
574 elif self.match(HEXNUM):
575 token = self.consume()
576 offset = int(token.text, 16)
579 return self.handleOffset(value, offset)
583 def _parse_value(self):
586 value = [self.parse_value()]
587 return self.handleArray(value)
589 token = self.consume()
591 return self.handleID(value)
592 elif self.match(STRING):
593 token = self.consume()
595 return self.handleString(value)
596 elif self.match(NUMBER):
597 token = self.consume()
598 if '.' in token.text:
599 value = float(token.text)
600 return self.handleFloat(value)
602 value = int(token.text)
603 return self.handleInt(value)
604 elif self.match(HEXNUM):
605 token = self.consume()
606 value = int(token.text, 16)
607 return self.handleInt(value)
608 elif self.match(LCURLY):
609 value = self.parse_sequence(LCURLY, RCURLY, self.parse_opt_pair)
610 if len(value) and isinstance(value[0], tuple):
612 return self.handleStruct(value)
614 return self.handleArray(value)
615 elif self.match(BLOB):
616 token = self.consume()
618 token = self.consume()
619 length = int(token.text)
621 return self.handleBlob(length)
622 elif self.match(WILDCARD):
623 token = self.consume()
624 return self.handleWildcard(token.text[1:-1])
628 def parse_sequence(self, ltype, rtype, elementParser):
629 '''Parse a comma separated list'''
635 while not self.match(rtype):
640 element = elementParser()
641 elements.append(element)
646 def handleID(self, value):
647 raise NotImplementedError
649 def handleInt(self, value):
650 raise NotImplementedError
652 def handleFloat(self, value):
653 raise NotImplementedError
655 def handleString(self, value):
656 raise NotImplementedError
658 def handleBitmask(self, value):
659 raise NotImplementedError
661 def handleOffset(self, value, offset):
662 raise NotImplementedError
664 def handleArray(self, value):
665 raise NotImplementedError
667 def handleStruct(self, value):
668 raise NotImplementedError
670 def handleBlob(self, length):
671 return self.handleID('blob(%u)' % length)
673 def handleWildcard(self, name):
674 raise NotImplementedError
676 def handleCall(self, callNo, functionName, args, ret):
677 raise NotImplementedError
680 class RefTraceParser(TraceParser):
682 def __init__(self, fileName):
683 TraceParser.__init__(self, open(fileName, 'rt'))
687 TraceParser.parse(self)
688 return TraceMatcher(self.calls)
690 def handleID(self, value):
691 return LiteralMatcher(value)
693 def handleInt(self, value):
694 return LiteralMatcher(value)
696 def handleFloat(self, value):
697 return ApproxMatcher(value)
699 def handleString(self, value):
700 return LiteralMatcher(value)
702 def handleBitmask(self, value):
703 return BitmaskMatcher(value)
705 def handleOffset(self, value, offset):
706 return OffsetMatcher(value, offset)
708 def handleArray(self, value):
709 return ArrayMatcher(value)
711 def handleStruct(self, value):
712 return StructMatcher(value)
714 def handleWildcard(self, name):
715 return WildcardMatcher(name)
717 def handleCall(self, callNo, functionName, args, ret):
718 call = CallMatcher(callNo, functionName, args, ret)
719 self.calls.append(call)
722 class SrcTraceParser(TraceParser):
724 def __init__(self, stream):
725 TraceParser.__init__(self, stream)
729 TraceParser.parse(self)
732 def handleID(self, value):
735 def handleInt(self, value):
738 def handleFloat(self, value):
741 def handleString(self, value):
744 def handleBitmask(self, value):
747 def handleArray(self, elements):
748 return list(elements)
750 def handleStruct(self, members):
753 def handleCall(self, callNo, functionName, args, ret):
754 call = (callNo, functionName, args, ret)
755 self.calls.append(call)
759 # Parse command line options
760 optparser = optparse.OptionParser(
761 usage='\n\t%prog [OPTIONS] REF_TXT SRC_TRACE',
763 optparser.add_option(
764 '--apitrace', metavar='PROGRAM',
765 type='string', dest='apitrace', default=os.environ.get('APITRACE', 'apitrace'),
766 help='path to apitrace executable')
767 optparser.add_option(
770 dest="verbose", default=True,
771 help="verbose output")
772 (options, args) = optparser.parse_args(sys.argv[1:])
775 optparser.error('wrong number of arguments')
777 refFileName, srcFileName = args
779 refParser = RefTraceParser(refFileName)
780 refTrace = refParser.parse()
782 sys.stdout.write('// Reference\n')
783 sys.stdout.write(str(refTrace))
784 sys.stdout.write('\n')
786 if srcFileName.endswith('.trace'):
787 cmd = [options.apitrace, 'dump', '--color=never', srcFileName]
788 p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
791 srcStream = open(srcFileName, 'rt')
792 srcParser = SrcTraceParser(srcStream)
793 srcTrace = srcParser.parse()
795 sys.stdout.write('// Source\n')
796 sys.stdout.write(''.join(['%s %s%r = %r\n' % call for call in srcTrace]))
797 sys.stdout.write('\n')
800 sys.stdout.write('// Matching\n')
801 mo = refTrace.match(srcTrace, options.verbose)
803 sys.stdout.write('\n')
806 sys.stdout.write('// Parameters\n')
807 paramNames = mo.params.keys()
809 for paramName in paramNames:
810 print '%s = %r' % (paramName, mo.params[paramName])
813 if __name__ == '__main__':