Package medite :: Package MediteAppli :: Package test :: Module contract
[hide private]
[frames] | no frames]

Source Code for Module medite.MediteAppli.test.contract

   1  #!/usr/bin/env python 
   2  """Programming-by-contract for Python, based on Eiffel's DBC. 
   3   
   4  Programming by contract documents class and modules with invariants, 
   5  expressions that must be true during the lifetime of a module or 
   6  instance; and documents functions and methods with pre- and post- 
   7  conditions that must be true during entry and return. 
   8   
   9  Copyright (c) 2003, Terence Way 
  10  This module is free software, and you may redistribute it and/or modify 
  11  it under the same terms as Python itself, so long as this copyright message 
  12  and disclaimer are retained in their original form. 
  13   
  14  IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, 
  15  SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF 
  16  THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
  17  DAMAGE. 
  18   
  19  THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT 
  20  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
  21  PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, 
  22  AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, 
  23  SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 
  24  """ 
  25   
  26  # Changes: 
  27  #   ttw001 2003-05-26 
  28  #   Jeff Epler points out we should have separate assertion exception 
  29  #   base class ContractViolationError, then exception classes 
  30  #   PreconditionViolationError, PostconditionViolationError, and 
  31  #   InvariantViolationError inheriting from ContractViolationError. 
  32  # 
  33  #   ttw002 2003-05-26 
  34  #   Support restructured text by allowing double colon (::) in 
  35  #   addition to single colon (:) after 'pre' 'inv' 'post' or 
  36  #   post variable declarations. 
  37  # 
  38  #   ttw003 2003-05-29 
  39  #   Dickon Reed discovers problem: invariants mustn't be checked after 
  40  #   a constructor throws an exception. 
  41  # 
  42  #   ttw004 2003-06-02 
  43  #   Make sure that objects returned in _get_members only include 
  44  #   things defined by that module 
  45  # 
  46  #   ttw005 2003-06-02 
  47  #   Save line number information in parse_docstring, _read_block, to 
  48  #   be trapped if a TokenError or ParseError occurs, and to pass along 
  49  #   to any contract exceptions raised. 
  50  # 
  51  #   ttw006 2003-06-06 
  52  #   The _read_block internal function past the last line of a docstring 
  53  #   contract.  This saves some hackery in parse_docstring, and makes 
  54  #   _read_block more useful for external tools. 
  55  # 
  56  #   ttw007 2003-06-06 
  57  #   Phillip Eby points out that OR-ed pre-conditions are worse than 
  58  #   useless.  So instead of OR-ing pre-conditions of a method with all 
  59  #   overridden methods, we check if a pre-condition fails whether *any* 
  60  #   overridden pre-condition would succeed, if so we raise an 
  61  #   InvalidPreconditionError 
  62  # 
  63  #   ttw008 2003-06-13 
  64  #   Support partial contract enforcement: check preconditions only. 
  65  #   add flag to checkmod to specify checklevel: CHECK_NONE, 
  66  #   CHECK_PRECONDITIONS, or CHECK_ALL 
  67  # 
  68  #   ttw009 2003-06-13 
  69  #   String module names can be passed into checkmod as well. 
  70  # 
  71  #   ttw010 2003-06-20 
  72  #   Python 2.2.1 has bool, True, False but Python 2.2 doesn't.  To 
  73  #   support built-in Python on Mac OS X, define local bool() if not 
  74  #   defined globally 
  75  # 
  76  #   ttw011 2003-06-21 
  77  #   Support Jython (Python 2.1).  Refactored the tokenize logic in 
  78  #   _read_block() to support older tokenize.tokenize(), backtracked 
  79  #   some 2.2 conveniences (like 'x in {dict}'). 
  80  # 
  81  #   ttw012 2005-10-19 
  82  #   From Gintautas Miliauskas: 
  83  #   "I discovered one problem though: you expect __import__(modname) to 
  84  #   return the corresponding module, however, it does not work like 
  85  #   that.  For example, if you invoke __import__('a.b.c.d'), the result 
  86  #   will be module <a>, not module <d>.  Yes, I think this is dumb too, 
  87  #   but that's how it works.  A workaround is to use __import__ to 
  88  #   import the module and then get the actual module from sys.modules." 
  89   
  90  __author__ = "Terence Way" 
  91  __email__ = "terry@wayforward.net" 
  92  __version__ = "1.1: October 19, 2005" 
  93  MODULE = 'contract' 
  94   
  95  import new 
  96  import re 
  97  import sys 
  98  import tokenize 
  99   
 100  from cStringIO import StringIO 
 101  from types import * 
 102   
 103  # Programming-by-contract extends the language to include invariant 
 104  # expressions for classes, and pre-condition and post-condition 
 105  # expressions for methods.  These are very similar to assertions, 
 106  # expressions which must be true or the program is stopped. 
 107  # 
 108  # Class invariants are expressions which must be true at the end of a 
 109  # class's constructor, and at the beginning and end of each public 
 110  # method. 
 111  # 
 112  # Pre-conditions must be true at the start of each method.  Post- 
 113  # conditions must be true at the end of each method.  Post-condition 
 114  # logic has access to an 'old' variable which records variable values 
 115  # before they were changed by the method. 
 116  # 
 117  # This implementation tries to fix what I consider a defect in 
 118  # Eiffel's DBC -- while it is easy to specify what does change after 
 119  # invoking a method, it is impossible to specify what doesn't change. 
 120  # This makes it hard to reason about methods, and limits portability 
 121  # to more formal techniques such as Z. 
 122  # 
 123  # A simple fix is to declare what parts change.  Simply modify the 
 124  # line 'post:' to 'post [a]:' to declare that the variable a is the 
 125  # only thing changed by the sort function.  This is modelled after Z 
 126  # schemas [2]. 
 127  # 
 128  # Simply saying 'post:' means anything can be changed: this is the 
 129  # original semantics of the Eiffel ENSURES statement.  Saying 'post 
 130  # []:' means nothing is changed. 
 131  # 
 132  # Python Specifications 
 133  # --------------------- 
 134  # The docstring of any module or class can have invariants documented 
 135  # by having the keyword 'inv' followed by a colon (:) at the start of 
 136  # a line.  Whitespace is ignored.  This is either followed by a single 
 137  # expression on the same line, or a series of expressions on following 
 138  # lines indented past the 'post' key- word.  The normal Python rules 
 139  # about implicit and explicit line continuations are followed here. 
 140  # 
 141  # To support Re-structured text, two colons (::) after the keyword is 
 142  # supported. 
 143  # 
 144  # Module invariants must be true at module load time, and at the entry 
 145  # and return of every public function within the module. 
 146  # 
 147  # Class invariants must be true after the __init__ function returns, 
 148  # and at the entry and return of every public method of the class. 
 149  # 
 150  # The docstring of any function or method can have pre-conditions 
 151  # documented with the keyword 'pre' following the same rules above, 
 152  # and can have post-conditions documented with the keyword 'post' 
 153  # optionally followed by a list of variables.  The variables are in 
 154  # the same scope as the body of the function or method. 
 155  # 
 156  # Expressions have access to some additional convenience values.  To 
 157  # make evaluating sequences easier, there are two functions: forall(a) 
 158  # and exists(a).  To make implication easier, there is a 
 159  # implies(x,a,b=True) function which mirrors C's conditional 
 160  # expression (x?a:c). 
 161  # 
 162  # The expressions in the post-conditions have access to two additional 
 163  # variables, __old__ which is filled with variables declared in the 
 164  # 'post' statement bound to shallow copies before the function or 
 165  # method is called; and __return__ which is bound to the return value 
 166  # of the function or method. 
 167  # 
 168  # Implementation 
 169  # -------------- 
 170  # This module is divided into four parts: 
 171  # 
 172  # 1 -- Find docstrings and parse them for contract expressions... 
 173  # 
 174  #      This is accomplished by: 
 175  #      a. recursive enumerating elements in modules, classes using code 
 176  #         lifted from the 'inspect' Python standard module 
 177  #         see: checkmod, _check 
 178  #      b. scanning the docstrings of public functions with a regular 
 179  #         expression looking for lines that matches 'pre:' 'post:' or 
 180  #         'inv:' at the start 
 181  #         see: parse_docstring 
 182  #      c. Using the 'tokenize' Python tokenizer to build expressions 
 183  #         see: _read_block, _read_decl, _read_token 
 184  # 
 185  # 2 -- Construct functions that do contract checking... 
 186  #      This is done by just constructing big strings that are function 
 187  #      definitions.  Each function or method gets associated with four 
 188  #      'helper' functions... to check pre-conditions, to save old 
 189  #      values, to check post-conditions, and of course the saved 
 190  #      original code.  These are stored as function attributes. 
 191  #         see: _install_wrapper, _define_checker 
 192  # 
 193  # 3 -- Run-time support for call checking 
 194  #         see: call_public_function_*, call_private_function_*, 
 195  #              call_public_method_*, call_private_method_*, 
 196  #              call_constructor_*, call_destructor_* 
 197  # 
 198  # 4 -- Declarations for use within contract expressions 
 199  #      includes 'forall' 'exists' 'implies' and exception classes 
 200   
 201   
 202  # change the keywords here, if necessary 
 203  INV = 'inv' 
 204  PRE = 'pre' 
 205  POST = 'post' 
 206  TYPE_CONTRACTS = [INV] 
 207  CODE_CONTRACTS = [PRE, POST] 
 208  OLD = '__old__' 
 209  RETURN = '__return__' 
 210  PREFIX = '__assert_' 
 211   
 212  # enumeration to pass to checkmod: how extensive checking should be 
 213  CHECK_DEFAULT, CHECK_NONE, CHECK_PRECONDITIONS, CHECK_ALL = range(4) 
 214   
 215  _ORIG = 'orig' 
 216  _SAVE = 'save' 
 217  _CHK = 'chk' 
 218   
 219  _CONTRACTS = tuple(TYPE_CONTRACTS + CODE_CONTRACTS) 
 220   
 221  # look for word+ [expr] space* ':' at the start of a line 
 222  # 
 223  _re_start = re.compile(r'^\s*(%s|%s|%s)\s*(\[|:)' % _CONTRACTS, 
 224                         re.MULTILINE) 
 225  _RE_KEYWORD = 1 
 226   
 227  # the tokenizer only seems to return OP(50) for any operators like : [ ] . , 
 228  # we need to do a further lookup to get the operator value, but only for the 
 229  # tokens we care about 
 230  # 
 231  _OPS = {':': tokenize.COLON, 
 232          '[': tokenize.LSQB, 
 233          ']': tokenize.RSQB, 
 234          '.': tokenize.DOT, 
 235          ',': tokenize.COMMA} 
 236   
 237  _EXCEPTIONS = {PRE: 'PreconditionViolationError', 
 238                 POST: 'PostconditionViolationError', 
 239                 INV: 'InvariantViolationError'} 
 240   
 241  # ttw010 support pre 2.2.1... 
 242  try: 
 243      bool, True, False = bool, True, False 
 244  except NameError: 
 245      False, True = 0, 1 
246 - def bool(x):
247 return not not x
248 # ...ttw010 249 250 ############################################################################### 251 # Part 1 -- Find docstrings and parse them for contract expressions... 252 # 253
254 -def checkmod(module, checklevel = CHECK_DEFAULT):
255 """Add invariant, pre- and post-condition checking to a module. 256 257 pre:: 258 isstring(module) or isinstance(module, ModuleType) 259 checklevel in [CHECK_DEFAULT, CHECK_NONE, CHECK_PRECONDITIONS, 260 CHECK_ALL] 261 """ 262 # ttw009 string module names... 263 if isinstance(module, StringType) or isinstance(module, UnicodeType): 264 # ttw012 imports... 265 __import__(module) 266 module = sys.modules[module] 267 # ...ttw012 268 # ...ttw009 269 270 # ttw008 partial contract enforcement... 271 if checklevel == CHECK_DEFAULT: 272 if __debug__: 273 checklevel = CHECK_ALL 274 else: 275 checklevel = CHECK_PRECONDITIONS 276 # ...ttw008 277 278 if checklevel != CHECK_NONE: 279 # get members *before* we start adding stuff to this module 280 path = [module] 281 members = _get_members(module, path) 282 invs = parse_docstring(module.__doc__, TYPE_CONTRACTS)[0] 283 name = PREFIX + INV 284 func = getattr(module, name, None) 285 # should we override 286 if not func or func.__name__.startswith(PREFIX): 287 if invs[2]: 288 func = _define_checker(name, '', invs, path) 289 else: 290 func = __assert_inv 291 module.__assert_inv = func 292 _check_members(members, path, checklevel) 293 # check module invariants now 294 func()
295
296 -def _check_type(code, path, checklevel):
297 """Modify a class to add invariant checking. 298 299 pre:: 300 isstring(code[0]) 301 type(code[1]) == code[2] 302 isinstance(code[0], MethodType) or isinstance(code[0], FunctionType) 303 isinstance(path[0], ModuleType) 304 forall(path[1:], isclass) 305 """ 306 name, obj = code 307 308 # get members *before* we start adding stuff to this class 309 path = path + [obj] 310 members = _get_members(obj, path) 311 invs = parse_docstring(obj.__doc__, TYPE_CONTRACTS)[0] 312 if invs[2]: 313 func = _define_checker(_mkname(path, INV), 'self', invs, path) 314 setattr(obj, PREFIX + INV, func) 315 delattr(path[0], func.func_name) 316 _check_members(members, path, checklevel)
317
318 -def _check_proc(code, path, checklevel):
319 """Modify a module or class to add invariant checking. 320 """ 321 name, obj = code 322 _install_wrapper(code, parse_docstring(obj.__doc__, CODE_CONTRACTS), path, 323 is_public = _ispublic(name), checklevel = checklevel)
324
325 -def _get_location(f):
326 """Get function location as tuple (name, filename, lineno). 327 328 pre:: 329 isinstance(f, MethodType) or isinstance(f, FunctionType) 330 post[]:: 331 isstring(__return__[0]) 332 isstring(__return__[1]) 333 isinstance(__return__[2], int) 334 """ 335 if isinstance(f, MethodType): 336 f = f.im_func 337 c = f.func_code 338 return f.func_name, c.co_filename, c.co_firstlineno
339
340 -def _get_members(obj, path):
341 """Returns two lists (procs, types) where each list contains (name, 342 value) tuples. 343 344 For classes, only attributes defined by the specific class are returned, 345 i.e. not inherited attributes. Attributes created by this module 346 (prefixed by '__assert_') are skipped as well. 347 348 Examples: 349 >>> import contract 350 >>> path = [contract] 351 >>> hasattr(contract, '_re_start') 352 1 353 >>> '_re_start' in [x[0] for x in _get_members(contract, path)[0]] 354 0 355 >>> '_get_members' in [x[0] for x in _get_members(contract, path)[0]] 356 1 357 >>> 'checkmod' in [x[0] for x in _get_members(contract, path)[0]] 358 1 359 >>> class base: 360 ... def foo(self): pass 361 >>> class derived(base): 362 ... def bar(self): pass 363 364 hasattr can get inherited attributes: 365 >>> hasattr(derived, 'foo') 366 1 367 368 but we don't: 369 >>> path = [__import__('__main__')] 370 >>> 'foo' in [x[0] for x in _get_members(derived, path)[0]] 371 0 372 """ 373 module_name = path[0].__name__ 374 module_dict = path[0].__dict__ 375 parent = path[-1] 376 procs = [] 377 types = [] 378 for key in obj.__dict__.keys(): 379 if not key.startswith(PREFIX): 380 m = getattr(obj, key) 381 # ttw004 only objects that belong to this module... 382 if (isinstance(m, MethodType) and 383 m.im_class is parent) or \ 384 (isinstance(m, FunctionType) and \ 385 m.func_globals is module_dict): 386 procs.append((key, m)) 387 elif isclass(m) and getattr(m, '__module__', None) is module_name: 388 types.append((key, m)) 389 # ...ttw004 390 return (procs, types)
391
392 -def _check_members((procs, types), path, checklevel):
393 for p in procs: 394 _check_proc(p, path, checklevel) 395 for t in types: 396 _check_type(t, path, checklevel)
397
398 -def _ispublic(name):
399 """Checks if a name is public (starts and ends with '__' or doesn't 400 start with a _ at all). 401 402 Examples: 403 >>> _ispublic('__init__') 404 1 405 >>> _ispublic('foo') 406 1 407 >>> _ispublic('_ispublic') 408 0 409 """ 410 return not name.startswith('_') or \ 411 (name.startswith('__') and name.endswith('__'))
412
413 -def parse_docstring(docstring, keywords):
414 """Parse a docstring, looking for design-by-contract expressions. 415 416 Returns a list of tuples: the list is the same length as keywords, and 417 matches each keyword. The tuple is (keyword, [decls], [exprs]), namely 418 the keyword, a list of string declarations, and a list of tuples (string, 419 lineno). 420 421 Examples:: 422 >>> from pprint import pprint 423 >>> pprint( parse_docstring(parse_docstring.__doc__, ['post', 'pre']) ) 424 [('post', [], [('[ x [ 0 ] for x in __return__ ] == keywords', 22)]), 425 ('pre', 426 [], 427 [('docstring is None or isstring ( docstring )', 18), 428 ('forall ( keywords , isstring )', 19)])] 429 430 pre:: 431 docstring is None or isstring(docstring) 432 forall(keywords, isstring) 433 434 post[]:: 435 [x[0] for x in __return__] == keywords 436 """ 437 result = [(x, [], []) for x in keywords] 438 439 if docstring is None: 440 return result 441 442 # step 1: scan through docstring looking for keyword 443 input = StringIO(docstring) 444 445 offset = 0 446 assert input.tell() == 0 447 448 line = input.readline() 449 lineno = 0 # zero-origined because tokenizer keeps 1-origined 450 while line != '': 451 a = _re_start.split(line) 452 453 if len(a) > _RE_KEYWORD and a[_RE_KEYWORD] in keywords: 454 # step 2: found a keyword, now rewind and scan looking 455 # for either an inline expression or a series of sub- 456 # indented expressions 457 input.seek(offset) 458 459 # ttw005... get lineno info and add to exception's lineno 460 # if a TokenError occurs... 461 try: 462 l = _read_block(input, lineno) 463 lineno = l[3] 464 # returns (keyword, decls, exprs, lineno) 465 except tokenize.TokenError, ex: 466 # reformat to include new line info 467 raise tokenize.TokenError(ex[0], 468 (lineno + ex[1][0],) + ex[1][1:]) 469 # ...ttw005 470 471 # Find the right result index based on keyword 472 r = result[keywords.index(l[0])] 473 r[1].extend(l[1]) 474 r[2].extend(l[2]) 475 else: 476 lineno += 1 477 offset = input.tell() 478 line = input.readline() 479 return result
480 481 # ttw011 refactor tokenize parser to use older tokenize.tokenize()... 482 # 483 # [INDENT] NAME 484 # [LSQB (NAME (DOT NAME)* (COMMA NAME (DOT NAME)*)*)* RSQB] 485 # COLON [COLON] 486 # (NEWLINE INDENT (not DEDENT)*) | (not NEWLINE)* 487
488 -class Done(Exception): pass
489
490 -class tokenizer:
491 - def __init__(self, input, startlineno):
492 self.input = input 493 self.startlineno = startlineno 494 self.endlineno = self.lineno = startlineno + 1 495 self.state = self.start 496 self.decl, self.decls, self.expr, self.exprs = [], [], [], [] 497 self.keyword, self.offset = '', input.tell()
498
499 - def next(self, token, string, start, end, line):
500 if token != tokenize.COMMENT and token != tokenize.NL: 501 if token == tokenize.OP and _OPS.has_key(string): 502 token = _OPS[string] 503 self.state(token, string) 504 if token == tokenize.NEWLINE or token == tokenize.NL: 505 self.lineno = self.startlineno + start[0] + 1
506 507 # all following methods are states in the state machine. 508 # the self.state variable indicates which function/state we're in
509 - def start(self, token, string):
510 if token == tokenize.INDENT: 511 self.state = self.indent 512 else: 513 self.indent(token, string)
514
515 - def indent(self, token, string):
516 if token == tokenize.NAME: 517 self.keyword = string 518 self.state = self.name 519 else: 520 raise SyntaxError("expected pre, post, or inv")
521
522 - def name(self, token, string):
523 if token == tokenize.LSQB: 524 self.state = self.decl0 525 else: 526 self.colon(token, string)
527
528 - def decl0(self, token, string):
529 if token == tokenize.NAME: 530 self.decl.append(string) 531 self.state = self.decl1 532 elif token == tokenize.RSQB: 533 self.state = self.colon 534 else: 535 raise SyntaxError("expected variable name or ]")
536
537 - def decl1(self, token, string):
538 if token == tokenize.DOT: 539 self.state = self.decln 540 elif token == tokenize.COMMA: 541 self.decls.append(self.decl) 542 self.decl = [] 543 self.state = self.decln 544 elif token == tokenize.RSQB: 545 self.decls.append(self.decl) 546 self.state = self.colon 547 else: 548 raise SyntaxError("expected one of (,.])")
549
550 - def decln(self, token, string):
551 if token == tokenize.NAME: 552 self.decl.append(string) 553 self.state = self.decl1 554 else: 555 raise SyntaxError("expected name")
556
557 - def colon(self, token, string):
558 if token == tokenize.COLON: 559 self.state = self.colon1 560 else: 561 raise SyntaxError("expected colon(:)")
562
563 - def colon1(self, token, string):
564 if token == tokenize.COLON: 565 self.state = self.colon2 566 else: 567 self.colon2(token, string)
568
569 - def colon2(self, token, string):
570 if token == tokenize.NEWLINE: 571 self.state = self.newline 572 else: 573 self.endtoken = tokenize.NEWLINE 574 self.state = self.rest 575 self.rest(token, string)
576
577 - def newline(self, token, string):
578 if token == tokenize.INDENT: 579 self.endtoken = tokenize.DEDENT 580 self.state = self.rest 581 else: 582 raise IndentationError("expected an indented block")
583
584 - def rest(self, token, string):
585 if token == self.endtoken or token == tokenize.ENDMARKER: 586 if self.expr: 587 self.exprs.append( (' '.join(self.expr), self.lineno) ) 588 raise Done() 589 self.offset, self.endlineno = self.input.tell(), self.lineno 590 if token == tokenize.NEWLINE: 591 self.exprs.append( (' '.join(self.expr), self.lineno) ) 592 self.expr = [] 593 else: 594 self.expr.append(string)
595
596 -def _read_block(input, startlineno):
597 r"""Read an indented block of expressions 598 599 startlineno is *zero* origined line number. 600 601 pre:: 602 input.readline # must have readline function 603 604 Examples: 605 #>>> _read_block(StringIO('\tfoo:\n'), 0) 606 #0 607 >>> _read_block(StringIO('\tpost[]: True\n'), 0) 608 ('post', [], [('True', 1)], 1) 609 >>> _read_block(StringIO('\tpre: 5 + 6 > 10\n'), 0) 610 ('pre', [], [('5 + 6 > 10', 1)], 1) 611 >>> _read_block(StringIO('\tpost:\n\t\t5 + 6 < 12\n\t\t2 + 2 == 4\n'), 0) 612 ('post', [], [('5 + 6 < 12', 2), ('2 + 2 == 4', 3)], 3) 613 >>> _read_block(StringIO('\tpost[foo.bar]: # changes\n' \ 614 ... '\t\tlen(foo.bar) > 0\n'), 0) 615 ('post', [['foo', 'bar']], [('len ( foo . bar ) > 0', 2)], 2) 616 617 Handles double colons (for re-structured text):: 618 >>> _read_block(StringIO('\tpre:: 5 + 6 > 10\n'), 0) 619 ('pre', [], [('5 + 6 > 10', 1)], 1) 620 """ 621 t = tokenizer(input, startlineno) 622 try: 623 tokenize.tokenize(input.readline, t.next) 624 except Done: 625 pass 626 input.seek(t.offset) 627 return (t.keyword, t.decls, t.exprs, t.endlineno)
628 # ...ttw011 629 630 # ...part 1 631 ############################################################################### 632 633 ############################################################################### 634 # Part 2 -- Construct functions that do contract checking... 635 #
636 -def _install_wrapper(code, contracts, path, is_public, checklevel):
637 """Creates and installs a function/method checker. 638 639 pre:: 640 contracts[0][0] == PRE and contracts[1][0] == POST 641 isinstance(path[0], ModuleType) 642 forall(path[1:], isclass) 643 """ 644 name, obj = code 645 646 newpath = path + [obj] 647 648 if isinstance(obj, MethodType): 649 func = obj.im_func 650 invs = hasattr(path[-1], PREFIX + INV) 651 else: 652 func = obj 653 invs = hasattr(path[0], PREFIX + INV) 654 655 # we must create a checker if: 656 # 1. there are any pre-conditions or any post-conditions OR 657 # 2. this is public AND there are invariants 658 if contracts[0][2] or contracts[1][2] or (is_public and invs): 659 argspec = getargspec(func) 660 args = _format_args(argspec) 661 662 # argl: argument list suitable for appending at the end of other args 663 if args: 664 argl = ', ' + args 665 else: 666 argl = args 667 668 output = StringIO() 669 670 output.write('def %s(%s):\n' % (_mkname(path, name, _CHK), args)) 671 output.write('\timport %s\n' % MODULE) 672 673 classname = '.'.join([c.__name__ for c in path[1:]]) 674 675 if isinstance(obj, FunctionType): 676 if is_public: 677 chkname, chkargs = 'call_public_function', '__assert_inv, ' 678 else: 679 chkname, chkargs = 'call_private_function', '' 680 else: 681 if not is_public: 682 chkname = 'call_private_method' 683 elif name == '__init__': 684 chkname = 'call_constructor' 685 elif name == '__del__': 686 chkname = 'call_destructor' 687 else: 688 chkname = 'call_public_method' 689 chkargs = classname + ', ' 690 691 # ttw008 partial contract enforcement... 692 if checklevel == CHECK_ALL: 693 suffix = '_all' 694 else: 695 suffix = '_pre' 696 # ...ttw008 697 698 output.write('\treturn %s.%s%s(%s' % (MODULE, chkname, suffix, 699 chkargs)) 700 if classname: 701 output.write(classname) 702 output.write('.') 703 output.write(name) 704 output.write(argl) 705 output.write(')\n') 706 707 newfunc = _define(_mkname(path, name, _CHK), output.getvalue(), 708 path[0]) 709 710 # if there are default arguments 711 if argspec[3]: 712 # install the default arguments... don't try to put them into 713 # our printed function definition, above, as they have already 714 # been evaluated. 715 newfunc = new.function(newfunc.func_code, newfunc.func_globals, 716 newfunc.func_name, argspec[3]) 717 718 setattr(newfunc, PREFIX + _ORIG, getattr(func, PREFIX + _ORIG, func)) 719 720 # write preconditions checker 721 if contracts[0][2]: 722 pre = _define_checker(_mkname(path, name, contracts[0][0]), args, 723 contracts[0], newpath) 724 725 delattr(path[0], pre.func_name) 726 setattr(newfunc, PREFIX + contracts[0][0], pre) 727 728 if checklevel == CHECK_ALL: 729 # write __old__ saver 730 if contracts[1][1]: 731 saver = _define_saver(_mkname(path, name, _SAVE), OLD + argl, 732 contracts[1][1], path[0]) 733 delattr(path[0], saver.func_name) 734 setattr(newfunc, PREFIX + _SAVE, saver) 735 736 # write postconditions checker 737 if contracts[1][2]: 738 post = _define_checker(_mkname(path, name, contracts[1][0]), 739 OLD + ', ' + RETURN + argl, 740 contracts[1], newpath) 741 742 delattr(path[0], post.func_name) 743 setattr(newfunc, PREFIX + contracts[1][0], post) 744 745 newname = newfunc.func_name 746 newfunc.__doc__ = obj.__doc__ 747 748 if isclass(path[-1]) and isinstance(obj, FunctionType): 749 # static method 750 newfunc = staticmethod(newfunc) 751 752 setattr(path[-1], name, newfunc) 753 754 if name != newname or len(path) > 1: 755 delattr(path[0], newname)
756 # end _install_wrapper 757
758 -def isclass(obj):
759 return isinstance(obj, TypeType) or isinstance(obj, ClassType)
760
761 -def isstring(obj):
762 return isinstance(obj, StringType) or isinstance(obj, UnicodeType)
763
764 -def _define_checker(name, args, contract, path):
765 """Define a function that does contract assertion checking. 766 767 args is a string argument declaration (ex: 'a, b, c = 1, *va, **ka') 768 contract is an element of the contracts list returned by parse_docstring 769 module is the containing module (not parent class) 770 771 Returns the newly-defined function. 772 773 pre:: 774 isstring(name) 775 isstring(args) 776 contract[0] in _CONTRACTS 777 len(contract[2]) > 0 778 post:: 779 isinstance(__return__, FunctionType) 780 __return__.__name__ == name 781 """ 782 output = StringIO() 783 output.write('def %s(%s):\n' % (name, args)) 784 # ttw001... raise new exception classes 785 ex = _EXCEPTIONS.get(contract[0], 'ContractViolationError') 786 output.write('\tfrom %s import forall, exists, implies, %s\n' % \ 787 (MODULE, ex)) 788 loc = '.'.join([x.__name__ for x in path]) 789 for c in contract[2]: 790 output.write('\tif not (') 791 output.write(c[0]) 792 output.write('): raise %s("%s", %u)\n' % (ex, loc, c[1])) 793 # ...ttw001 794 return _define(name, output.getvalue(), path[0])
795
796 -def _define_saver(name, args, decls, module):
797 """Create a function that saves values into an __old__ variable. 798 799 pre:: decls 800 post:: isinstance(__return__, FunctionType) 801 """ 802 output = StringIO() 803 output.write('def %s(%s):\n' % (name, args)) 804 output.write('\timport %s, copy\n' % MODULE) 805 806 _save_decls(output, '', _decltodict(decls)) 807 808 return _define(name, output.getvalue(), module)
809
810 -def _define(name, text, module):
811 #print text 812 exec text in vars(module) 813 return getattr(module, name)
814
815 -def _format_args( (arguments, rest, keywords, default_values) ):
816 """Formats an argument desc into a string suitable for both a function/ 817 method declaration or a function call. 818 819 This does *not* handle default arguments. Default arguments are already 820 evaluated... use new.function() to create a function with pre-evaluated 821 default arguments. 822 823 Examples: 824 >>> def foo(a, (b, c), d = 1, e = 2, *va, **ka): 825 ... pass 826 >>> a = getargspec(foo) 827 >>> a 828 (['a', '(b, c)', 'd', 'e'], 'va', 'ka', (1, 2)) 829 >>> _format_args(a) 830 'a, (b, c), d, e, *va, **ka' 831 832 pre:: 833 isinstance(arguments, list) 834 rest is None or isstring(rest) 835 keywords is None or isstring(keywords) 836 post[]:: True 837 """ 838 if rest is not None: arguments = arguments + ['*' + rest] 839 if keywords is not None: arguments = arguments + ['**' + keywords] 840 return ', '.join(arguments)
841 842 CO_VARARGS, CO_VARKEYWORDS = 4, 8 843 844 try: 845 import inspect 846
847 - def _format_arg(a):
848 """Convert an argument list into a tuple string. 849 850 >>> _format_arg(['a', 'b', 'c']) 851 '(a, b, c)' 852 >>> _format_arg(['a', ['b', 'c', 'd']]) 853 '(a, (b, c, d))' 854 >>> _format_arg(['a']) 855 '(a,)' 856 >>> _format_arg('a') 857 'a' 858 """ 859 if isinstance(a, ListType): 860 if len(a) == 1: 861 return '(' + _format_arg(a[0]) + ',)' 862 else: 863 return '(' + ', '.join([_format_arg(z) for z in a]) + ')' 864 else: 865 return a
866
867 - def _getargs(function):
868 t = inspect.getargspec(function) 869 return ([_format_arg(a) for a in t[0]], t[1], t[2], t[3])
870 871 getmro = inspect.getmro 872 873 except ImportError: 874 # inspect module not available, on say Jython 2.1
875 - def _getargs(function):
876 code = function.func_code 877 i = code.co_argcount 878 args = list(code.co_varnames[:i]) 879 if code.co_flags & CO_VARARGS: 880 va = code.co_varnames[i] 881 i += 1 882 else: 883 va = None 884 if code.co_flags & CO_VARKEYWORDS: 885 ka = code.co_varnames[i] 886 i += 1 887 else: 888 ka = None 889 return (args, va, ka, function.func_defaults)
890
891 - def _searchbases(cls, accum):
892 # Simulate the "classic class" search order. 893 if cls in accum: 894 return 895 accum.append(cls) 896 for base in cls.__bases__: 897 _searchbases(base, accum)
898
899 - def getmro(cls):
900 """Return tuple of base classes (including cls) in method resolution 901 order.""" 902 if hasattr(cls, "__mro__"): 903 return cls.__mro__ 904 else: 905 result = [] 906 _searchbases(cls, result) 907 return tuple(result)
908
909 -def getargspec(function):
910 """Get argument information about a function. 911 912 Returns a tuple (args, varargs, keywordvarargs, defaults) where 913 args is a list of strings, varargs is None or the name of the 914 *va argument, keywordvarargs is None or the name of the **ka 915 argument, and defaults is a list of default values. 916 917 This function is different from the Python-provided 918 inspect.getargspec in that 1) tuple arguments are returned as 919 a string grouping '(a, b, c)' instead of broken out "['a', 'b', 'c']" 920 and 2) it works in Jython, which doesn't support inspect (yet). 921 922 >>> getargspec(lambda a, b: a * b) 923 (['a', 'b'], None, None, None) 924 >>> getargspec(lambda a, (b, c, d) = (5, 6, 7), *va, **ka: a * b) 925 (['a', '(b, c, d)'], 'va', 'ka', ((5, 6, 7),)) 926 927 pre:: 928 isinstance(function, FunctionType) 929 post[]:: 930 # tuple of form (args, va, ka, defaults) 931 isinstance(__return__, TupleType) and len(__return__) == 4 932 # args is a list of strings 933 isinstance(__return__[0], ListType) 934 forall(__return__[0], isstring) 935 # va is None or a string 936 __return__[1] is None or isstring(__return__[1]) 937 # ka is None or a string 938 __return__[2] is None or isstring(__return__[2]) 939 # defaults is None or a tuple 940 __return__[3] is None or isinstance(__return__[3], TupleType) 941 """ 942 return _getargs(function)
943
944 -def _mkname(path, *va):
945 """Define a name combining a path and arbitrary strings. 946 947 pre:: 948 isinstance(path[0], ModuleType) 949 forall(path[1:], isclass) 950 951 Examples: 952 >>> import contract 953 >>> _mkname([contract], 'test') 954 '__assert_test' 955 >>> class foo: 956 ... pass 957 >>> _mkname([contract, foo], 'func', 'pre') 958 '__assert_foo_func_pre' 959 """ 960 return PREFIX + '_'.join([c.__name__ for c in path[1:]] + list(va))
961
962 -def _save_decls(output, name, d):
963 """Recursively output a dictionary into a set of 'old' assignments. 964 965 The dictionary d is a tree of variable declarations. So, for example, 966 the declaration [self, self.buf, self.obj.a] would turn into the dict 967 {'self': {'buf': {}, 'obj': {'a': {}}}}, and would get output as 968 __old__.self = contract._holder() 969 __old__.self.buf = copy.copy(self.buf) 970 __old__.self.obj = contract._holder() 971 __old__.self.obj.a = copy.copy(self.obj.a) 972 """ 973 for k, v in d.items(): 974 n = name + k 975 output.write('\t%s.%s = ' % (OLD, n)) 976 if v == {}: 977 output.write('copy.copy(%s)\n' % n) 978 else: 979 output.write('%s._holder()\n' % MODULE) 980 _save_decls(output, n + '.', v)
981
982 -def _decltodict(l):
983 """Converts a list of list of names into a hierarchy of dictionaries. 984 985 Examples: 986 >>> d = _decltodict([['self', 'buf'], 987 ... ['self', 'item', 'a'], 988 ... ['self', 'item', 'b']]) 989 >>> d == {'self': {'buf': {}, 'item': {'a': {}, 'b': {}}}} 990 1 991 """ 992 result = {} 993 for i in l: 994 d = result 995 for n in i: 996 # n in d ? d[n] : d[n] = {} 997 d = d.setdefault(n, {}) 998 return result
999 1000 # 1001 # ...part 2 1002 ############################################################################### 1003 1004 ############################################################################### 1005 # Part 3... Run-time support for call checking 1006 # 1007 1008 ##################################### 1009 # ttw001 add new assertion classes...
1010 -class ContractViolationError(AssertionError):
1011 pass
1012
1013 -class PreconditionViolationError(ContractViolationError):
1014 pass
1015
1016 -class PostconditionViolationError(ContractViolationError):
1017 pass
1018
1019 -class InvariantViolationError(ContractViolationError):
1020 pass
1021 # ...ttw001 1022 ##################################### 1023 1024 # ttw006 correctly weaken pre-conditions...
1025 -class InvalidPreconditionError(ContractViolationError):
1026 """Method pre-conditions can only weaken overridden methods' 1027 preconditions. 1028 """ 1029 pass
1030 # ...ttw006 1031 1032 # ttw008 partial contract enforcement 1033 1034 # these are 'drivers': functions which check contracts and call the 1035 # original wrapped functions. Each driver has two variants: one for 1036 # CHECK_PRECONDITIONS and one for CHECK_ALL: the CHECK_ALL checks 1037 # preconditions and invariants on entry, and postconditions and 1038 # invariants on exit. The CHECK_PRECONDITIONS only checks pre- 1039 # conditions and invariants on entry. There needs to be a driver 1040 # for public and private functions, public and private methods, and 1041 # constructors and destructors. Hence: 1042 # call_public_function_pre/call_public_function_all 1043 # checks module invariants and function pre[post] conditions 1044 # call_private_function_pre/call_private_function_all 1045 # checks function pre[post] conditions 1046 # call_public_method_pre/call_public_method_all 1047 # checks class/type invariants and method pre[post] conditions 1048 # call_private_method_pre/call_private_method_all 1049 # checks method pre[post] conditions 1050 # call_constructor_pre/call_constructor_all 1051 # checks method pre-conditions [type/class invariants and post- 1052 # conditions on exit] 1053 # call_destructor_pre/call_destructor_all 1054 # checks method pre-conditions and class/type invariants on entry. 1055
1056 -def call_public_function_all(inv, func, *va, **ka):
1057 """Check the invocation of a public function or static method. 1058 1059 Checks module invariants on entry and exit. Checks any 1060 pre-conditions and post-conditions. 1061 """ 1062 inv() 1063 try: 1064 return _call_all([func], func, va, ka) 1065 finally: 1066 inv()
1067
1068 -def call_public_function_pre(inv, func, *va, **ka):
1069 """Check the invocation of a public function or static method. 1070 1071 Checks module invariants on entry. Checks any pre-conditions. 1072 """ 1073 inv() 1074 return _call_pre([func], func, va, ka)
1075
1076 -def call_private_function_all(func, *va, **ka):
1077 """Check the invocation of a private function or static method. 1078 1079 Only checks pre-conditions and post-conditions. 1080 """ 1081 return _call_all([func], func, va, ka)
1082
1083 -def call_private_function_pre(func, *va, **ka):
1084 """Check the invocation of a private function or static method. 1085 1086 Only checks pre-conditions 1087 """ 1088 return _call_pre([func], func, va, ka)
1089
1090 -def call_public_method_all(cls, method, *va, **ka):
1091 """Check the invocation of a public method. 1092 1093 Check this class and all super-classes invariants on entry and 1094 exit. Checks all post-conditions of this method and all over- 1095 ridden method. 1096 """ 1097 mro = getmro(cls) 1098 _check_class_invariants(mro, va[0]) 1099 try: 1100 return _method_call_all(mro, method, va, ka) 1101 finally: 1102 _check_class_invariants(mro, va[0])
1103
1104 -def call_public_method_pre(cls, method, *va, **ka):
1105 """Check the invocation of a public method. 1106 1107 Check this class and all super-classes invariants on entry. 1108 exit. 1109 """ 1110 mro = getmro(cls) 1111 _check_class_invariants(mro, va[0]) 1112 return _method_call_pre(mro, method, va, ka)
1113
1114 -def call_constructor_all(cls, method, *va, **ka):
1115 """Check the invocation of an __init__ constructor. 1116 1117 Checks pre-conditions and post-conditions, and only checks 1118 invariants on successful completion. 1119 """ 1120 # ttw003 invariants mustn't be checked if constructor raises exception... 1121 mro = getmro(cls) 1122 result = _method_call_all(mro, method, va, ka) 1123 _check_class_invariants(mro, va[0]) 1124 return result
1125 # ...ttw003 1126
1127 -def call_constructor_pre(cls, method, *va, **ka):
1128 """Check the invocation of an __init__ constructor. 1129 1130 Checks pre-conditions and post-conditions, and only checks 1131 invariants on successful completion. 1132 """ 1133 # ttw003 invariants mustn't be checked if constructor raises exception... 1134 mro = getmro(cls) 1135 result = _method_call_pre(mro, method, va, ka) 1136 _check_class_invariants(mro, va[0]) 1137 return result
1138 # ...ttw003 1139
1140 -def call_destructor_all(cls, method, *va, **ka):
1141 """Check the invocation of a __del__ destructor. 1142 1143 Checks pre-conditions and post-conditions, and only checks 1144 invariants on entry. 1145 """ 1146 mro = getmro(cls) 1147 _check_class_invariants(mro, va[0]) 1148 return _method_call_all(mro, method, va, ka)
1149
1150 -def call_destructor_pre(cls, method, *va, **ka):
1151 """Check the invocation of a __del__ destructor. 1152 1153 Checks pre-conditions and post-conditions, and only checks 1154 invariants on entry. 1155 """ 1156 mro = getmro(cls) 1157 _check_class_invariants(mro, va[0]) 1158 return _method_call_pre(mro, method, va, ka)
1159
1160 -def call_private_method_all(cls, method, *va, **ka):
1161 """Check the invocation of a private method call. 1162 1163 Checks pre-conditions and post-conditions. 1164 """ 1165 return _method_call_all(getmro(cls), method, va, ka)
1166
1167 -def call_private_method_pre(cls, method, *va, **ka):
1168 """Check the invocation of a private method call. 1169 1170 Checks pre-conditions. 1171 """ 1172 return _method_call_pre(getmro(cls), method, va, ka)
1173
1174 -def _check_class_invariants(mro, instance):
1175 """Checks class invariants on an instance. 1176 1177 mro - list of classes in method-resolution order 1178 instance - object to test 1179 1180 pre:: 1181 # instance must be an instance of each class in mro 1182 forall(mro, lambda x: isinstance(instance, x)) 1183 """ 1184 for c in mro: 1185 try: 1186 p = c.__assert_inv 1187 except AttributeError: 1188 pass 1189 else: 1190 p(instance)
1191
1192 -def _method_call_all(mro, method, va, ka):
1193 """Check the invocation of a method. 1194 1195 mro -- list/tuple of class objects in method resolution order 1196 """ 1197 # NO CONTRACTS... recursion 1198 assert isinstance(method, MethodType) 1199 1200 func = method.im_func 1201 name = func.__assert_orig.__name__ 1202 # list of all method functions with name 1203 a = [getattr(c, name).im_func for c in mro if _has_method(c, name)] 1204 1205 return _call_all(a, func, va, ka)
1206
1207 -def _method_call_pre(mro, method, va, ka):
1208 """Check the invocation of a method. 1209 1210 mro -- list/tuple of class objects in method resolution order 1211 """ 1212 # NO CONTRACTS... recursion 1213 func = method.im_func 1214 name = func.__assert_orig.__name__ 1215 # list of all method functions with name 1216 a = [getattr(c, name).im_func for c in mro if _has_method(c, name)] 1217 1218 return _call_pre(a, func, va, ka)
1219
1220 -def _call_all(a, func, va, ka):
1221 _check_preconditions(a, func, va, ka) 1222 1223 # save old values 1224 old = _holder() 1225 for f in a: 1226 try: 1227 p = f.__assert_save 1228 except AttributeError: 1229 pass 1230 else: 1231 p(old, *va, **ka) 1232 1233 result = func.__assert_orig(*va, **ka) 1234 for f in a: 1235 # check post-conditions 1236 try: 1237 p = f.__assert_post 1238 except AttributeError: 1239 pass 1240 else: 1241 p(old, result, *va, **ka) 1242 1243 return result
1244
1245 -def _call_pre(a, func, va, ka):
1246 _check_preconditions(a, func, va, ka) 1247 return func.__assert_orig(*va, **ka)
1248
1249 -def _check_preconditions(a, func, va, ka):
1250 # ttw006: correctly weaken pre-conditions... 1251 try: 1252 p = func.__assert_pre 1253 except AttributeError: 1254 # no pre-conditions 1255 pass 1256 else: 1257 try: 1258 p(*va, **ka) 1259 except PreconditionViolationError, args: 1260 # if the pre-conditions fail, *all* super-preconditions 1261 # must fail too, otherwise 1262 for f in a: 1263 if f is not func: 1264 try: 1265 p = f.__assert_pre 1266 except AttributeError: 1267 pass 1268 else: 1269 p(*va, **ka) 1270 raise InvalidPreconditionError(args) 1271 raise
1272 # ...ttw006 1273
1274 -def _has_method(cls, name):
1275 """Test if a class has a named method. 1276 1277 pre:: 1278 isclass(cls) 1279 isstring(name) 1280 post:: __return__ == (hasattr(cls, name) and \ 1281 isinstance(getattr(cls, name), MethodType)) 1282 """ 1283 return isinstance(getattr(cls, name, None), MethodType)
1284
1285 -def __assert_inv():
1286 """Empty invariant assertions 1287 """ 1288 pass
1289 1290 # ...part 3 1291 ############################################################################### 1292 1293 ############################################################################### 1294 # Part 4 -- Declarations for use within contract expressions... 1295 #
1296 -def forall(a, fn = bool):
1297 """Checks that all elements in a sequence are true. 1298 1299 Returns True(1) if all elements are true. Return False(0) otherwise. 1300 1301 Examples: 1302 >>> forall([True, True, True]) 1303 1 1304 >>> forall( () ) 1305 1 1306 >>> forall([True, True, False, True]) 1307 0 1308 """ 1309 for i in a: 1310 if not fn(i): 1311 return False 1312 return True
1313
1314 -def exists(a, fn = bool):
1315 """Checks that at least one element in a sequence is true. 1316 1317 Returns True(1) if at least one element is true. Return False(0) 1318 otherwise. 1319 1320 Examples: 1321 >>> exists([False, False, True]) 1322 1 1323 >>> exists([]) 1324 0 1325 >>> exists([False, 0, '', []]) 1326 0 1327 """ 1328 for i in a: 1329 if fn(i): 1330 return True 1331 return False
1332
1333 -def implies(test, then_val, else_val = True):
1334 """Logical implication. 1335 1336 implies(x, y) should be read 'x implies y' or 'if x then y' 1337 implies(x, a, b) should be read 'if x then a else b' 1338 1339 Examples: 1340 >>> implies(False, False) 1341 1 1342 >>> implies(False, True) 1343 1 1344 >>> implies(True, False) 1345 0 1346 >>> implies(True, True) 1347 1 1348 """ 1349 if test: 1350 return then_val 1351 else: 1352 return else_val
1353
1354 -class _holder:
1355 """Placeholder for arbitrary 'old' values. 1356 """ 1357 pass
1358 1359 # 1360 # ...part 4 1361 ############################################################################### 1362 1363 __test__ = { 1364 '_ispublic': _ispublic, '_get_members': _get_members, 1365 '_decltodict': _decltodict, '_read_block': _read_block, 1366 '_format_args': _format_args, '_mkname': _mkname} 1367 1368 if __name__ == '__main__': 1369 import doctest, contract 1370 #contract.checkmod(contract) 1371 doctest.testmod(contract) 1372