1#!/usr/bin/env python 2############################################################################ 3# Joshua R. Boverhof, LBNL 4# See LBNLCopyright for copyright notice! 5########################################################################### 6from compiler.ast import Module 7import StringIO, copy, getopt 8import os, sys, unittest, urlparse, signal, time, warnings, subprocess 9from ConfigParser import ConfigParser, NoSectionError, NoOptionError 10from ZSI.wstools.TimeoutSocket import TimeoutError 11from ZSI.generate import commands 12 13"""Global Variables: 14 CONFIG_FILE -- configuration file 15 CONFIG_PARSER -- ConfigParser instance 16 DOCUMENT -- test section variable, specifying document style. 17 LITERAL -- test section variable, specifying literal encodings. 18 BROKE -- test section variable, specifying broken test. 19 TESTS -- test section variable, whitespace separated list of modules. 20 SECTION_CONFIGURATION -- configuration section, turn on/off debuggging. 21 TRACEFILE -- file class instance. 22 TOPDIR -- current working directory 23 MODULEDIR -- stubs directory 24 PORT -- port of local container 25 HOST -- address of local container 26 SECTION_SERVERS -- services to be tested, values are paths to executables. 27""" 28CONFIG_FILE = 'config.txt' 29CONFIG_PARSER = ConfigParser() 30DOCUMENT = 'document' 31LITERAL = 'literal' 32BROKE = 'broke' 33TESTS = 'tests' 34SECTION_CONFIGURATION = 'configuration' 35SECTION_DISPATCH = 'dispatch' 36TRACEFILE = sys.stdout 37TOPDIR = os.getcwd() 38MODULEDIR = os.path.join(TOPDIR, 'stubs') 39SECTION_SERVERS = 'servers' 40 41CONFIG_PARSER.read(CONFIG_FILE) 42 43DEBUG = CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'debug') 44SKIP = CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'skip') 45TWISTED = CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'twisted') 46LAZY = CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'lazy') 47OUTPUT = CONFIG_PARSER.get(SECTION_CONFIGURATION, 'output') or sys.stdout 48 49if DEBUG: 50 from ZSI.wstools.logging import setBasicLoggerDEBUG 51 setBasicLoggerDEBUG() 52 53sys.path.append('%s/%s' %(os.getcwd(), 'stubs')) 54ENVIRON = copy.copy(os.environ) 55ENVIRON['PYTHONPATH'] = ENVIRON.get('PYTHONPATH', '') + ':' + MODULEDIR 56 57 58def _SimpleMain(): 59 """Gets tests to run from configuration file. 60 """ 61 unittest.TestProgram(defaultTest="all") 62main = _SimpleMain 63 64 65def _TwistedMain(): 66 """Gets tests to run from configuration file. 67 """ 68 from twisted.internet import reactor 69 reactor.callWhenRunning(_TwistedTestProgram, defaultTest="all") 70 reactor.run(installSignalHandlers=0) 71if TWISTED: main = _TwistedMain 72 73 74def _LaunchContainer(cmd): 75 ''' 76 Parameters: 77 cmd -- executable, sets up a ServiceContainer or ? 78 ''' 79 host = CONFIG_PARSER.get(SECTION_DISPATCH, 'host') 80 port = CONFIG_PARSER.get(SECTION_DISPATCH, 'port') 81 try: 82 process = subprocess.Popen(['python', cmd, port], env=ENVIRON) 83 except: 84 print >>sys.stderr, 'error executing: %s' %cmd 85 raise 86 time.sleep(3) 87 return process 88 89 90class _TwistedTestProgram(unittest.TestProgram): 91 92 def runTests(self): 93 from twisted.internet import reactor 94 if self.testRunner is None: 95 self.testRunner = unittest.TextTestRunner(verbosity=self.verbosity) 96 97 result = self.testRunner.run(self.test) 98 reactor.stop() 99 return result.wasSuccessful() 100 101 102 103class ConfigException(Exception): 104 """Exception thrown when configuration settings arent correct. 105 """ 106 pass 107 108class TestException(Exception): 109 """Exception thrown when test case isn't correctly set up. 110 """ 111 pass 112 113 114class ServiceTestCase(unittest.TestCase): 115 """Conventions for method names: 116 test_net* 117 -- network tests 118 119 test_local* 120 -- local tests 121 122 test_dispatch* 123 -- tests that use the a spawned local container 124 125 class attributes: Edit/Override these in the inheriting class as needed 126 out -- file descriptor to write output to 127 name -- configuration item, must be set in class. 128 url_section -- configuration section, maps a test module 129 name to an URL. 130 client_file_name -- 131 types_file_name -- 132 server_file_name -- 133 """ 134 out = OUTPUT 135 name = None 136 url_section = 'WSDL' 137 client_file_name = None 138 types_file_name = None 139 server_file_name = None 140 141 def __init__(self, methodName): 142 """ 143 parameters: 144 methodName -- 145 instance variables: 146 client_module 147 types_module 148 server_module 149 processID 150 done 151 152 """ 153 self.methodName = methodName 154 self.url = None 155 self.wsdl2py_args = [] 156 self.wsdl2dispatch_args = [] 157 self.portkwargs = {} 158 self.client_module = self.types_module = self.server_module = None 159 self.done = False 160 161 if TWISTED: 162 self.wsdl2py_args.append('--twisted') 163 164 if LAZY: 165 self.wsdl2py_args.append('--lazy') 166 167 unittest.TestCase.__init__(self, methodName) 168 169 write = lambda self, arg: self.out.write(arg) 170 171 if sys.version_info[:2] >= (2,5): 172 _exc_info = unittest.TestCase._exc_info 173 else: 174 _exc_info = unittest.TestCase._TestCase__exc_info 175 176 def __call__(self, *args, **kwds): 177 self.run(*args, **kwds) 178 179 def run(self, result=None): 180 if result is None: result = self.defaultTestResult() 181 result.startTest(self) 182 testMethod = getattr(self, self.methodName) 183 try: 184 try: 185 self.setUp() 186 except KeyboardInterrupt: 187 raise 188 except: 189 result.addError(self, self._exc_info()) 190 return 191 192 ok = False 193 try: 194 t1 = time.time() 195 pyobj = testMethod() 196 t2 = time.time() 197 ok = True 198 except self.failureException: 199 result.addFailure(self, self._exc_info()) 200 except KeyboardInterrupt: 201 raise 202 except: 203 result.addError(self, self._exc_info()) 204 205 try: 206 self.tearDown() 207 except KeyboardInterrupt: 208 raise 209 except: 210 result.addError(self, self._exc_info()) 211 ok = False 212 if ok: 213 result.addSuccess(self) 214 print>>self 215 print>>self, "|"+"-"*60 216 print>>self, "| TestCase: %s" %self.methodName 217 print>>self, "|"+"-"*20 218 print>>self, "| run time: %s ms" %((t2-t1)*1000) 219 print>>self, "| return : %s" %pyobj 220 print>>self, "|"+"-"*60 221 222 finally: 223 result.stopTest(self) 224 225 226 227 228 def getPortKWArgs(self): 229 kw = {} 230 if CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'tracefile'): 231 kw['tracefile'] = TRACEFILE 232 233 kw.update(self.portkwargs) 234 return kw 235 236 def _setUpDispatch(self): 237 """Set this test up as a dispatch test. 238 url -- 239 """ 240 host = CONFIG_PARSER.get(SECTION_DISPATCH, 'host') 241 port = CONFIG_PARSER.get(SECTION_DISPATCH, 'port') 242 path = CONFIG_PARSER.get(SECTION_DISPATCH, 'path') 243 244 scheme = 'http' 245 netloc = '%s:%s' %(host, port) 246 params = query = fragment = None 247 248 self.portkwargs['url'] = \ 249 urlparse.urlunparse((scheme,netloc,path,params,query,fragment)) 250 251 _wsdl = {} 252 def _generate(self): 253 """call the wsdl2py and wsdl2dispatch scripts and 254 automatically add the "-f" or "-u" argument. Other args 255 can be appended via the "wsdl2py_args" and "wsdl2dispatch_args" 256 instance attributes. 257 """ 258 url = self.url 259 if os.path.isfile(url): 260 url = os.path.abspath(url) 261 262 if SKIP: 263 ServiceTestCase._wsdl[url] = True 264 return 265 266 ServiceTestCase._wsdl[url] = False 267 try: 268 os.mkdir(MODULEDIR) 269 except OSError, ex: 270 pass 271 272 os.chdir(MODULEDIR) 273 if MODULEDIR not in sys.path: 274 sys.path.append(MODULEDIR) 275 276 try: 277 commands.wsdl2py([url] + self.wsdl2py_args) 278 ServiceTestCase._wsdl[url] = True 279 finally: 280 os.chdir(TOPDIR) 281 282 _process = None 283 _lastToDispatch = None 284 def setUp(self): 285 """Generate types and services modules once, then make them 286 available thru the *_module attributes if the *_file_name 287 attributes were specified. 288 """ 289 section = self.url_section 290 name = self.name 291 if not section or not name: 292 raise TestException, 'section(%s) or name(%s) not defined' %( 293 section, name) 294 295 if not CONFIG_PARSER.has_section(section): 296 raise TestException,\ 297 'No such section(%s) in configuration file(%s)' %( 298 self.url_section, CONFIG_FILE) 299 300 self.url = CONFIG_PARSER.get(section, name) 301 302 status = ServiceTestCase._wsdl.get(self.url) 303 if status is False: 304 self.fail('generation failed for "%s"' %self.url) 305 306 if status is None: 307 self._generate() 308 309 # Check for files 310 tfn = self.types_file_name 311 cfn = self.client_file_name 312 sfn = self.server_file_name 313 314 files = filter(lambda f: f is not None, [cfn, tfn,sfn]) 315 if None is cfn is tfn is sfn: 316 return 317 318 for n,m in map(lambda i: (i,__import__(i.split('.py')[0])), files): 319 if tfn is not None and tfn == n: 320 self.types_module = m 321 elif cfn is not None and cfn == n: 322 self.client_module = m 323 elif sfn is not None and sfn == n: 324 self.server_module = m 325 else: 326 self.fail('Unexpected module %s' %n) 327 328 # DISPATCH PORTION OF SETUP 329 if not self.methodName.startswith('test_dispatch'): 330 return 331 332 self._setUpDispatch() 333 if ServiceTestCase._process is not None: 334 return 335 336 try: 337 expath = CONFIG_PARSER.get(SECTION_DISPATCH, name) 338 except (NoSectionError, NoOptionError), ex: 339 self.fail('section dispatch has no item "%s"' %name) 340 341 if ServiceTestCase._lastToDispatch == expath: 342 return 343 344 if ServiceTestCase._lastToDispatch is not None: 345 ServiceTestCase.CleanUp() 346 347 ServiceTestCase._lastToDispatch = expath 348 ServiceTestCase._process = \ 349 _LaunchContainer(os.path.join(os.path.abspath(TOPDIR), 350 *expath.split('/'))) 351 352 353 def CleanUp(cls): 354 """call this when dispatch server is no longer needed, 355 maybe another needs to be started. Assumption that 356 a single "Suite" uses the same server, once all the 357 tests are run in that suite do a cleanup. 358 """ 359 if cls._process is None: 360 return 361 os.kill(cls._process.pid, signal.SIGKILL) 362 cls._process = None 363 CleanUp = classmethod(CleanUp) 364 365 366class ServiceTestSuite(unittest.TestSuite): 367 """A test suite is a composite test consisting of a number of TestCases. 368 369 For use, create an instance of TestSuite, then add test case instances. 370 When all tests have been added, the suite can be passed to a test 371 runner, such as TextTestRunner. It will run the individual test cases 372 in the order in which they were added, aggregating the results. When 373 subclassing, do not forget to call the base class constructor. 374 """ 375 def __init__(self, tests=()): 376 unittest.TestSuite.__init__(self, tests) 377 378 def __call__(self, result): 379 # for python2.4 380 return self.run(result) 381 382 def addTest(self, test): 383 unittest.TestSuite.addTest(self, test) 384 385 def run(self, result): 386 for test in self._tests: 387 if result.shouldStop: 388 break 389 test(result) 390 391 ServiceTestCase.CleanUp() 392 return result 393 394 395