| 32 | | |
|---|
| 33 | | try: |
|---|
| 34 | | set |
|---|
| 35 | | except NameError: |
|---|
| 36 | | from sets import Set as set |
|---|
| 37 | | |
|---|
| 38 | | |
|---|
| 39 | | def help(testList): |
|---|
| 40 | | """Print help for test.py command-line options.""" |
|---|
| 41 | | |
|---|
| 42 | | print """CherryPy Test Program |
|---|
| 43 | | Usage: |
|---|
| 44 | | test.py -mode -cover -profile -1.1 testName1 testName2 testName... |
|---|
| 45 | | |
|---|
| 46 | | modes: wsgi, severless, native, all (default is wsgi) |
|---|
| 47 | | |
|---|
| 48 | | cover: turns on code-coverage tool |
|---|
| 49 | | |
|---|
| 50 | | profile: turns on profiling tool |
|---|
| | 32 | import getopt |
|---|
| | 33 | |
|---|
| | 34 | |
|---|
| | 35 | class TestHarness(object): |
|---|
| | 36 | |
|---|
| | 37 | """A test harness for the CherryPy framework and CherryPy applications.""" |
|---|
| | 38 | |
|---|
| | 39 | # The first server in the list is the default server. |
|---|
| | 40 | available_servers = {'serverless': (0, "Serverless", None), |
|---|
| | 41 | 'native': (1, "Native HTTP Server", |
|---|
| | 42 | "cherrypy._cphttpserver.embedded_server"), |
|---|
| | 43 | 'wsgi': (2, "Native WSGI Server", |
|---|
| | 44 | "cherrypy._cpwsgi.WSGIServer"), |
|---|
| | 45 | } |
|---|
| | 46 | default_server = "wsgi" |
|---|
| | 47 | |
|---|
| | 48 | def __init__(self, available_tests): |
|---|
| | 49 | """Constructor to populate the TestHarness instance. |
|---|
| | 50 | |
|---|
| | 51 | available_tests should be a list of module names (strings). |
|---|
| | 52 | """ |
|---|
| | 53 | self.available_tests = available_tests |
|---|
| | 54 | |
|---|
| | 55 | self.cover = False |
|---|
| | 56 | self.profile = False |
|---|
| | 57 | self.protocol = "HTTP/1.0" |
|---|
| | 58 | self.basedir = None |
|---|
| | 59 | |
|---|
| | 60 | self.servers = [] |
|---|
| | 61 | self.tests = [] |
|---|
| | 62 | |
|---|
| | 63 | def load(self, args=sys.argv[1:]): |
|---|
| | 64 | """Populate a TestHarness from sys.argv. |
|---|
| | 65 | |
|---|
| | 66 | args defaults to sys.argv[1:], but you can provide a different |
|---|
| | 67 | set of args if you like. |
|---|
| | 68 | """ |
|---|
| | 69 | |
|---|
| | 70 | longopts = ['cover', 'profile', '1.1', 'help', 'basedir=', 'all'] |
|---|
| | 71 | longopts.extend(self.available_servers) |
|---|
| | 72 | longopts.extend(self.available_tests) |
|---|
| | 73 | try: |
|---|
| | 74 | opts, args = getopt.getopt(args, "", longopts) |
|---|
| | 75 | except getopt.GetoptError: |
|---|
| | 76 | # print help information and exit |
|---|
| | 77 | self.help() |
|---|
| | 78 | sys.exit(2) |
|---|
| | 79 | |
|---|
| | 80 | self.cover = False |
|---|
| | 81 | self.profile = False |
|---|
| | 82 | self.protocol = "HTTP/1.0" |
|---|
| | 83 | self.basedir = None |
|---|
| | 84 | |
|---|
| | 85 | self.servers = [] |
|---|
| | 86 | self.tests = [] |
|---|
| | 87 | |
|---|
| | 88 | for o, a in opts: |
|---|
| | 89 | if o == '--help': |
|---|
| | 90 | self.help() |
|---|
| | 91 | sys.exit() |
|---|
| | 92 | elif o == "--cover": |
|---|
| | 93 | self.cover = True |
|---|
| | 94 | elif o == "--profile": |
|---|
| | 95 | self.profile = True |
|---|
| | 96 | elif o == "-1.1": |
|---|
| | 97 | self.protocol = "HTTP/1.1" |
|---|
| | 98 | elif o == "--basedir": |
|---|
| | 99 | self.basedir = a |
|---|
| | 100 | elif o == "--all": |
|---|
| | 101 | self.servers = self.available_servers.keys() |
|---|
| | 102 | else: |
|---|
| | 103 | o = o[2:] |
|---|
| | 104 | if o in self.available_servers and o not in self.servers: |
|---|
| | 105 | self.servers.append(o) |
|---|
| | 106 | elif o in self.available_tests and o not in self.tests: |
|---|
| | 107 | self.tests.append(o) |
|---|
| | 108 | |
|---|
| | 109 | if self.cover and self.profile: |
|---|
| | 110 | # Print error message and exit |
|---|
| | 111 | print ('Error: you cannot run the profiler and the ' |
|---|
| | 112 | 'coverage tool at the same time.') |
|---|
| | 113 | sys.exit(2) |
|---|
| | 114 | |
|---|
| | 115 | if not self.servers: |
|---|
| | 116 | self.servers = [self.default_server] |
|---|
| | 117 | |
|---|
| | 118 | if not self.tests: |
|---|
| | 119 | self.tests = self.available_tests[:] |
|---|
| | 120 | |
|---|
| | 121 | def help(self): |
|---|
| | 122 | """Print help for test.py command-line options.""" |
|---|
| | 123 | |
|---|
| | 124 | print """CherryPy Test Program |
|---|
| | 125 | Usage: |
|---|
| | 126 | test.py --server --1.1 --cover --basedir=path --profile --test |
|---|
| | 127 | """ |
|---|
| | 128 | print ' servers:' |
|---|
| | 129 | s = [(val, name) for name, val in self.available_servers.iteritems()] |
|---|
| | 130 | s.sort() |
|---|
| | 131 | for val, name in s: |
|---|
| | 132 | if name == self.default_server: |
|---|
| | 133 | print ' ', name, '(default)' |
|---|
| | 134 | else: |
|---|
| | 135 | print ' ', name |
|---|
| | 136 | |
|---|
| | 137 | print """ all (runs all servers in order) |
|---|
| 54 | | |
|---|
| 55 | | print ' tests:' |
|---|
| 56 | | for name in testList: |
|---|
| 57 | | print ' ', name |
|---|
| 58 | | |
|---|
| 59 | | |
|---|
| 60 | | class BadArgument(Exception): |
|---|
| 61 | | pass |
|---|
| 62 | | |
|---|
| 63 | | class DisplayHelp(Exception): |
|---|
| 64 | | pass |
|---|
| 65 | | |
|---|
| 66 | | |
|---|
| 67 | | class Options: |
|---|
| 68 | | |
|---|
| 69 | | """A container for test.py command-line options.""" |
|---|
| 70 | | |
|---|
| 71 | | def __init__(self, args, testList): |
|---|
| 72 | | """Constructor to populate the Options instance. |
|---|
| 73 | | |
|---|
| 74 | | args is usually sys.argv[1:]. |
|---|
| 75 | | testList should be a list of module names (strings). |
|---|
| | 146 | |
|---|
| | 147 | print ' tests:' |
|---|
| | 148 | for name in self.available_tests: |
|---|
| | 149 | print ' ', name |
|---|
| | 150 | |
|---|
| | 151 | def start_coverage(self): |
|---|
| | 152 | """Start the coverage tool. |
|---|
| | 153 | |
|---|
| | 154 | To use this feature, you need to download 'coverage.py', |
|---|
| | 155 | either Gareth Rees' original implementation: |
|---|
| | 156 | http://www.garethrees.org/2001/12/04/python-coverage/ |
|---|
| | 157 | |
|---|
| | 158 | or Ned Batchelder's enhanced version: |
|---|
| | 159 | http://www.nedbatchelder.com/code/modules/coverage.html |
|---|
| | 160 | |
|---|
| | 161 | If neither module is found in PYTHONPATH, coverage is disabled. |
|---|
| 77 | | |
|---|
| 78 | | argSet = set([arg.lower() for arg in args]) |
|---|
| 79 | | |
|---|
| 80 | | if '-help' in args: |
|---|
| 81 | | raise DisplayHelp |
|---|
| 82 | | |
|---|
| 83 | | servers = set() |
|---|
| 84 | | if '-all' in argSet: |
|---|
| 85 | | servers.update(['wsgi', 'native', 'serverless']) |
|---|
| | 163 | try: |
|---|
| | 164 | from coverage import the_coverage as coverage |
|---|
| | 165 | c = os.path.join(os.path.dirname(__file__), "../lib/coverage.cache") |
|---|
| | 166 | coverage.cache_default = c |
|---|
| | 167 | if c and os.path.exists(c): |
|---|
| | 168 | os.remove(c) |
|---|
| | 169 | coverage.start() |
|---|
| | 170 | import cherrypy |
|---|
| | 171 | cherrypy.codecoverage = True |
|---|
| | 172 | except ImportError: |
|---|
| | 173 | coverage = None |
|---|
| | 174 | self.coverage = coverage |
|---|
| | 175 | |
|---|
| | 176 | def stop_coverage(self): |
|---|
| | 177 | """Stop the coverage tool, save results, and report.""" |
|---|
| | 178 | import cherrypy |
|---|
| | 179 | cherrypy.codecoverage = False |
|---|
| | 180 | if self.coverage: |
|---|
| | 181 | self.coverage.save() |
|---|
| | 182 | self.report_coverage() |
|---|
| | 183 | print ("run cherrypy/lib/covercp.py as a script to serve " |
|---|
| | 184 | "coverage results on port 8080") |
|---|
| | 185 | |
|---|
| | 186 | def report_coverage(self): |
|---|
| | 187 | """Print a summary from the code coverage tool.""" |
|---|
| | 188 | |
|---|
| | 189 | basedir = self.basedir |
|---|
| | 190 | if basedir is None: |
|---|
| | 191 | # Assume we want to cover everything in "../../cherrypy/" |
|---|
| | 192 | localDir = os.path.dirname(__file__) |
|---|
| | 193 | basedir = os.path.normpath(os.path.join(os.getcwd(), localDir, '../')) |
|---|
| 87 | | if '-native' in argSet: |
|---|
| 88 | | servers.add('native') |
|---|
| 89 | | if '-serverless' in argSet: |
|---|
| 90 | | servers.add('serverless') |
|---|
| 91 | | if '-wsgi' in argSet or not servers: |
|---|
| 92 | | servers.add('wsgi') |
|---|
| 93 | | self.servers = servers |
|---|
| 94 | | argSet.difference_update(['-wsgi', '-native', '-serverless', '-all']) |
|---|
| 95 | | |
|---|
| 96 | | self.cover = ("-cover" in argSet) |
|---|
| 97 | | self.profile = ("-profile" in argSet) |
|---|
| 98 | | if self.cover and self.profile: |
|---|
| 99 | | raise BadArgument('Bad Arguments: you cannot run the profiler and the coverage tool at the same time.') |
|---|
| 100 | | argSet.difference_update(['-cover', '-profile']) |
|---|
| 101 | | |
|---|
| 102 | | if "-1.1" in argSet: |
|---|
| 103 | | self.protocol = "HTTP/1.1" |
|---|
| 104 | | argSet.difference_update(['-1.1']) |
|---|
| 105 | | else: |
|---|
| 106 | | self.protocol = "HTTP/1.0" |
|---|
| 107 | | |
|---|
| 108 | | # All remaining args should be test names. |
|---|
| 109 | | tests = [] |
|---|
| 110 | | for name in testList: |
|---|
| 111 | | if ("-" + name) in argSet: |
|---|
| 112 | | tests.append(name) |
|---|
| 113 | | argSet.discard("-" + name) |
|---|
| 114 | | if not tests: |
|---|
| 115 | | tests = testList |
|---|
| 116 | | self.tests = tests |
|---|
| 117 | | |
|---|
| 118 | | if len(argSet): |
|---|
| 119 | | for arg in args: |
|---|
| 120 | | if arg.lower() in argSet: |
|---|
| 121 | | raise BadArgument('Bad Argument: %s is not a valid option.' % arg) |
|---|
| 122 | | |
|---|
| 123 | | def get_coverage(): |
|---|
| 124 | | """Return a coverage.the_coverage instance. |
|---|
| 125 | | |
|---|
| 126 | | To use this feature, or the coverage server in cherrypy/lib/covercp, |
|---|
| 127 | | you need to download 'coverage.py', either Gareth Rees' original |
|---|
| 128 | | implementation: |
|---|
| 129 | | http://www.garethrees.org/2001/12/04/python-coverage/ |
|---|
| 130 | | |
|---|
| 131 | | or Ned Batchelder's enhanced version: |
|---|
| 132 | | http://www.nedbatchelder.com/code/modules/coverage.html |
|---|
| 133 | | |
|---|
| 134 | | If neither module is found in PYTHONPATH, this module returns None. |
|---|
| 135 | | """ |
|---|
| 136 | | try: |
|---|
| 137 | | from coverage import the_coverage as coverage |
|---|
| 138 | | c = os.path.join(os.path.dirname(__file__), "../lib/coverage.cache") |
|---|
| 139 | | coverage.cache_default = c |
|---|
| 140 | | if c and os.path.exists(c): |
|---|
| 141 | | os.remove(c) |
|---|
| 142 | | coverage.start() |
|---|
| 143 | | except ImportError: |
|---|
| 144 | | coverage = None |
|---|
| 145 | | return coverage |
|---|
| 146 | | |
|---|
| 147 | | |
|---|
| 148 | | def main(opts, conf=None, includeNotReady=False): |
|---|
| 149 | | """Run the test suite against multiple servers and other options. |
|---|
| 150 | | |
|---|
| 151 | | opts should be an Options instance, with the following attributes: |
|---|
| 152 | | tests = a list of module names |
|---|
| 153 | | servers = a list of servers (serverless, wsgi, native, all) |
|---|
| 154 | | cover = whether or not to run the coverage tool |
|---|
| 155 | | profile = whether or not to run the profiling tool |
|---|
| 156 | | protocol = the HTTP protocol version for requests, e.g. "HTTP/1.0" |
|---|
| 157 | | |
|---|
| 158 | | conf may be a dictionary or a filename (string). |
|---|
| 159 | | |
|---|
| 160 | | includeNotReady specifies whether or not to run the NotReadyTest, |
|---|
| 161 | | (which is typically only run for cherrypy itself; other apps |
|---|
| 162 | | which use this module may safely leave it off/False). |
|---|
| 163 | | |
|---|
| 164 | | """ |
|---|
| 165 | | |
|---|
| 166 | | if opts.cover: |
|---|
| | 195 | if not os.path.isabs(basedir): |
|---|
| | 196 | basedir = os.path.normpath(os.path.join(os.getcwd(), basedir)) |
|---|
| | 197 | basedir = basedir.lower() |
|---|
| | 198 | |
|---|
| | 199 | self.coverage.get_ready() |
|---|
| | 200 | morfs = [x for x in self.coverage.cexecuted |
|---|
| | 201 | if x.lower().startswith(basedir)] |
|---|
| | 202 | |
|---|
| | 203 | total_statements = 0 |
|---|
| | 204 | total_executed = 0 |
|---|
| | 205 | |
|---|
| | 206 | print |
|---|
| | 207 | print "CODE COVERAGE (this might take a while)", |
|---|
| | 208 | for morf in morfs: |
|---|
| | 209 | sys.stdout.write(".") |
|---|
| | 210 | sys.stdout.flush() |
|---|
| | 211 | name = os.path.split(morf)[1] |
|---|
| | 212 | try: |
|---|
| | 213 | _, statements, _, missing, readable = self.coverage.analysis2(morf) |
|---|
| | 214 | n = len(statements) |
|---|
| | 215 | m = n - len(missing) |
|---|
| | 216 | total_statements = total_statements + n |
|---|
| | 217 | total_executed = total_executed + m |
|---|
| | 218 | except KeyboardInterrupt: |
|---|
| | 219 | raise |
|---|
| | 220 | except: |
|---|
| | 221 | # No, really! We truly want to ignore any other errors. |
|---|
| | 222 | pass |
|---|
| | 223 | |
|---|
| | 224 | pc = 100.0 |
|---|
| | 225 | if total_statements > 0: |
|---|
| | 226 | pc = 100.0 * total_executed / total_statements |
|---|
| | 227 | |
|---|
| | 228 | print ("\nTotal: %s Covered: %s Percent: %2d%%" |
|---|
| | 229 | % (total_statements, total_executed, pc)) |
|---|
| | 230 | |
|---|
| | 231 | def run(self, conf=None): |
|---|
| | 232 | """Run the test harness.""" |
|---|
| | 233 | self.load() |
|---|
| | 234 | |
|---|
| 169 | | coverage = get_coverage() |
|---|
| 170 | | |
|---|
| 171 | | import cherrypy |
|---|
| 172 | | print "Python version used to run this test script:", sys.version.split()[0] |
|---|
| 173 | | print "CherryPy version", cherrypy.__version__ |
|---|
| 174 | | print |
|---|
| 175 | | |
|---|
| 176 | | from cherrypy.test import helper |
|---|
| 177 | | |
|---|
| 178 | | if includeNotReady: |
|---|
| | 237 | if self.cover: |
|---|
| | 238 | self.start_coverage() |
|---|
| | 239 | |
|---|
| | 240 | import cherrypy |
|---|
| | 241 | print "Python version used to run this test script:", sys.version.split()[0] |
|---|
| | 242 | print "CherryPy version", cherrypy.__version__ |
|---|
| | 243 | print |
|---|
| | 244 | |
|---|
| | 245 | if conf is None: |
|---|
| | 246 | conf = {'global': {'server.socketHost': '127.0.0.1', |
|---|
| | 247 | 'server.socketPort': 8000, |
|---|
| | 248 | 'server.threadPool': 10, |
|---|
| | 249 | 'server.logToScreen': False, |
|---|
| | 250 | 'server.environment': "production", |
|---|
| | 251 | } |
|---|
| | 252 | } |
|---|
| | 253 | elif isinstance(conf, basestring): |
|---|
| | 254 | conf = cherrypy.config.dict_from_config_file(conf) |
|---|
| | 255 | |
|---|
| | 256 | conf['server.protocolVersion'] = self.protocol |
|---|
| | 257 | |
|---|
| | 258 | if self.profile: |
|---|
| | 259 | conf['profiling.on'] = True |
|---|
| | 260 | |
|---|
| | 261 | self._run_all_servers(conf) |
|---|
| | 262 | |
|---|
| | 263 | if self.profile: |
|---|
| | 264 | del conf['profiling.on'] |
|---|
| | 265 | print |
|---|
| | 266 | print ("run /cherrypy/lib/profiler.py as a script to serve " |
|---|
| | 267 | "profiling results on port 8080") |
|---|
| | 268 | |
|---|
| | 269 | if self.cover: |
|---|
| | 270 | self.stop_coverage() |
|---|
| | 271 | |
|---|
| | 272 | def _run_all_servers(self, conf): |
|---|
| | 273 | # helper must be imported lazily so the coverage tool |
|---|
| | 274 | # can run against module-level statements within cherrypy. |
|---|
| | 275 | from cherrypy.test import helper |
|---|
| | 276 | s = [self.available_servers[name] for name in self.servers] |
|---|
| | 277 | s.sort() |
|---|
| | 278 | for priority, name, cls in s: |
|---|
| | 279 | print |
|---|
| | 280 | print "Running tests:", name |
|---|
| | 281 | helper.run_test_suite(self.tests, cls, conf) |
|---|
| | 282 | |
|---|
| | 283 | |
|---|
| | 284 | class CPTestHarness(TestHarness): |
|---|
| | 285 | |
|---|
| | 286 | def _run_all_servers(self, conf): |
|---|
| | 287 | from cherrypy.test import helper |
|---|
| | 288 | |
|---|
| 187 | | |
|---|
| 188 | | if conf is None: |
|---|
| 189 | | conf = {'global': {'server.socketHost': helper.HOST, |
|---|
| 190 | | 'server.socketPort': helper.PORT, |
|---|
| 191 | | 'server.threadPool': 10, |
|---|
| 192 | | 'server.logToScreen': False, |
|---|
| 193 | | 'server.environment': "production", |
|---|
| 194 | | } |
|---|
| 195 | | } |
|---|
| 196 | | elif isinstance(conf, basestring): |
|---|
| 197 | | conf = cherrypy.config.dict_from_config_file(conf) |
|---|
| 198 | | |
|---|
| 199 | | conf['server.protocolVersion'] = opts.protocol |
|---|
| 200 | | |
|---|
| 201 | | if opts.cover: |
|---|
| 202 | | cherrypy.codecoverage = True |
|---|
| 203 | | |
|---|
| 204 | | if opts.profile: |
|---|
| 205 | | conf['profiling.on'] = True |
|---|
| 206 | | |
|---|
| 207 | | if 'serverless' in opts.servers: |
|---|
| 208 | | print |
|---|
| 209 | | print "Running tests: Serverless" |
|---|
| 210 | | helper.run_test_suite(opts.tests, None, conf) |
|---|
| 211 | | |
|---|
| 212 | | if 'native' in opts.servers: |
|---|
| 213 | | print |
|---|
| 214 | | print "Running tests: Native HTTP Server" |
|---|
| 215 | | helper.run_test_suite(opts.tests, |
|---|
| 216 | | "cherrypy._cphttpserver.embedded_server", |
|---|
| 217 | | conf) |
|---|
| 218 | | |
|---|
| 219 | | if 'wsgi' in opts.servers: |
|---|
| 220 | | print |
|---|
| 221 | | print "Running tests: Native WSGI Server" |
|---|
| 222 | | helper.run_test_suite(opts.tests, |
|---|
| 223 | | "cherrypy._cpwsgi.WSGIServer", |
|---|
| 224 | | conf) |
|---|
| 225 | | |
|---|
| 226 | | if opts.profile or opts.cover: |
|---|
| 227 | | print |
|---|
| 228 | | |
|---|
| 229 | | if opts.profile: |
|---|
| 230 | | del conf['profiling.on'] |
|---|
| 231 | | print "run /cherrypy/lib/profiler.py as a script to serve profiling results on port 8080" |
|---|
| 232 | | |
|---|
| 233 | | if opts.cover: |
|---|
| 234 | | cherrypy.codecoverage = False |
|---|
| 235 | | if coverage: |
|---|
| 236 | | coverage.save() |
|---|
| 237 | | helper.report_coverage(coverage) |
|---|
| 238 | | print "run /cherrypy/lib/covercp.py as a script to serve coverage results on port 8080" |
|---|
| | 298 | |
|---|
| | 299 | TestHarness._run_all_servers(self, conf) |
|---|