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 (SKIP, r'//[^\r\n]*', False),
447 (ID, r'[a-zA-Z_][a-zA-Z0-9_]*(?:::[a-zA-Z_][a-zA-Z0-9_]*)?', True),
450 (HEXNUM, r'-?0x[0-9a-fA-F]+', False),
453 (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)(?:[eE][-+][0-9]+)?', False),
456 (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False),
459 (WILDCARD, r'<[^>]*>', False),
481 class CallLexer(Lexer):
483 scanner = CallScanner()
485 def filter(self, type, text):
490 text = text.replace('\\\r\n', '')
491 text = text.replace('\\\r', '')
492 text = text.replace('\\\n', '')
495 text = text.replace('\\"', '"')
502 class TraceParser(Parser):
504 def __init__(self, stream):
505 lexer = CallLexer(fp = stream)
506 Parser.__init__(self, lexer)
509 return self.match(EOF)
512 while not self.eof():
514 return TraceMatcher(self.calls)
516 def parse_call(self):
517 if self.lookahead.type == NUMBER:
518 token = self.consume()
519 callNo = self.handleInt(int(token.text))
520 elif self.lookahead.type == WILDCARD:
521 token = self.consume()
522 callNo = self.handleWildcard((token.text[1:-1]))
526 functionName = self.consume(ID).text
528 args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair)
530 if self.match(EQUAL):
532 ret = self.parse_value()
536 self.handleCall(callNo, functionName, args, ret)
538 def parse_pair(self):
539 '''Parse a `name = value` pair.'''
540 name = self.consume(ID).text
542 value = self.parse_value()
545 def parse_opt_pair(self):
546 '''Parse an optional `name = value` pair.'''
548 name = self.consume(ID).text
549 if self.match(EQUAL):
551 value = self.parse_value()
557 value = self.parse_value()
563 def parse_value(self):
564 value = self._parse_value()
567 while self.match(VERT):
569 value = self._parse_value()
571 return self.handleBitmask(flags)
572 elif self.match(PLUS):
574 if self.match(NUMBER):
575 token = self.consume()
576 offset = int(token.text)
577 elif self.match(HEXNUM):
578 token = self.consume()
579 offset = int(token.text, 16)
582 return self.handleOffset(value, offset)
586 def _parse_value(self):
589 value = [self.parse_value()]
590 return self.handleArray(value)
592 token = self.consume()
594 return self.handleID(value)
595 elif self.match(STRING):
596 token = self.consume()
598 return self.handleString(value)
599 elif self.match(NUMBER):
600 token = self.consume()
602 value = int(token.text)
604 value = float(token.text)
605 return self.handleFloat(value)
607 return self.handleInt(value)
608 elif self.match(HEXNUM):
609 token = self.consume()
610 value = int(token.text, 16)
611 return self.handleInt(value)
612 elif self.match(LCURLY):
613 value = self.parse_sequence(LCURLY, RCURLY, self.parse_opt_pair)
614 if len(value) and isinstance(value[0], tuple):
616 return self.handleStruct(value)
618 return self.handleArray(value)
619 elif self.match(BLOB):
620 token = self.consume()
622 token = self.consume()
623 length = int(token.text)
625 return self.handleBlob(length)
626 elif self.match(WILDCARD):
627 token = self.consume()
628 return self.handleWildcard(token.text[1:-1])
632 def parse_sequence(self, ltype, rtype, elementParser):
633 '''Parse a comma separated list'''
639 while not self.match(rtype):
644 element = elementParser()
645 elements.append(element)
650 def handleID(self, value):
651 raise NotImplementedError
653 def handleInt(self, value):
654 raise NotImplementedError
656 def handleFloat(self, value):
657 raise NotImplementedError
659 def handleString(self, value):
660 raise NotImplementedError
662 def handleBitmask(self, value):
663 raise NotImplementedError
665 def handleOffset(self, value, offset):
666 raise NotImplementedError
668 def handleArray(self, value):
669 raise NotImplementedError
671 def handleStruct(self, value):
672 raise NotImplementedError
674 def handleBlob(self, length):
675 return self.handleID('blob(%u)' % length)
677 def handleWildcard(self, name):
678 raise NotImplementedError
680 def handleCall(self, callNo, functionName, args, ret):
681 raise NotImplementedError
684 class RefTraceParser(TraceParser):
686 def __init__(self, fileName):
687 TraceParser.__init__(self, open(fileName, 'rt'))
691 TraceParser.parse(self)
692 return TraceMatcher(self.calls)
694 def handleID(self, value):
695 return LiteralMatcher(value)
697 def handleInt(self, value):
698 return LiteralMatcher(value)
700 def handleFloat(self, value):
701 return ApproxMatcher(value)
703 def handleString(self, value):
704 return LiteralMatcher(value)
706 def handleBitmask(self, value):
707 return BitmaskMatcher(value)
709 def handleOffset(self, value, offset):
710 return OffsetMatcher(value, offset)
712 def handleArray(self, value):
713 return ArrayMatcher(value)
715 def handleStruct(self, value):
716 return StructMatcher(value)
718 def handleWildcard(self, name):
719 return WildcardMatcher(name)
721 def handleCall(self, callNo, functionName, args, ret):
722 call = CallMatcher(callNo, functionName, args, ret)
723 self.calls.append(call)
726 class SrcTraceParser(TraceParser):
728 def __init__(self, stream):
729 TraceParser.__init__(self, stream)
733 TraceParser.parse(self)
736 def handleID(self, value):
739 def handleInt(self, value):
742 def handleFloat(self, value):
745 def handleString(self, value):
748 def handleBitmask(self, value):
751 def handleArray(self, elements):
752 return list(elements)
754 def handleStruct(self, members):
757 def handleCall(self, callNo, functionName, args, ret):
758 call = (callNo, functionName, args, ret)
759 self.calls.append(call)
763 # Parse command line options
764 optparser = optparse.OptionParser(
765 usage='\n\t%prog [OPTIONS] REF_TXT SRC_TRACE',
767 optparser.add_option(
768 '--apitrace', metavar='PROGRAM',
769 type='string', dest='apitrace', default=os.environ.get('APITRACE', 'apitrace'),
770 help='path to apitrace executable')
771 optparser.add_option(
774 dest="verbose", default=True,
775 help="verbose output")
776 (options, args) = optparser.parse_args(sys.argv[1:])
779 optparser.error('wrong number of arguments')
781 refFileName, srcFileName = args
783 refParser = RefTraceParser(refFileName)
784 refTrace = refParser.parse()
786 sys.stdout.write('// Reference\n')
787 sys.stdout.write(str(refTrace))
788 sys.stdout.write('\n')
790 if srcFileName.endswith('.trace'):
791 cmd = [options.apitrace, 'dump', '--verbose', '--color=never', srcFileName]
792 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True)
795 srcStream = open(srcFileName, 'rt')
796 srcParser = SrcTraceParser(srcStream)
797 srcTrace = srcParser.parse()
799 sys.stdout.write('// Source\n')
800 sys.stdout.write(''.join(['%s %s%r = %r\n' % call for call in srcTrace]))
801 sys.stdout.write('\n')
804 sys.stdout.write('// Matching\n')
805 mo = refTrace.match(srcTrace, options.verbose)
807 sys.stdout.write('\n')
810 sys.stdout.write('// Parameters\n')
811 paramNames = mo.params.keys()
813 for paramName in paramNames:
814 print '%s = %r' % (paramName, mo.params[paramName])
817 if __name__ == '__main__':