00001
00002
00003
00004
00005
00006 from compiler.ast import Module
00007 import StringIO, copy, getopt
00008 import os, sys, unittest, urlparse, signal, time, warnings, subprocess
00009 from ConfigParser import ConfigParser, NoSectionError, NoOptionError
00010 from ZSI.wstools.TimeoutSocket import TimeoutError
00011 from ZSI.generate import commands
00012
00013 """Global Variables:
00014 CONFIG_FILE -- configuration file
00015 CONFIG_PARSER -- ConfigParser instance
00016 DOCUMENT -- test section variable, specifying document style.
00017 LITERAL -- test section variable, specifying literal encodings.
00018 BROKE -- test section variable, specifying broken test.
00019 TESTS -- test section variable, whitespace separated list of modules.
00020 SECTION_CONFIGURATION -- configuration section, turn on/off debuggging.
00021 TRACEFILE -- file class instance.
00022 TOPDIR -- current working directory
00023 MODULEDIR -- stubs directory
00024 PORT -- port of local container
00025 HOST -- address of local container
00026 SECTION_SERVERS -- services to be tested, values are paths to executables.
00027 """
00028 CONFIG_FILE = 'config.txt'
00029 CONFIG_PARSER = ConfigParser()
00030 DOCUMENT = 'document'
00031 LITERAL = 'literal'
00032 BROKE = 'broke'
00033 TESTS = 'tests'
00034 SECTION_CONFIGURATION = 'configuration'
00035 SECTION_DISPATCH = 'dispatch'
00036 TRACEFILE = sys.stdout
00037 TOPDIR = os.getcwd()
00038 MODULEDIR = os.path.join(TOPDIR, 'stubs')
00039 SECTION_SERVERS = 'servers'
00040
00041 CONFIG_PARSER.read(CONFIG_FILE)
00042
00043 DEBUG = CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'debug')
00044 SKIP = CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'skip')
00045 TWISTED = CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'twisted')
00046 LAZY = CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'lazy')
00047 OUTPUT = CONFIG_PARSER.get(SECTION_CONFIGURATION, 'output') or sys.stdout
00048
00049 if DEBUG:
00050 from ZSI.wstools.logging import setBasicLoggerDEBUG
00051 setBasicLoggerDEBUG()
00052
00053 sys.path.append('%s/%s' %(os.getcwd(), 'stubs'))
00054 ENVIRON = copy.copy(os.environ)
00055 ENVIRON['PYTHONPATH'] = ENVIRON.get('PYTHONPATH', '') + ':' + MODULEDIR
00056
00057
00058 def _SimpleMain():
00059 """Gets tests to run from configuration file.
00060 """
00061 unittest.TestProgram(defaultTest="all")
00062 main = _SimpleMain
00063
00064
00065 def _TwistedMain():
00066 """Gets tests to run from configuration file.
00067 """
00068 from twisted.internet import reactor
00069 reactor.callWhenRunning(_TwistedTestProgram, defaultTest="all")
00070 reactor.run(installSignalHandlers=0)
00071 if TWISTED: main = _TwistedMain
00072
00073
00074 def _LaunchContainer(cmd):
00075 '''
00076 Parameters:
00077 cmd -- executable, sets up a ServiceContainer or ?
00078 '''
00079 host = CONFIG_PARSER.get(SECTION_DISPATCH, 'host')
00080 port = CONFIG_PARSER.get(SECTION_DISPATCH, 'port')
00081 try:
00082 process = subprocess.Popen(['python', cmd, port], env=ENVIRON)
00083 except:
00084 print >>sys.stderr, 'error executing: %s' %cmd
00085 raise
00086 time.sleep(3)
00087 return process
00088
00089
00090 class _TwistedTestProgram(unittest.TestProgram):
00091
00092 def runTests(self):
00093 from twisted.internet import reactor
00094 if self.testRunner is None:
00095 self.testRunner = unittest.TextTestRunner(verbosity=self.verbosity)
00096
00097 result = self.testRunner.run(self.test)
00098 reactor.stop()
00099 return result.wasSuccessful()
00100
00101
00102
00103 class ConfigException(Exception):
00104 """Exception thrown when configuration settings arent correct.
00105 """
00106 pass
00107
00108 class TestException(Exception):
00109 """Exception thrown when test case isn't correctly set up.
00110 """
00111 pass
00112
00113
00114 class ServiceTestCase(unittest.TestCase):
00115 """Conventions for method names:
00116 test_net*
00117 -- network tests
00118
00119 test_local*
00120 -- local tests
00121
00122 test_dispatch*
00123 -- tests that use the a spawned local container
00124
00125 class attributes: Edit/Override these in the inheriting class as needed
00126 out -- file descriptor to write output to
00127 name -- configuration item, must be set in class.
00128 url_section -- configuration section, maps a test module
00129 name to an URL.
00130 client_file_name --
00131 types_file_name --
00132 server_file_name --
00133 """
00134 out = OUTPUT
00135 name = None
00136 url_section = 'WSDL'
00137 client_file_name = None
00138 types_file_name = None
00139 server_file_name = None
00140
00141 def __init__(self, methodName):
00142 """
00143 parameters:
00144 methodName --
00145 instance variables:
00146 client_module
00147 types_module
00148 server_module
00149 processID
00150 done
00151
00152 """
00153 self.methodName = methodName
00154 self.url = None
00155 self.wsdl2py_args = []
00156 self.wsdl2dispatch_args = []
00157 self.portkwargs = {}
00158 self.client_module = self.types_module = self.server_module = None
00159 self.done = False
00160
00161 if TWISTED:
00162 self.wsdl2py_args.append('--twisted')
00163
00164 if LAZY:
00165 self.wsdl2py_args.append('--lazy')
00166
00167 unittest.TestCase.__init__(self, methodName)
00168
00169 write = lambda self, arg: self.out.write(arg)
00170
00171 if sys.version_info[:2] >= (2,5):
00172 _exc_info = unittest.TestCase._exc_info
00173 else:
00174 _exc_info = unittest.TestCase._TestCase__exc_info
00175
00176 def __call__(self, *args, **kwds):
00177 self.run(*args, **kwds)
00178
00179 def run(self, result=None):
00180 if result is None: result = self.defaultTestResult()
00181 result.startTest(self)
00182 testMethod = getattr(self, self.methodName)
00183 try:
00184 try:
00185 self.setUp()
00186 except KeyboardInterrupt:
00187 raise
00188 except:
00189 result.addError(self, self._exc_info())
00190 return
00191
00192 ok = False
00193 try:
00194 t1 = time.time()
00195 pyobj = testMethod()
00196 t2 = time.time()
00197 ok = True
00198 except self.failureException:
00199 result.addFailure(self, self._exc_info())
00200 except KeyboardInterrupt:
00201 raise
00202 except:
00203 result.addError(self, self._exc_info())
00204
00205 try:
00206 self.tearDown()
00207 except KeyboardInterrupt:
00208 raise
00209 except:
00210 result.addError(self, self._exc_info())
00211 ok = False
00212 if ok:
00213 result.addSuccess(self)
00214 print>>self
00215 print>>self, "|"+"-"*60
00216 print>>self, "| TestCase: %s" %self.methodName
00217 print>>self, "|"+"-"*20
00218 print>>self, "| run time: %s ms" %((t2-t1)*1000)
00219 print>>self, "| return : %s" %pyobj
00220 print>>self, "|"+"-"*60
00221
00222 finally:
00223 result.stopTest(self)
00224
00225
00226
00227
00228 def getPortKWArgs(self):
00229 kw = {}
00230 if CONFIG_PARSER.getboolean(SECTION_CONFIGURATION, 'tracefile'):
00231 kw['tracefile'] = TRACEFILE
00232
00233 kw.update(self.portkwargs)
00234 return kw
00235
00236 def _setUpDispatch(self):
00237 """Set this test up as a dispatch test.
00238 url --
00239 """
00240 host = CONFIG_PARSER.get(SECTION_DISPATCH, 'host')
00241 port = CONFIG_PARSER.get(SECTION_DISPATCH, 'port')
00242 path = CONFIG_PARSER.get(SECTION_DISPATCH, 'path')
00243
00244 scheme = 'http'
00245 netloc = '%s:%s' %(host, port)
00246 params = query = fragment = None
00247
00248 self.portkwargs['url'] = \
00249 urlparse.urlunparse((scheme,netloc,path,params,query,fragment))
00250
00251 _wsdl = {}
00252 def _generate(self):
00253 """call the wsdl2py and wsdl2dispatch scripts and
00254 automatically add the "-f" or "-u" argument. Other args
00255 can be appended via the "wsdl2py_args" and "wsdl2dispatch_args"
00256 instance attributes.
00257 """
00258 url = self.url
00259 if os.path.isfile(url):
00260 url = os.path.abspath(url)
00261
00262 if SKIP:
00263 ServiceTestCase._wsdl[url] = True
00264 return
00265
00266 ServiceTestCase._wsdl[url] = False
00267 try:
00268 os.mkdir(MODULEDIR)
00269 except OSError, ex:
00270 pass
00271
00272 os.chdir(MODULEDIR)
00273 if MODULEDIR not in sys.path:
00274 sys.path.append(MODULEDIR)
00275
00276 try:
00277 commands.wsdl2py([url] + self.wsdl2py_args)
00278 ServiceTestCase._wsdl[url] = True
00279 finally:
00280 os.chdir(TOPDIR)
00281
00282 _process = None
00283 _lastToDispatch = None
00284 def setUp(self):
00285 """Generate types and services modules once, then make them
00286 available thru the *_module attributes if the *_file_name
00287 attributes were specified.
00288 """
00289 section = self.url_section
00290 name = self.name
00291 if not section or not name:
00292 raise TestException, 'section(%s) or name(%s) not defined' %(
00293 section, name)
00294
00295 if not CONFIG_PARSER.has_section(section):
00296 raise TestException,\
00297 'No such section(%s) in configuration file(%s)' %(
00298 self.url_section, CONFIG_FILE)
00299
00300 self.url = CONFIG_PARSER.get(section, name)
00301
00302 status = ServiceTestCase._wsdl.get(self.url)
00303 if status is False:
00304 self.fail('generation failed for "%s"' %self.url)
00305
00306 if status is None:
00307 self._generate()
00308
00309
00310 tfn = self.types_file_name
00311 cfn = self.client_file_name
00312 sfn = self.server_file_name
00313
00314 files = filter(lambda f: f is not None, [cfn, tfn,sfn])
00315 if None is cfn is tfn is sfn:
00316 return
00317
00318 for n,m in map(lambda i: (i,__import__(i.split('.py')[0])), files):
00319 if tfn is not None and tfn == n:
00320 self.types_module = m
00321 elif cfn is not None and cfn == n:
00322 self.client_module = m
00323 elif sfn is not None and sfn == n:
00324 self.server_module = m
00325 else:
00326 self.fail('Unexpected module %s' %n)
00327
00328
00329 if not self.methodName.startswith('test_dispatch'):
00330 return
00331
00332 self._setUpDispatch()
00333 if ServiceTestCase._process is not None:
00334 return
00335
00336 try:
00337 expath = CONFIG_PARSER.get(SECTION_DISPATCH, name)
00338 except (NoSectionError, NoOptionError), ex:
00339 self.fail('section dispatch has no item "%s"' %name)
00340
00341 if ServiceTestCase._lastToDispatch == expath:
00342 return
00343
00344 if ServiceTestCase._lastToDispatch is not None:
00345 ServiceTestCase.CleanUp()
00346
00347 ServiceTestCase._lastToDispatch = expath
00348 ServiceTestCase._process = \
00349 _LaunchContainer(os.path.join(os.path.abspath(TOPDIR),
00350 *expath.split('/')))
00351
00352
00353 def CleanUp(cls):
00354 """call this when dispatch server is no longer needed,
00355 maybe another needs to be started. Assumption that
00356 a single "Suite" uses the same server, once all the
00357 tests are run in that suite do a cleanup.
00358 """
00359 if cls._process is None:
00360 return
00361 os.kill(cls._process.pid, signal.SIGKILL)
00362 cls._process = None
00363 CleanUp = classmethod(CleanUp)
00364
00365
00366 class ServiceTestSuite(unittest.TestSuite):
00367 """A test suite is a composite test consisting of a number of TestCases.
00368
00369 For use, create an instance of TestSuite, then add test case instances.
00370 When all tests have been added, the suite can be passed to a test
00371 runner, such as TextTestRunner. It will run the individual test cases
00372 in the order in which they were added, aggregating the results. When
00373 subclassing, do not forget to call the base class constructor.
00374 """
00375 def __init__(self, tests=()):
00376 unittest.TestSuite.__init__(self, tests)
00377
00378 def __call__(self, result):
00379
00380 return self.run(result)
00381
00382 def addTest(self, test):
00383 unittest.TestSuite.addTest(self, test)
00384
00385 def run(self, result):
00386 for test in self._tests:
00387 if result.shouldStop:
00388 break
00389 test(result)
00390
00391 ServiceTestCase.CleanUp()
00392 return result
00393
00394