| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2007-2008 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 """ | |
| 5 Tests for L{twisted.application.app} and L{twisted.scripts.twistd}. | |
| 6 """ | |
| 7 | |
| 8 import os, sys, cPickle | |
| 9 try: | |
| 10 import pwd, grp | |
| 11 except ImportError: | |
| 12 pwd = grp = None | |
| 13 | |
| 14 from zope.interface import implements | |
| 15 | |
| 16 from twisted.trial import unittest | |
| 17 | |
| 18 from twisted.application import service, app | |
| 19 from twisted.scripts import twistd | |
| 20 from twisted.python import log | |
| 21 | |
| 22 try: | |
| 23 import profile | |
| 24 except ImportError: | |
| 25 profile = None | |
| 26 | |
| 27 try: | |
| 28 import hotshot | |
| 29 import hotshot.stats | |
| 30 except (ImportError, SystemExit): | |
| 31 # For some reasons, hotshot.stats seems to raise SystemExit on some | |
| 32 # distributions, probably when considered non-free. See the import of | |
| 33 # this module in twisted.application.app for more details. | |
| 34 hotshot = None | |
| 35 | |
| 36 try: | |
| 37 import cProfile | |
| 38 import pstats | |
| 39 except ImportError: | |
| 40 cProfile = None | |
| 41 | |
| 42 | |
| 43 | |
| 44 def patchUserDatabase(patch, user, uid, group, gid): | |
| 45 """ | |
| 46 Patch L{pwd.getpwnam} so that it behaves as though only one user exists | |
| 47 and patch L{grp.getgrnam} so that it behaves as though only one group | |
| 48 exists. | |
| 49 | |
| 50 @param patch: A function like L{TestCase.patch} which will be used to | |
| 51 install the fake implementations. | |
| 52 | |
| 53 @type user: C{str} | |
| 54 @param user: The name of the single user which will exist. | |
| 55 | |
| 56 @type uid: C{int} | |
| 57 @param uid: The UID of the single user which will exist. | |
| 58 | |
| 59 @type group: C{str} | |
| 60 @param group: The name of the single user which will exist. | |
| 61 | |
| 62 @type gid: C{int} | |
| 63 @param gid: The GID of the single group which will exist. | |
| 64 """ | |
| 65 # Try not to be an unverified fake, but try not to depend on quirks of | |
| 66 # the system either (eg, run as a process with a uid and gid which | |
| 67 # equal each other, and so doesn't reliably test that uid is used where | |
| 68 # uid should be used and gid is used where gid should be used). -exarkun | |
| 69 pwent = pwd.getpwuid(os.getuid()) | |
| 70 grent = grp.getgrgid(os.getgid()) | |
| 71 | |
| 72 def getpwnam(name): | |
| 73 result = list(pwent) | |
| 74 result[result.index(pwent.pw_name)] = user | |
| 75 result[result.index(pwent.pw_uid)] = uid | |
| 76 result = tuple(result) | |
| 77 return {user: result}[name] | |
| 78 | |
| 79 def getgrnam(name): | |
| 80 result = list(grent) | |
| 81 result[result.index(grent.gr_name)] = group | |
| 82 result[result.index(grent.gr_gid)] = gid | |
| 83 result = tuple(result) | |
| 84 return {group: result}[name] | |
| 85 | |
| 86 patch(pwd, "getpwnam", getpwnam) | |
| 87 patch(grp, "getgrnam", getgrnam) | |
| 88 | |
| 89 | |
| 90 | |
| 91 class MockServiceMaker(object): | |
| 92 """ | |
| 93 A non-implementation of L{twisted.application.service.IServiceMaker}. | |
| 94 """ | |
| 95 tapname = 'ueoa' | |
| 96 | |
| 97 def makeService(self, options): | |
| 98 """ | |
| 99 Take a L{usage.Options} instance and return a | |
| 100 L{service.IService} provider. | |
| 101 """ | |
| 102 self.options = options | |
| 103 self.service = service.Service() | |
| 104 return self.service | |
| 105 | |
| 106 | |
| 107 | |
| 108 class CrippledApplicationRunner(twistd._SomeApplicationRunner): | |
| 109 """ | |
| 110 An application runner that cripples the platform-specific runner and | |
| 111 nasty side-effect-having code so that we can use it without actually | |
| 112 running any environment-affecting code. | |
| 113 """ | |
| 114 def preApplication(self): | |
| 115 pass | |
| 116 | |
| 117 def postApplication(self): | |
| 118 pass | |
| 119 | |
| 120 def startLogging(self, observer): | |
| 121 pass | |
| 122 | |
| 123 | |
| 124 | |
| 125 class ServerOptionsTest(unittest.TestCase): | |
| 126 """ | |
| 127 Non-platform-specific tests for the pltaform-specific ServerOptions class. | |
| 128 """ | |
| 129 | |
| 130 def test_postOptionsSubCommandCausesNoSave(self): | |
| 131 """ | |
| 132 postOptions should set no_save to True when a subcommand is used. | |
| 133 """ | |
| 134 config = twistd.ServerOptions() | |
| 135 config.subCommand = 'ueoa' | |
| 136 config.postOptions() | |
| 137 self.assertEquals(config['no_save'], True) | |
| 138 | |
| 139 | |
| 140 def test_postOptionsNoSubCommandSavesAsUsual(self): | |
| 141 """ | |
| 142 If no sub command is used, postOptions should not touch no_save. | |
| 143 """ | |
| 144 config = twistd.ServerOptions() | |
| 145 config.postOptions() | |
| 146 self.assertEquals(config['no_save'], False) | |
| 147 | |
| 148 | |
| 149 def test_reportProfileDeprecation(self): | |
| 150 """ | |
| 151 Check that the --report-profile option prints a C{DeprecationWarning}. | |
| 152 """ | |
| 153 config = twistd.ServerOptions() | |
| 154 self.assertWarns( | |
| 155 DeprecationWarning, "--report-profile option is deprecated and " | |
| 156 "a no-op since Twisted 8.0.", app.__file__, | |
| 157 config.parseOptions, ["--report-profile", "foo"]) | |
| 158 | |
| 159 | |
| 160 | |
| 161 class TapFileTest(unittest.TestCase): | |
| 162 """ | |
| 163 Test twistd-related functionality that requires a tap file on disk. | |
| 164 """ | |
| 165 | |
| 166 def setUp(self): | |
| 167 """ | |
| 168 Create a trivial Application and put it in a tap file on disk. | |
| 169 """ | |
| 170 self.tapfile = self.mktemp() | |
| 171 cPickle.dump(service.Application("Hi!"), file(self.tapfile, 'wb')) | |
| 172 | |
| 173 | |
| 174 def test_createOrGetApplicationWithTapFile(self): | |
| 175 """ | |
| 176 Ensure that the createOrGetApplication call that 'twistd -f foo.tap' | |
| 177 makes will load the Application out of foo.tap. | |
| 178 """ | |
| 179 config = twistd.ServerOptions() | |
| 180 config.parseOptions(['-f', self.tapfile]) | |
| 181 application = CrippledApplicationRunner(config).createOrGetApplication() | |
| 182 self.assertEquals(service.IService(application).name, 'Hi!') | |
| 183 | |
| 184 | |
| 185 | |
| 186 class TestApplicationRunner(app.ApplicationRunner): | |
| 187 """ | |
| 188 An ApplicationRunner which tracks the environment in which its | |
| 189 methods are called. | |
| 190 """ | |
| 191 def preApplication(self): | |
| 192 self.order = ["pre"] | |
| 193 self.hadApplicationPreApplication = hasattr(self, 'application') | |
| 194 | |
| 195 | |
| 196 def getLogObserver(self): | |
| 197 self.order.append("log") | |
| 198 self.hadApplicationLogObserver = hasattr(self, 'application') | |
| 199 return lambda events: None | |
| 200 | |
| 201 | |
| 202 def startLogging(self, observer): | |
| 203 pass | |
| 204 | |
| 205 | |
| 206 def postApplication(self): | |
| 207 self.order.append("post") | |
| 208 self.hadApplicationPostApplication = hasattr(self, 'application') | |
| 209 | |
| 210 | |
| 211 | |
| 212 class ApplicationRunnerTest(unittest.TestCase): | |
| 213 """ | |
| 214 Non-platform-specific tests for the platform-specific ApplicationRunner. | |
| 215 """ | |
| 216 def setUp(self): | |
| 217 config = twistd.ServerOptions() | |
| 218 self.serviceMaker = MockServiceMaker() | |
| 219 # Set up a config object like it's been parsed with a subcommand | |
| 220 config.loadedPlugins = {'test_command': self.serviceMaker} | |
| 221 config.subOptions = object() | |
| 222 config.subCommand = 'test_command' | |
| 223 self.config = config | |
| 224 | |
| 225 | |
| 226 def test_applicationRunnerGetsCorrectApplication(self): | |
| 227 """ | |
| 228 Ensure that a twistd plugin gets used in appropriate ways: it | |
| 229 is passed its Options instance, and the service it returns is | |
| 230 added to the application. | |
| 231 """ | |
| 232 arunner = CrippledApplicationRunner(self.config) | |
| 233 arunner.run() | |
| 234 | |
| 235 self.assertIdentical( | |
| 236 self.serviceMaker.options, self.config.subOptions, | |
| 237 "ServiceMaker.makeService needs to be passed the correct " | |
| 238 "sub Command object.") | |
| 239 self.assertIdentical( | |
| 240 self.serviceMaker.service, | |
| 241 service.IService(arunner.application).services[0], | |
| 242 "ServiceMaker.makeService's result needs to be set as a child " | |
| 243 "of the Application.") | |
| 244 | |
| 245 | |
| 246 def test_preAndPostApplication(self): | |
| 247 """ | |
| 248 Test thet preApplication and postApplication methods are | |
| 249 called by ApplicationRunner.run() when appropriate. | |
| 250 """ | |
| 251 s = TestApplicationRunner(self.config) | |
| 252 s.run() | |
| 253 self.failIf(s.hadApplicationPreApplication) | |
| 254 self.failUnless(s.hadApplicationPostApplication) | |
| 255 self.failUnless(s.hadApplicationLogObserver) | |
| 256 self.assertEquals(s.order, ["pre", "log", "post"]) | |
| 257 | |
| 258 | |
| 259 def test_stdoutLogObserver(self): | |
| 260 """ | |
| 261 Verify that if C{'-'} is specified as the log file, stdout is used. | |
| 262 """ | |
| 263 self.config.parseOptions(["--logfile", "-", "--nodaemon"]) | |
| 264 runner = CrippledApplicationRunner(self.config) | |
| 265 observerMethod = runner.getLogObserver() | |
| 266 observer = observerMethod.im_self | |
| 267 self.failUnless(isinstance(observer, log.FileLogObserver)) | |
| 268 writeMethod = observer.write | |
| 269 fileObj = writeMethod.__self__ | |
| 270 self.assertIdentical(fileObj, sys.stdout) | |
| 271 | |
| 272 | |
| 273 def test_fileLogObserver(self): | |
| 274 """ | |
| 275 Verify that if a string other than C{'-'} is specified as the log file, | |
| 276 the file with that name is used. | |
| 277 """ | |
| 278 logfilename = os.path.abspath(self.mktemp()) | |
| 279 self.config.parseOptions(["--logfile", logfilename]) | |
| 280 runner = CrippledApplicationRunner(self.config) | |
| 281 observerMethod = runner.getLogObserver() | |
| 282 observer = observerMethod.im_self | |
| 283 self.failUnless(isinstance(observer, log.FileLogObserver)) | |
| 284 writeMethod = observer.write | |
| 285 fileObj = writeMethod.im_self | |
| 286 self.assertEqual(fileObj.path, logfilename) | |
| 287 | |
| 288 | |
| 289 def _applicationStartsWithConfiguredID(self, argv, uid, gid): | |
| 290 """ | |
| 291 Assert that given a particular command line, an application is started | |
| 292 as a particular UID/GID. | |
| 293 | |
| 294 @param argv: A list of strings giving the options to parse. | |
| 295 @param uid: An integer giving the expected UID. | |
| 296 @param gid: An integer giving the expected GID. | |
| 297 """ | |
| 298 self.config.parseOptions(argv) | |
| 299 | |
| 300 events = [] | |
| 301 class FakeUnixApplicationRunner(twistd._SomeApplicationRunner): | |
| 302 def setupEnvironment(self, chroot, rundir, nodaemon, pidfile): | |
| 303 events.append('environment') | |
| 304 | |
| 305 def shedPrivileges(self, euid, uid, gid): | |
| 306 events.append(('privileges', euid, uid, gid)) | |
| 307 | |
| 308 def startReactor(self, reactor, oldstdout, oldstderr): | |
| 309 events.append('reactor') | |
| 310 | |
| 311 def removePID(self, pidfile): | |
| 312 pass | |
| 313 | |
| 314 | |
| 315 class FakeService(object): | |
| 316 implements(service.IService, service.IProcess) | |
| 317 | |
| 318 processName = None | |
| 319 | |
| 320 def privilegedStartService(self): | |
| 321 events.append('privilegedStartService') | |
| 322 | |
| 323 def startService(self): | |
| 324 events.append('startService') | |
| 325 | |
| 326 def stopService(self): | |
| 327 pass | |
| 328 | |
| 329 runner = FakeUnixApplicationRunner(self.config) | |
| 330 runner.preApplication() | |
| 331 runner.application = FakeService() | |
| 332 runner.postApplication() | |
| 333 | |
| 334 self.assertEqual( | |
| 335 events, | |
| 336 ['environment', 'privilegedStartService', | |
| 337 ('privileges', False, uid, gid), 'startService', 'reactor']) | |
| 338 | |
| 339 | |
| 340 def test_applicationStartsWithConfiguredNumericIDs(self): | |
| 341 """ | |
| 342 L{postApplication} should change the UID and GID to the values | |
| 343 specified as numeric strings by the configuration after running | |
| 344 L{service.IService.privilegedStartService} and before running | |
| 345 L{service.IService.startService}. | |
| 346 """ | |
| 347 uid = 1234 | |
| 348 gid = 4321 | |
| 349 self._applicationStartsWithConfiguredID( | |
| 350 ["--uid", str(uid), "--gid", str(gid)], uid, gid) | |
| 351 | |
| 352 | |
| 353 def test_applicationStartsWithConfiguredNameIDs(self): | |
| 354 """ | |
| 355 L{postApplication} should change the UID and GID to the values | |
| 356 specified as user and group names by the configuration after running | |
| 357 L{service.IService.privilegedStartService} and before running | |
| 358 L{service.IService.startService}. | |
| 359 """ | |
| 360 user = "foo" | |
| 361 uid = 1234 | |
| 362 group = "bar" | |
| 363 gid = 4321 | |
| 364 patchUserDatabase(self.patch, user, uid, group, gid) | |
| 365 self._applicationStartsWithConfiguredID( | |
| 366 ["--uid", user, "--gid", group], uid, gid) | |
| 367 | |
| 368 if getattr(os, 'setuid', None) is None: | |
| 369 msg = "Platform does not support --uid/--gid twistd options." | |
| 370 test_applicationStartsWithConfiguredNameIDs.skip = msg | |
| 371 test_applicationStartsWithConfiguredNumericIDs.skip = msg | |
| 372 del msg | |
| 373 | |
| 374 | |
| 375 def test_startReactorRunsTheReactor(self): | |
| 376 """ | |
| 377 L{startReactor} calls L{reactor.run}. | |
| 378 """ | |
| 379 reactor = DummyReactor() | |
| 380 runner = app.ApplicationRunner({ | |
| 381 "profile": False, | |
| 382 "profiler": "profile", | |
| 383 "debug": False}) | |
| 384 runner.startReactor(reactor, None, None) | |
| 385 self.assertTrue( | |
| 386 reactor.called, "startReactor did not call reactor.run()") | |
| 387 | |
| 388 | |
| 389 | |
| 390 class DummyReactor(object): | |
| 391 """ | |
| 392 A dummy reactor, only providing a C{run} method and checking that it | |
| 393 has been called. | |
| 394 | |
| 395 @ivar called: if C{run} has been called or not. | |
| 396 @type called: C{bool} | |
| 397 """ | |
| 398 called = False | |
| 399 | |
| 400 def run(self): | |
| 401 """ | |
| 402 A fake run method, checking that it's been called one and only time. | |
| 403 """ | |
| 404 if self.called: | |
| 405 raise RuntimeError("Already called") | |
| 406 self.called = True | |
| 407 | |
| 408 | |
| 409 | |
| 410 class AppProfilingTestCase(unittest.TestCase): | |
| 411 """ | |
| 412 Tests for L{app.AppProfiler}. | |
| 413 """ | |
| 414 | |
| 415 def test_profile(self): | |
| 416 """ | |
| 417 L{app.ProfileRunner.run} should call the C{run} method of the reactor | |
| 418 and save profile data in the specified file. | |
| 419 """ | |
| 420 config = twistd.ServerOptions() | |
| 421 config["profile"] = self.mktemp() | |
| 422 config["profiler"] = "profile" | |
| 423 profiler = app.AppProfiler(config) | |
| 424 reactor = DummyReactor() | |
| 425 | |
| 426 profiler.run(reactor) | |
| 427 | |
| 428 self.assertTrue(reactor.called) | |
| 429 data = file(config["profile"]).read() | |
| 430 self.assertIn("DummyReactor.run", data) | |
| 431 self.assertIn("function calls", data) | |
| 432 | |
| 433 if profile is None: | |
| 434 test_profile.skip = "profile module not available" | |
| 435 | |
| 436 | |
| 437 def test_profileSaveStats(self): | |
| 438 """ | |
| 439 With the C{savestats} option specified, L{app.ProfileRunner.run} | |
| 440 should save the raw stats object instead of a summary output. | |
| 441 """ | |
| 442 config = twistd.ServerOptions() | |
| 443 config["profile"] = self.mktemp() | |
| 444 config["profiler"] = "profile" | |
| 445 config["savestats"] = True | |
| 446 profiler = app.AppProfiler(config) | |
| 447 reactor = DummyReactor() | |
| 448 | |
| 449 profiler.run(reactor) | |
| 450 | |
| 451 self.assertTrue(reactor.called) | |
| 452 data = file(config["profile"]).read() | |
| 453 self.assertIn("DummyReactor.run", data) | |
| 454 self.assertNotIn("function calls", data) | |
| 455 | |
| 456 if profile is None: | |
| 457 test_profileSaveStats.skip = "profile module not available" | |
| 458 | |
| 459 | |
| 460 def test_withoutProfile(self): | |
| 461 """ | |
| 462 When the C{profile} module is not present, L{app.ProfilerRunner.run} | |
| 463 should raise a C{SystemExit} exception. | |
| 464 """ | |
| 465 savedModules = sys.modules.copy() | |
| 466 | |
| 467 config = twistd.ServerOptions() | |
| 468 config["profiler"] = "profile" | |
| 469 profiler = app.AppProfiler(config) | |
| 470 | |
| 471 sys.modules["profile"] = None | |
| 472 try: | |
| 473 self.assertRaises(SystemExit, profiler.run, None) | |
| 474 finally: | |
| 475 sys.modules.clear() | |
| 476 sys.modules.update(savedModules) | |
| 477 | |
| 478 | |
| 479 def test_profilePrintStatsError(self): | |
| 480 """ | |
| 481 When an error happens during the print of the stats, C{sys.stdout} | |
| 482 should be restored to its initial value. | |
| 483 """ | |
| 484 class ErroneousProfile(profile.Profile): | |
| 485 def print_stats(self): | |
| 486 raise RuntimeError("Boom") | |
| 487 self.patch(profile, "Profile", ErroneousProfile) | |
| 488 | |
| 489 config = twistd.ServerOptions() | |
| 490 config["profile"] = self.mktemp() | |
| 491 config["profiler"] = "profile" | |
| 492 profiler = app.AppProfiler(config) | |
| 493 reactor = DummyReactor() | |
| 494 | |
| 495 oldStdout = sys.stdout | |
| 496 self.assertRaises(RuntimeError, profiler.run, reactor) | |
| 497 self.assertIdentical(sys.stdout, oldStdout) | |
| 498 | |
| 499 if profile is None: | |
| 500 test_profilePrintStatsError.skip = "profile module not available" | |
| 501 | |
| 502 | |
| 503 def test_hotshot(self): | |
| 504 """ | |
| 505 L{app.HotshotRunner.run} should call the C{run} method of the reactor | |
| 506 and save profile data in the specified file. | |
| 507 """ | |
| 508 config = twistd.ServerOptions() | |
| 509 config["profile"] = self.mktemp() | |
| 510 config["profiler"] = "hotshot" | |
| 511 profiler = app.AppProfiler(config) | |
| 512 reactor = DummyReactor() | |
| 513 | |
| 514 profiler.run(reactor) | |
| 515 | |
| 516 self.assertTrue(reactor.called) | |
| 517 data = file(config["profile"]).read() | |
| 518 self.assertIn("run", data) | |
| 519 self.assertIn("function calls", data) | |
| 520 | |
| 521 if hotshot is None: | |
| 522 test_hotshot.skip = "hotshot module not available" | |
| 523 | |
| 524 | |
| 525 def test_hotshotSaveStats(self): | |
| 526 """ | |
| 527 With the C{savestats} option specified, L{app.HotshotRunner.run} should | |
| 528 save the raw stats object instead of a summary output. | |
| 529 """ | |
| 530 config = twistd.ServerOptions() | |
| 531 config["profile"] = self.mktemp() | |
| 532 config["profiler"] = "hotshot" | |
| 533 config["savestats"] = True | |
| 534 profiler = app.AppProfiler(config) | |
| 535 reactor = DummyReactor() | |
| 536 | |
| 537 profiler.run(reactor) | |
| 538 | |
| 539 self.assertTrue(reactor.called) | |
| 540 data = file(config["profile"]).read() | |
| 541 self.assertIn("hotshot-version", data) | |
| 542 self.assertIn("run", data) | |
| 543 self.assertNotIn("function calls", data) | |
| 544 | |
| 545 if hotshot is None: | |
| 546 test_hotshotSaveStats.skip = "hotshot module not available" | |
| 547 | |
| 548 | |
| 549 def test_withoutHotshot(self): | |
| 550 """ | |
| 551 When the C{hotshot} module is not present, L{app.HotshotRunner.run} | |
| 552 should raise a C{SystemExit} exception and log the C{ImportError}. | |
| 553 """ | |
| 554 savedModules = sys.modules.copy() | |
| 555 sys.modules["hotshot"] = None | |
| 556 | |
| 557 config = twistd.ServerOptions() | |
| 558 config["profiler"] = "hotshot" | |
| 559 profiler = app.AppProfiler(config) | |
| 560 try: | |
| 561 self.assertRaises(SystemExit, profiler.run, None) | |
| 562 finally: | |
| 563 sys.modules.clear() | |
| 564 sys.modules.update(savedModules) | |
| 565 | |
| 566 | |
| 567 def test_nothotshotDeprecation(self): | |
| 568 """ | |
| 569 Check that switching on the C{nothotshot} option produces a warning and | |
| 570 sets the profiler to B{profile}. | |
| 571 """ | |
| 572 config = twistd.ServerOptions() | |
| 573 config['nothotshot'] = True | |
| 574 profiler = self.assertWarns(DeprecationWarning, | |
| 575 "The --nothotshot option is deprecated. Please specify the " | |
| 576 "profiler name using the --profiler option", | |
| 577 app.__file__, app.AppProfiler, config) | |
| 578 self.assertEquals(profiler.profiler, "profile") | |
| 579 | |
| 580 | |
| 581 def test_hotshotPrintStatsError(self): | |
| 582 """ | |
| 583 When an error happens while printing the stats, C{sys.stdout} | |
| 584 should be restored to its initial value. | |
| 585 """ | |
| 586 import pstats | |
| 587 class ErroneousStats(pstats.Stats): | |
| 588 def print_stats(self): | |
| 589 raise RuntimeError("Boom") | |
| 590 self.patch(pstats, "Stats", ErroneousStats) | |
| 591 | |
| 592 config = twistd.ServerOptions() | |
| 593 config["profile"] = self.mktemp() | |
| 594 config["profiler"] = "hotshot" | |
| 595 profiler = app.AppProfiler(config) | |
| 596 reactor = DummyReactor() | |
| 597 | |
| 598 oldStdout = sys.stdout | |
| 599 self.assertRaises(RuntimeError, profiler.run, reactor) | |
| 600 self.assertIdentical(sys.stdout, oldStdout) | |
| 601 | |
| 602 if hotshot is None: | |
| 603 test_hotshotPrintStatsError.skip = "hotshot module not available" | |
| 604 | |
| 605 | |
| 606 def test_cProfile(self): | |
| 607 """ | |
| 608 L{app.CProfileRunner.run} should call the C{run} method of the | |
| 609 reactor and save profile data in the specified file. | |
| 610 """ | |
| 611 config = twistd.ServerOptions() | |
| 612 config["profile"] = self.mktemp() | |
| 613 config["profiler"] = "cProfile" | |
| 614 profiler = app.AppProfiler(config) | |
| 615 reactor = DummyReactor() | |
| 616 | |
| 617 profiler.run(reactor) | |
| 618 | |
| 619 self.assertTrue(reactor.called) | |
| 620 data = file(config["profile"]).read() | |
| 621 self.assertIn("run", data) | |
| 622 self.assertIn("function calls", data) | |
| 623 | |
| 624 if cProfile is None: | |
| 625 test_cProfile.skip = "cProfile module not available" | |
| 626 | |
| 627 | |
| 628 def test_cProfileSaveStats(self): | |
| 629 """ | |
| 630 With the C{savestats} option specified, | |
| 631 L{app.CProfileRunner.run} should save the raw stats object | |
| 632 instead of a summary output. | |
| 633 """ | |
| 634 config = twistd.ServerOptions() | |
| 635 config["profile"] = self.mktemp() | |
| 636 config["profiler"] = "cProfile" | |
| 637 config["savestats"] = True | |
| 638 profiler = app.AppProfiler(config) | |
| 639 reactor = DummyReactor() | |
| 640 | |
| 641 profiler.run(reactor) | |
| 642 | |
| 643 self.assertTrue(reactor.called) | |
| 644 data = file(config["profile"]).read() | |
| 645 self.assertIn("run", data) | |
| 646 | |
| 647 if cProfile is None: | |
| 648 test_cProfileSaveStats.skip = "cProfile module not available" | |
| 649 | |
| 650 | |
| 651 def test_withoutCProfile(self): | |
| 652 """ | |
| 653 When the C{cProfile} module is not present, | |
| 654 L{app.CProfileRunner.run} should raise a C{SystemExit} | |
| 655 exception and log the C{ImportError}. | |
| 656 """ | |
| 657 savedModules = sys.modules.copy() | |
| 658 sys.modules["cProfile"] = None | |
| 659 | |
| 660 config = twistd.ServerOptions() | |
| 661 config["profiler"] = "cProfile" | |
| 662 profiler = app.AppProfiler(config) | |
| 663 try: | |
| 664 self.assertRaises(SystemExit, profiler.run, None) | |
| 665 finally: | |
| 666 sys.modules.clear() | |
| 667 sys.modules.update(savedModules) | |
| 668 | |
| 669 | |
| 670 def test_unknownProfiler(self): | |
| 671 """ | |
| 672 Check that L{app.AppProfiler} raises L{SystemExit} when given an | |
| 673 unknown profiler name. | |
| 674 """ | |
| 675 config = twistd.ServerOptions() | |
| 676 config["profile"] = self.mktemp() | |
| 677 config["profiler"] = "foobar" | |
| 678 | |
| 679 error = self.assertRaises(SystemExit, app.AppProfiler, config) | |
| 680 self.assertEquals(str(error), "Unsupported profiler name: foobar") | |
| 681 | |
| 682 | |
| 683 def test_oldRunWithProfiler(self): | |
| 684 """ | |
| 685 L{app.runWithProfiler} should print a C{DeprecationWarning} pointing | |
| 686 at L{AppProfiler}. | |
| 687 """ | |
| 688 class DummyProfiler(object): | |
| 689 called = False | |
| 690 def run(self, reactor): | |
| 691 self.called = True | |
| 692 profiler = DummyProfiler() | |
| 693 self.patch(app, "AppProfiler", lambda conf: profiler) | |
| 694 | |
| 695 def runWithProfiler(): | |
| 696 return app.runWithProfiler(DummyReactor(), {}) | |
| 697 | |
| 698 self.assertWarns(DeprecationWarning, | |
| 699 "runWithProfiler is deprecated since Twisted 8.0. " | |
| 700 "Use ProfileRunner instead.", __file__, | |
| 701 runWithProfiler) | |
| 702 self.assertTrue(profiler.called) | |
| 703 | |
| 704 | |
| 705 def test_oldRunWithHotshot(self): | |
| 706 """ | |
| 707 L{app.runWithHotshot} should print a C{DeprecationWarning} pointing | |
| 708 at L{AppProfiler}. | |
| 709 """ | |
| 710 class DummyProfiler(object): | |
| 711 called = False | |
| 712 def run(self, reactor): | |
| 713 self.called = True | |
| 714 profiler = DummyProfiler() | |
| 715 self.patch(app, "AppProfiler", lambda conf: profiler) | |
| 716 | |
| 717 def runWithHotshot(): | |
| 718 return app.runWithHotshot(DummyReactor(), {}) | |
| 719 | |
| 720 self.assertWarns(DeprecationWarning, | |
| 721 "runWithHotshot is deprecated since Twisted 8.0. " | |
| 722 "Use HotshotRunner instead.", __file__, | |
| 723 runWithHotshot) | |
| 724 self.assertTrue(profiler.called) | |
| OLD | NEW |