Package qm :: Package test :: Package classes :: Module python
[hide private]
[frames] | no frames]

Source Code for Module qm.test.classes.python

  1  ######################################################################## 
  2  # 
  3  # File:   python.py 
  4  # Author: Alex Samuel 
  5  # Date:   2001-04-03 
  6  # 
  7  # Contents: 
  8  #   Test classes for tests written in Python. 
  9  # 
 10  # Copyright (c) 2001, 2002 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  """Test classes for tests written in Python.""" 
 17   
 18  ######################################################################## 
 19  # imports 
 20  ######################################################################## 
 21   
 22  import qm 
 23  import qm.fields 
 24  import qm.test.base 
 25  from   qm.test.result import * 
 26  from   qm.test.test import * 
 27  import string 
 28  import sys 
 29  import types 
 30   
 31  ######################################################################## 
 32  # classes 
 33  ######################################################################## 
 34   
35 -class ExecTest(Test):
36 """Check that a Python expression evaluates to true. 37 38 An 'ExecTest' test consists of Python source code together with 39 an (optional) Python expression. First the Python code is 40 executed. If it throws an (uncaught) exception the test fails. 41 If the optional expression is present, it is then evaluated. If it 42 evaluates to false, the test fails. Otherwise, the test passes.""" 43 44 arguments = [ 45 qm.fields.TextField( 46 name="source", 47 title="Python Source Code", 48 description="""The source code. 49 50 This code may contain class definitions, function 51 definitions, statements, and so forth. If this code 52 throws an uncaught exception, the test will fail.""", 53 verbatim="true", 54 multiline="true", 55 default_value="pass" 56 ), 57 58 qm.fields.TextField( 59 name="expression", 60 title="Python Expression", 61 description="""The expression to evaluate. 62 63 If the expression evaluates to true, the test will pass, 64 unless the source code above throws an uncaught exception. 65 66 If this field is left blank, it is treated as an expression 67 that is always true.""", 68 verbatim="true", 69 multiline="true", 70 default_value="1" 71 ) 72 ] 73 74
75 - def Run(self, context, result):
76 77 # Adjust the source code. Make sure the source ends with a 78 # newline. A user is likely to be confused by the error message 79 # if it's missing. 80 if not self.source: 81 self.source = "\n" 82 elif self.source[-1] != "\n": 83 self.source += "\n" 84 global_namespace, local_namespace = make_namespaces(context) 85 # Execute the source code. 86 try: 87 exec self.source in global_namespace, local_namespace 88 except: 89 # The source raised an unhandled exception, so the test 90 # fails 91 result.NoteException(cause="Exception executing source.") 92 else: 93 # The source code executed OK. Was an additional expression 94 # provided? 95 if self.expression is not None: 96 # Yes; evaluate it. 97 try: 98 value = eval(self.expression, 99 global_namespace, local_namespace) 100 except: 101 # Oops, an exception while evaluating the 102 # expression. The test fails. 103 result.NoteException(cause= 104 "Exception evaluating expression.") 105 else: 106 # We evaluated the expression. The test passes iff 107 # the expression's value is boolean true. 108 if not value: 109 result.Fail("Expression evaluates to false.", 110 { "ExecTest.expr" : self.expression, 111 "ExecTest.value" : repr(value) }) 112 else: 113 # No expression provided; if we got this far, the test 114 # passes. 115 pass
116 117
118 -class BaseExceptionTest(Test):
119 """Base class for tests of exceptions.""" 120 121 arguments = [ 122 qm.fields.TextField( 123 name="source", 124 title="Python Source Code", 125 description="""The source code. 126 127 This code may contain class definitions, function 128 definitions, statements, and so forth.""", 129 verbatim="true", 130 multiline="true", 131 default_value="pass" 132 ), 133 134 qm.fields.TextField( 135 name="exception_argument", 136 title="Exception Argument", 137 description="""The expected value of the exception. 138 139 This value is a Python expression which should evaluate 140 to the same value as the exception raised. 141 142 If this field is left blank, the value of the exception is 143 ignored.""", 144 default_value="" 145 ) 146 ] 147 148
149 - def Run(self, context, result):
150 151 # Adjust the exception argument. 152 if string.strip(self.exception_argument) != "": 153 self.exception_argument = eval(self.exception_argument, {}, {}) 154 self.has_exception_argument = 1 155 else: 156 self.has_exception_argument = 0 157 158 global_namespace, local_namespace = make_namespaces(context) 159 try: 160 # Execute the test code. 161 exec self.source in global_namespace, local_namespace 162 except: 163 exc_info = sys.exc_info() 164 # Check the exception argument. 165 self.CheckArgument(exc_info, result) 166 if result.GetOutcome() != Result.PASS: 167 return 168 # Check that the exception itself is OK. 169 self.MakeResult(exc_info, result) 170 else: 171 # The test code didn't raise an exception. 172 result.Fail(qm.message("test did not raise"))
173 174
175 - def CheckArgument(self, exc_info, result):
176 """Check that the exception argument matches expectations. 177 178 'result' -- The result object for this test.""" 179 180 # Was an expected exception argument specified? 181 if self.has_exception_argument: 182 # Yes. Extract the exception argument. 183 argument = exc_info[1] 184 if cmp(argument, self.exception_argument): 185 cause = qm.message("test raised wrong argument") 186 result.Fail(cause, 187 { "BaseExceptionTest.type" : 188 str(exc_info[0]), 189 "BaseExceptionTest.argument" : 190 repr(argument) })
191 192
193 - def MakeResult(self, exc_info, result):
194 """Check the exception in 'exc_info' and construct the result. 195 196 'result' -- The result object for this test.""" 197 198 pass
199 200 201
202 -class ExceptionTest(BaseExceptionTest):
203 """Check that the specified Python code raises an exception. 204 205 An 'ExceptionTest' checks that the specified Python code raises a 206 particular exception. The test passes if the exception is an 207 instance of the expected class and (optionally) if its value matches 208 the expected value. If the code fails to raise an exception, the 209 test fails.""" 210 211 arguments = [ 212 qm.fields.TextField( 213 name="exception_class", 214 title="Exception Class", 215 description="""The expected type of the exception. 216 217 This value is the name of a Python class. If the 218 exception raised is not an instance of this class, the 219 test fails.""", 220 default_value="Exception" 221 ) 222 ] 223 224
225 - def MakeResult(self, exc_info, result):
226 # Make sure the exception is an object. 227 if not type(exc_info[0]) in [types.ClassType, type]: 228 result.Fail(qm.message("test raised non-object", 229 exc_type=str(type(exc_info[0])))) 230 # Make sure it's an instance of the right class. 231 exception_class_name = exc_info[0].__name__ 232 if exception_class_name != self.exception_class: 233 cause = qm.message("test raised wrong class", 234 class_name=exception_class_name) 235 result.Fail(cause=cause)
236 237
238 - def CheckArgument(self, exc_info, result):
239 """Check that the exception argument matches expectations. 240 241 'result' -- The result object for this test.""" 242 243 # Was an expected argument specified? 244 if self.has_exception_argument: 245 # Extract the actual argument from the exception object. 246 try: 247 argument = exc_info[1].args 248 except: 249 # If the "args" were not available, then the exception 250 # object does not use the documented interface given 251 # for Exception. 252 result.Fail("Exception object does not provide access " 253 "to arguments provided to 'raise'", 254 { "ExceptionTest.type" : str(exc_info[0]) }) 255 return 256 257 # Now the expected argument. 258 expected_argument = self.exception_argument 259 # Python wraps the arguments to class exceptions in strange 260 # ways, so wrap the expected argument similarly. A 'None' 261 # argument is represented by an empty tuple. 262 if expected_argument is None: 263 expected_argument = () 264 # Tuple arguments are unmodified. 265 elif type(expected_argument) is types.TupleType: 266 pass 267 # A non-tuple argument is wrapped in a tuple. 268 else: 269 expected_argument = (expected_argument, ) 270 271 # Compare the actual argument to the expectation. 272 if cmp(expected_argument, argument) != 0: 273 # We got a different argument. The test fails. 274 cause = qm.message("test raised wrong argument") 275 result.Fail(cause, 276 { "ExceptionTest.type" : str(exc_info[0]), 277 "ExceptionTest.argument" : repr(argument) })
278 279 280
281 -class StringExceptionTest(BaseExceptionTest):
282 """Check that the specified Python code raises a string exception. 283 284 A 'StringExceptionTest' checks that the specified code throws 285 an exception. The exception must be a string and must have 286 the expected value.""" 287 288 arguments = [ 289 qm.fields.TextField( 290 name="exception_text", 291 title="Exception Text", 292 description="The expected exception string.", 293 default_value="exception" 294 ) 295 ] 296 297
298 - def MakeResult(self, exc_info, result):
299 # Make sure the exception is an object. 300 if not type(exc_info[0]) is types.StringType: 301 result.Fail(qm.message("test raised non-string", 302 exc_type=str(type(exc_info[0])))) 303 # Make sure it's the right string. 304 if exc_info[0] != self.exception_text: 305 result.Fail(qm.message("test raised wrong string", 306 text=exc_info[0]))
307 308 309 310 ######################################################################## 311 # functions 312 ######################################################################## 313
314 -def make_namespaces(context):
315 """Construct namespaces for eval/exec of Python test code. 316 317 'context' -- The test context. 318 319 returns -- A pair '(global_namespace, local_namespace)' of maps.""" 320 321 # The global namespace contains only the context object. 322 global_namespace = { 323 "context": context, 324 } 325 # The local namespace is empty. 326 local_namespace = { 327 } 328 # FIXME: As of 2005-03-21, all python versions I tested with 329 # contain a bug that makes non-trivial code fail when 330 # global_namespace != local_namespace. See bug #1167300 331 # 332 # Meanwhile, we use the global namespace for both, which 333 # yields the same as when calling 'exec source in globals()' 334 335 #return global_namespace, local_namespace 336 return global_namespace, global_namespace
337 338 339 ######################################################################## 340 # Local Variables: 341 # mode: python 342 # indent-tabs-mode: nil 343 # fill-column: 72 344 # End: 345