From: José Fonseca Date: Sun, 11 Mar 2012 15:48:38 +0000 (+0000) Subject: Better checking (WIP). X-Git-Url: https://git.cworth.org/git?p=apitrace-tests;a=commitdiff_plain;h=8c500069e366f273fd806349e3c0fefbdb639d8d Better checking (WIP). --- diff --git a/checker.py b/checker.py new file mode 100644 index 0000000..32c9663 --- /dev/null +++ b/checker.py @@ -0,0 +1,543 @@ +#!/usr/bin/env python +########################################################################## +# +# Copyright 2008-2012 Jose Fonseca +# All Rights Reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +##########################################################################/ + + +import sys +import re + + +class ValueMatcher: + + def match(self, value): + raise NotImplementedError + + def __str__(self): + raise NotImplementerError + + +class WildcardMatcher(ValueMatcher): + + def match(self, value): + return true + + def __str__(self): + return '*' + + +class LiteralValueMatcher(ValueMatcher): + + def __init__(self, refValue): + self.refValue = refValue + + def match(self, value): + return self.refValue == value + + def __str__(self): + return repr(self.refValue) + + +class ApproxValueMatcher(ValueMatcher): + + + def __init__(self, refValue, tolerance = 2**-23): + self.refValue = refValue + self.tolerance = tolerance + + def match(self, value): + error = abs(self.refValue - value) + if self.refValue: + error = error / self.refValue + return error <= self.tolerance + + def __str__(self): + return repr(self.refValue) + + +class ArrayMatcher(ValueMatcher): + + def __init__(self, refElements): + self.refElements = refElements + + def match(self, value): + if not isinstance(value, list): + return False + + if len(value) != len(self.refElements): + return False + + for refElement, element in zip(self.refElements, value): + if not refElement.match(element): + return False + + return True + + def __str__(self): + return '{' + ', '.join(map(str, self.refElements)) + '}' + + +class StructMatcher(ValueMatcher): + + def __init__(self, refMembers): + self.refMembers = refMembers + + def match(self, value): + if not isinstance(value, dict): + return False + + if len(value) != len(self.refMembers): + return False + + for name, refMember in self.refMembers.iteritems(): + try: + member = value[name] + except KeyError: + return False + else: + if not refMember.match(member): + return False + + return True + + def __str__(self): + print self.refMembers + return '{' + ', '.join(['%s = %s' % refMember for refMember in self.refMembers.iteritems()]) + '}' + + +class CallMatcher: + + def __init__(self, refFunctionName, refArgs, refRet = None): + self.refFunctionName = refFunctionName + self.refArgs = refArgs + self.refRet = refRet + + def match(self, functionName, args, ret = None): + if refFunctionName != functionName: + return False + + if len(self.refArgs) != len(args): + return False + + for (refArgName, refArg), (argName, arg) in zip(self.refArgs, args): + if not refArg.match(arg): + return False + + if self.refRet is None: + if ret is not None: + return False + else: + if not self.refRet.match(ret): + return False + + return True + + def __str__(self): + s = self.refFunctionName + s += '(' + ', '.join(['%s = %s' % refArg for refArg in self.refArgs]) + ')' + if self.refRet is not None: + s += ' = ' + str(self.refRet) + return s + + +class TraceMatcher: + + def __init__(self, refCalls): + self.refCalls = refCalls + + def __str__(self): + return ''.join(['%s\n' % refCall for refCall in self.refCalls]) + + +EOF = -1 +SKIP = -2 + + +class ParseError(Exception): + + def __init__(self, msg=None, filename=None, line=None, col=None): + self.msg = msg + self.filename = filename + self.line = line + self.col = col + + def __str__(self): + return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None]) + + +class Scanner: + """Stateless scanner.""" + + # should be overriden by derived classes + tokens = [] + symbols = {} + literals = {} + ignorecase = False + + def __init__(self): + flags = re.DOTALL + if self.ignorecase: + flags |= re.IGNORECASE + self.tokens_re = re.compile( + '|'.join(['(' + regexp + ')' for type, regexp, test_lit in self.tokens]), + flags + ) + + def next(self, buf, pos): + if pos >= len(buf): + return EOF, '', pos + mo = self.tokens_re.match(buf, pos) + if mo: + text = mo.group() + type, regexp, test_lit = self.tokens[mo.lastindex - 1] + pos = mo.end() + if test_lit: + type = self.literals.get(text, type) + return type, text, pos + else: + c = buf[pos] + return self.symbols.get(c, None), c, pos + 1 + + +class Token: + + def __init__(self, type, text, line, col): + self.type = type + self.text = text + self.line = line + self.col = col + + +class Lexer: + + # should be overriden by derived classes + scanner = None + tabsize = 8 + + newline_re = re.compile(r'\r\n?|\n') + + def __init__(self, buf = None, pos = 0, filename = None, fp = None): + if fp is not None: + try: + fileno = fp.fileno() + length = os.path.getsize(fp.name) + import mmap + except: + # read whole file into memory + buf = fp.read() + pos = 0 + else: + # map the whole file into memory + if length: + # length must not be zero + buf = mmap.mmap(fileno, length, access = mmap.ACCESS_READ) + pos = os.lseek(fileno, 0, 1) + else: + buf = '' + pos = 0 + + if filename is None: + try: + filename = fp.name + except AttributeError: + filename = None + + self.buf = buf + self.pos = pos + self.line = 1 + self.col = 1 + self.filename = filename + + def next(self): + while True: + # save state + pos = self.pos + line = self.line + col = self.col + + type, text, endpos = self.scanner.next(self.buf, pos) + assert pos + len(text) == endpos + self.consume(text) + type, text = self.filter(type, text) + self.pos = endpos + + if type == SKIP: + continue + elif type is None: + msg = 'unexpected char ' + if text >= ' ' and text <= '~': + msg += "'%s'" % text + else: + msg += "0x%X" % ord(text) + raise ParseError(msg, self.filename, line, col) + else: + break + return Token(type = type, text = text, line = line, col = col) + + def consume(self, text): + # update line number + pos = 0 + for mo in self.newline_re.finditer(text, pos): + self.line += 1 + self.col = 1 + pos = mo.end() + + # update column number + while True: + tabpos = text.find('\t', pos) + if tabpos == -1: + break + self.col += tabpos - pos + self.col = ((self.col - 1)//self.tabsize + 1)*self.tabsize + 1 + pos = tabpos + 1 + self.col += len(text) - pos + + +class Parser: + + def __init__(self, lexer): + self.lexer = lexer + self.lookahead = self.lexer.next() + + def match(self, type): + return self.lookahead.type == type + + def skip(self, type): + while not self.match(type): + self.consume() + + def error(self): + raise ParseError( + msg = 'unexpected token %r' % self.lookahead.text, + filename = self.lexer.filename, + line = self.lookahead.line, + col = self.lookahead.col) + + def consume(self, type = None): + if type is not None and not self.match(type): + self.error() + token = self.lookahead + self.lookahead = self.lexer.next() + return token + + +ID = 0 +NUMBER = 1 +HEXNUM = 2 +STRING = 3 + +LPAREN = 4 +RPAREN = 5 +LCURLY = 6 +RCURLY = 7 +COMMA = 8 +AMP = 9 +EQUAL = 11 + +BLOB = 12 + + +class CallScanner(Scanner): + + # token regular expression table + tokens = [ + # whitespace + (SKIP, r'[ \t\f\r\n\v]+', False), + + # Alphanumeric IDs + (ID, r'[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*', True), + + # Numeric IDs + (HEXNUM, r'-?0x[0-9a-fA-F]+', False), + + # Numeric IDs + (NUMBER, r'-?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)', False), + + # String IDs + (STRING, r'"[^"\\]*(?:\\.[^"\\]*)*"', False), + ] + + # symbol table + symbols = { + '(': LPAREN, + ')': RPAREN, + '{': LCURLY, + '}': RCURLY, + ',': COMMA, + '&': AMP, + '=': EQUAL, + } + + # literal table + literals = { + 'blob': BLOB + } + + +class CallLexer(Lexer): + + scanner = CallScanner() + + def filter(self, type, text): + if type == STRING: + text = text[1:-1] + + # line continuations + text = text.replace('\\\r\n', '') + text = text.replace('\\\r', '') + text = text.replace('\\\n', '') + + # quotes + text = text.replace('\\"', '"') + + type = ID + + return type, text + + +class CallParser(Parser): + + def __init__(self, stream): + lexer = CallLexer(fp = stream) + Parser.__init__(self, lexer) + + def parse(self): + while not self.match(EOF): + self.parse_call() + + def parse_call(self): + if self.lookahead.type == NUMBER: + token = self.consume() + callNo = int(token.text) + else: + callNo = None + + functionName = self.consume(ID).text + + args = self.parse_sequence(LPAREN, RPAREN, self.parse_pair) + + if self.match(EQUAL): + self.consume(EQUAL) + ret = self.parse_value() + else: + ret = None + + self.handle_call(callNo, functionName, args, ret) + + def parse_pair(self): + '''Parse a `name = value` pair.''' + name = self.consume(ID).text + self.consume(EQUAL) + value = self.parse_value() + return name, value + + def parse_opt_pair(self): + '''Parse an optional `name = value` pair.''' + if self.match(ID): + name = self.consume(ID).text + if self.match(EQUAL): + self.consume(EQUAL) + value = self.parse_value() + else: + value = name + name = None + else: + name = None + value = self.parse_value() + if name is None: + return value + else: + return name, value + + def parse_value(self): + if self.match(AMP): + self.consume() + value = [self.parse_value()] + return ArrayMatcher(value) + elif self.match(ID): + token = self.consume() + value = token.text + return LiteralValueMatcher(value) + elif self.match(STRING): + token = self.consume() + value = token.text + return LiteralValueMatcher(value) + elif self.match(NUMBER): + token = self.consume() + value = float(token.text) + return ApproxValueMatcher(value) + elif self.match(HEXNUM): + token = self.consume() + value = int(token.text, 16) + return LiteralValueMatcher(value) + elif self.match(LCURLY): + value = self.parse_sequence(LCURLY, RCURLY, self.parse_opt_pair) + if len(value) and isinstance(value[0], tuple): + return StructMatcher(dict(value)) + else: + return ArrayMatcher(value) + elif self.match(BLOB): + token = self.consume() + self.consume(LPAREN) + length = self.consume() + self.consume(RPAREN) + # TODO + return WildcardMatcher() + else: + self.error() + + def parse_sequence(self, ltype, rtype, elementParser): + '''Parse a comma separated list''' + + elements = [] + + self.consume(ltype) + sep = None + while not self.match(rtype): + if sep is None: + sep = COMMA + else: + self.consume(sep) + element = elementParser() + elements.append(element) + self.consume(rtype) + + return elements + + def handle_call(self, callNo, functionName, args, ret): + matcher = CallMatcher(functionName, args, ret) + + if callNo is not None: + sys.stdout.write('%u ' % callNo) + sys.stdout.write(str(matcher)) + sys.stdout.write('\n') + + +def main(): + parser = CallParser(sys.stdin) + parser.parse() + + +if __name__ == '__main__': + main()