importhttplibfromhttplibimportBadStatusLineimportosimportsysimportthreadingimporttimefromcherrypy.testimporttesttest.prefer_parent_path()importcherrypyengine=cherrypy.enginethisdir=os.path.join(os.getcwd(),os.path.dirname(__file__))PID_file_path=os.path.join(thisdir,'pid_for_test_daemonize')classRoot:defindex(self):return"Hello World"index.exposed=Truedefctrlc(self):raiseKeyboardInterrupt()ctrlc.exposed=Truedefgraceful(self):engine.graceful()return"app was (gracefully) restarted succesfully"graceful.exposed=Truedefblock_explicit(self):whileTrue:ifcherrypy.response.timed_out:cherrypy.response.timed_out=Falsereturn"broken!"time.sleep(0.01)block_explicit.exposed=Truedefblock_implicit(self):time.sleep(0.5)return"response.timeout = %s"%cherrypy.response.timeoutblock_implicit.exposed=Truecherrypy.tree.mount(Root())cherrypy.config.update({'environment':'test_suite','engine.deadlock_poll_freq':0.1,})classDependency:def__init__(self,bus):self.bus=busself.running=Falseself.startcount=0self.gracecount=0self.threads={}defsubscribe(self):self.bus.subscribe('start',self.start)self.bus.subscribe('stop',self.stop)self.bus.subscribe('graceful',self.graceful)self.bus.subscribe('start_thread',self.startthread)self.bus.subscribe('stop_thread',self.stopthread)defstart(self):self.running=Trueself.startcount+=1defstop(self):self.running=Falsedefgraceful(self):self.gracecount+=1defstartthread(self,thread_id):self.threads[thread_id]=Nonedefstopthread(self,thread_id):delself.threads[thread_id]db_connection=Dependency(engine)db_connection.subscribe()fromcherrypy.testimporthelperclassServerStateTests(helper.CPWebCase):deftest_0_NormalStateFlow(self):ifnotself.server_class:# Without having called "engine.start()", we should# get a 503 Service Unavailable response.self.getPage("/")self.assertStatus(503)# And our db_connection should not be runningself.assertEqual(db_connection.running,False)self.assertEqual(db_connection.startcount,0)self.assertEqual(len(db_connection.threads),0)# Test server startcherrypy.server.quickstart(self.server_class)engine.start()self.assertEqual(engine.state,engine.states.STARTED)ifself.server_class:host=cherrypy.server.socket_hostport=cherrypy.server.socket_portself.assertRaises(IOError,cherrypy._cpserver.check_port,host,port)# The db_connection should be running nowself.assertEqual(db_connection.running,True)self.assertEqual(db_connection.startcount,1)self.assertEqual(len(db_connection.threads),0)self.getPage("/")self.assertBody("Hello World")self.assertEqual(len(db_connection.threads),1)# Test engine stop. This will also stop the HTTP server.engine.stop()self.assertEqual(engine.state,engine.states.STOPPED)# Verify that our custom stop function was calledself.assertEqual(db_connection.running,False)self.assertEqual(len(db_connection.threads),0)ifnotself.server_class:# Once the engine has stopped, we should get a 503# error again. (If we were running an HTTP server,# then the connection should not even be processed).self.getPage("/")self.assertStatus(503)# Block the main thread now and verify that stop() works.defstoptest():self.getPage("/")self.assertBody("Hello World")engine.stop()cherrypy.server.start()engine.start_with_callback(stoptest)engine.block()self.assertEqual(engine.state,engine.states.STOPPED)deftest_1_Restart(self):cherrypy.server.start()engine.start()# The db_connection should be running nowself.assertEqual(db_connection.running,True)grace=db_connection.gracecountself.getPage("/")self.assertBody("Hello World")self.assertEqual(len(db_connection.threads),1)# Test server restart from this threadengine.graceful()self.assertEqual(engine.state,engine.states.STARTED)self.getPage("/")self.assertBody("Hello World")self.assertEqual(db_connection.running,True)self.assertEqual(db_connection.gracecount,grace+1)self.assertEqual(len(db_connection.threads),1)# Test server restart from inside a page handlerself.getPage("/graceful")self.assertEqual(engine.state,engine.states.STARTED)self.assertBody("app was (gracefully) restarted succesfully")self.assertEqual(db_connection.running,True)self.assertEqual(db_connection.gracecount,grace+2)# Since we are requesting synchronously, is only one thread used?# Note that the "/graceful" request has been flushed.self.assertEqual(len(db_connection.threads),0)engine.stop()self.assertEqual(engine.state,engine.states.STOPPED)self.assertEqual(db_connection.running,False)self.assertEqual(len(db_connection.threads),0)deftest_2_KeyboardInterrupt(self):ifself.server_class:# Raise a keyboard interrupt in the HTTP server's main thread.# We must start the server in this, the main threadengine.start()cherrypy.server.start()self.persistent=Truetry:# Make the first request and assert there's no "Connection: close".self.getPage("/")self.assertStatus('200 OK')self.assertBody("Hello World")self.assertNoHeader("Connection")cherrypy.server.httpserver.interrupt=KeyboardInterruptengine.block()self.assertEqual(db_connection.running,False)self.assertEqual(len(db_connection.threads),0)self.assertEqual(engine.state,engine.states.STOPPED)finally:self.persistent=False# Raise a keyboard interrupt in a page handler; on multithreaded# servers, this should occur in one of the worker threads.# This should raise a BadStatusLine error, since the worker# thread will just die without writing a response.engine.start()cherrypy.server.start()try:self.getPage("/ctrlc")exceptBadStatusLine:passelse:printself.bodyself.fail("AssertionError: BadStatusLine not raised")engine.block()self.assertEqual(db_connection.running,False)self.assertEqual(len(db_connection.threads),0)deftest_3_Deadlocks(self):cherrypy.config.update({'response.timeout':0.2})engine.start()cherrypy.server.start()try:self.assertNotEqual(cherrypy.timeout_monitor.thread,None)# Request a "normal" page.self.assertEqual(cherrypy.timeout_monitor.servings,[])self.getPage("/")self.assertBody("Hello World")# request.close is called async.whilecherrypy.timeout_monitor.servings:print".",time.sleep(0.01)# Request a page that explicitly checks itself for deadlock.# The deadlock_timeout should be 2 secs.self.getPage("/block_explicit")self.assertBody("broken!")# Request a page that implicitly breaks deadlock.# If we deadlock, we want to touch as little code as possible,# so we won't even call handle_error, just bail ASAP.self.getPage("/block_implicit")self.assertStatus(500)self.assertInBody("raise cherrypy.TimeoutError()")finally:engine.stop()deftest_4_Autoreload(self):ifnotself.server_class:print"skipped (no server) ",return# Start the demo script in a new processdemoscript=os.path.join(os.getcwd(),os.path.dirname(__file__),"test_states_demo.py")host=cherrypy.server.socket_hostport=cherrypy.server.socket_portcherrypy._cpserver.wait_for_free_port(host,port)args=[sys.executable,demoscript,host,str(port)]ifself.scheme=="https":args.append('-ssl')pid=os.spawnl(os.P_NOWAIT,sys.executable,*args)cherrypy._cpserver.wait_for_occupied_port(host,port)try:self.getPage("/start")start=float(self.body)# Give the autoreloader time to cache the file time.time.sleep(2)# Touch the fileos.utime(demoscript,None)# Give the autoreloader time to re-exec the processtime.sleep(2)cherrypy._cpserver.wait_for_occupied_port(host,port)self.getPage("/pid")pid=int(self.body)self.getPage("/start")self.assert_(float(self.body)>start)finally:# Shut down the spawned processself.getPage("/stop")try:try:# Mac, UNIXprintos.wait()exceptAttributeError:# Windowsprintos.waitpid(pid,0)exceptOSError,x:ifx.args!=(10,'No child processes'):raisedeftest_5_Start_Error(self):ifnotself.server_class:print"skipped (no server) ",return# Start the demo script in a new processdemoscript=os.path.join(os.getcwd(),os.path.dirname(__file__),"test_states_demo.py")host=cherrypy.server.socket_hostport=cherrypy.server.socket_port# If a process errors during start, it should stop the engine# and exit with a non-zero exit code.args=[sys.executable,demoscript,host,str(port),'-starterror']ifself.scheme=="https":args.append('-ssl')exit_code=os.spawnl(os.P_WAIT,sys.executable,*args)ifexit_code==0:self.fail("Process failed to return nonzero exit code.")classDaemonizeTests(helper.CPWebCase):deftest_1_Daemonize(self):ifnotself.server_class:print"skipped (no server) ",returnifos.namenotin['posix']:print"skipped (not on posix) ",return# Start the demo script in a new processdemoscript=os.path.join(os.getcwd(),os.path.dirname(__file__),"test_states_demo.py")host=cherrypy.server.socket_hostport=cherrypy.server.socket_portcherrypy._cpserver.wait_for_free_port(host,port)args=[sys.executable,demoscript,host,str(port),'-daemonize']ifself.scheme=="https":args.append('-ssl')# Spawn the process and wait, when this returns, the original process# is finished. If it daemonized properly, we should still be able# to access pages.exit_code=os.spawnl(os.P_WAIT,sys.executable,*args)cherrypy._cpserver.wait_for_occupied_port(host,port)# Give the server some time to start uptime.sleep(2)# Get the PID from the file.pid=int(open(PID_file_path).read())try:# Just get the pid of the daemonization process.self.getPage("/pid")self.assertStatus(200)page_pid=int(self.body)self.assertEqual(page_pid,pid)finally:# Shut down the spawned processself.getPage("/stop")try:printos.waitpid(pid,0)exceptOSError,x:ifx.args!=(10,'No child processes'):raise# Wait until here to test the exit code because we want to ensure# that we wait for the daemon to finish running before we fail.ifexit_code!=0:self.fail("Daemonized process failed to exit cleanly")deftest_2_Start_Error_With_Daemonize(self):ifnotself.server_class:print"skipped (no server) ",returnifos.namenotin['posix']:print"skipped (not on posix) ",return# Start the demo script in a new processdemoscript=os.path.join(os.getcwd(),os.path.dirname(__file__),"test_states_demo.py")host=cherrypy.server.socket_hostport=cherrypy.server.socket_port# If a process errors during start, it should stop the engine# and exit with a non-zero exit code, even if we daemonize soon# thereafter.args=[sys.executable,demoscript,host,str(port),'-starterror','-daemonize']ifself.scheme=="https":args.append('-ssl')exit_code=os.spawnl(os.P_WAIT,sys.executable,*args)ifexit_code==0:self.fail("Process failed to return nonzero exit code.")time.sleep(2)# Wait for the daemonized process to exit.defrun(server,conf):helper.setConfig(conf)ServerStateTests.server_class=serverDaemonizeTests.server_class=serversuite=helper.CPTestLoader.loadTestsFromTestCase(ServerStateTests)daemon_suite=helper.CPTestLoader.loadTestsFromTestCase(DaemonizeTests)try:try:importpyconquerexceptImportError:helper.CPTestRunner.run(suite)helper.CPTestRunner.run(daemon_suite)else:tr=pyconquer.Logger("cherrypy")tr.out=open(os.path.join(os.path.dirname(__file__),"test_states_conquer.log"),"wb")try:tr.start()helper.CPTestRunner.run(suite)helper.CPTestRunner.run(daemon_suite)finally:tr.stop()tr.out.close()finally:engine.stop()defrun_all(host,port,ssl=False):conf={'server.socket_host':host,'server.socket_port':port,'server.thread_pool':10,'environment':"test_suite",}ifhost:DaemonizeTests.HOST=ServerStateTests.HOST=hostifport:DaemonizeTests.PORT=ServerStateTests.PORT=portifssl:localDir=os.path.dirname(__file__)serverpem=os.path.join(os.getcwd(),localDir,'test.pem')conf['server.ssl_certificate']=serverpemconf['server.ssl_private_key']=serverpemDaemonizeTests.scheme=ServerStateTests.scheme="https"DaemonizeTests.HTTP_CONN=ServerStateTests.HTTP_CONN=httplib.HTTPSConnectiondef_run(server):printprint"Testing %s on %s:%s..."%(server,host,port)run(server,conf)_run("cherrypy._cpwsgi.CPWSGIServer")if__name__=="__main__":importsyshost='127.0.0.1'port=8000ssl=Falseargv=sys.argv[1:]ifargv:help_args=[prefix+atomforatomin("?","h","help")forprefixin("","-","--","\\")]forarginargv:ifarginhelp_args:printprint"test_states.py -? -> this help page"print"test_states.py [-host=h] [-port=p] -> run the tests on h:p"print"test_states.py -ssl [-host=h] [-port=p] -> run the tests using SSL on h:p"sys.exit(0)ifarg=="-ssl":ssl=Trueelifarg.startswith("-host="):host=arg[6:].strip("\"'")elifarg.startswith("-port="):port=int(arg[6:].strip())run_all(host,port,ssl)