1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017 |
- import os
- import signal
- import time
- import unittest
- import sys
- import errno
- from mock import Mock, patch, sentinel
- from supervisor.tests.base import DummyOptions
- from supervisor.tests.base import DummyPConfig
- from supervisor.tests.base import DummyProcess
- from supervisor.tests.base import DummyPGroupConfig
- from supervisor.tests.base import DummyDispatcher
- from supervisor.tests.base import DummyEvent
- from supervisor.tests.base import DummyFCGIGroupConfig
- from supervisor.tests.base import DummySocketConfig
- from supervisor.tests.base import DummyProcessGroup
- from supervisor.tests.base import DummyFCGIProcessGroup
- from supervisor.process import Subprocess
- from supervisor.options import BadCommand
- class SubprocessTests(unittest.TestCase):
- def _getTargetClass(self):
- from supervisor.process import Subprocess
- return Subprocess
- def _makeOne(self, *arg, **kw):
- return self._getTargetClass()(*arg, **kw)
- def tearDown(self):
- from supervisor.events import clear
- clear()
- def test_getProcessStateDescription(self):
- from supervisor.states import ProcessStates
- from supervisor.process import getProcessStateDescription
- for statename, code in ProcessStates.__dict__.items():
- if isinstance(code, int):
- self.assertEqual(getProcessStateDescription(code), statename)
- def test_ctor(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'cat', 'bin/cat',
- stdout_logfile='/tmp/temp123.log',
- stderr_logfile='/tmp/temp456.log')
- instance = self._makeOne(config)
- self.assertEqual(instance.config, config)
- self.assertEqual(instance.config.options, options)
- self.assertEqual(instance.laststart, 0)
- self.assertEqual(instance.pid, 0)
- self.assertEqual(instance.laststart, 0)
- self.assertEqual(instance.laststop, 0)
- self.assertEqual(instance.delay, 0)
- self.assertEqual(instance.administrative_stop, 0)
- self.assertEqual(instance.killing, 0)
- self.assertEqual(instance.backoff, 0)
- self.assertEqual(instance.pipes, {})
- self.assertEqual(instance.dispatchers, {})
- self.assertEqual(instance.spawnerr, None)
- def test_repr(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'cat', 'bin/cat')
- instance = self._makeOne(config)
- s = repr(instance)
- self.assertTrue(s.startswith('<Subprocess at'))
- self.assertTrue(s.endswith('with name cat in state STOPPED>'))
- def test_reopenlogs(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.dispatchers = {0:DummyDispatcher(readable=True),
- 1:DummyDispatcher(writable=True)}
- instance.reopenlogs()
- self.assertEqual(instance.dispatchers[0].logs_reopened, True)
- self.assertEqual(instance.dispatchers[1].logs_reopened, False)
- def test_removelogs(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.dispatchers = {0:DummyDispatcher(readable=True),
- 1:DummyDispatcher(writable=True)}
- instance.removelogs()
- self.assertEqual(instance.dispatchers[0].logs_removed, True)
- self.assertEqual(instance.dispatchers[1].logs_removed, False)
- def test_drain(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test',
- stdout_logfile='/tmp/foo',
- stderr_logfile='/tmp/bar')
- instance = self._makeOne(config)
- instance.dispatchers = {0:DummyDispatcher(readable=True),
- 1:DummyDispatcher(writable=True)}
- instance.drain()
- self.assertTrue(instance.dispatchers[0].read_event_handled)
- self.assertTrue(instance.dispatchers[1].write_event_handled)
- def test_get_execv_args_bad_command_extraquote(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'extraquote', 'extraquote"')
- instance = self._makeOne(config)
- self.assertRaises(BadCommand, instance.get_execv_args)
- def test_get_execv_args_bad_command_empty(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'empty', '')
- instance = self._makeOne(config)
- self.assertRaises(BadCommand, instance.get_execv_args)
- def test_get_execv_args_bad_command_whitespaceonly(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'whitespaceonly', ' \t ')
- instance = self._makeOne(config)
- self.assertRaises(BadCommand, instance.get_execv_args)
- def test_get_execv_args_abs_missing(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere')
- instance = self._makeOne(config)
- args = instance.get_execv_args()
- self.assertEqual(args, ('/notthere', ['/notthere']))
- def test_get_execv_args_abs_withquotes_missing(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere "an argument"')
- instance = self._makeOne(config)
- args = instance.get_execv_args()
- self.assertEqual(args, ('/notthere', ['/notthere', 'an argument']))
- def test_get_execv_args_rel_missing(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', 'notthere')
- instance = self._makeOne(config)
- args = instance.get_execv_args()
- self.assertEqual(args, ('notthere', ['notthere']))
- def test_get_execv_args_rel_withquotes_missing(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', 'notthere "an argument"')
- instance = self._makeOne(config)
- args = instance.get_execv_args()
- self.assertEqual(args, ('notthere', ['notthere', 'an argument']))
- def test_get_execv_args_abs(self):
- executable = '/bin/sh foo'
- options = DummyOptions()
- config = DummyPConfig(options, 'sh', executable)
- instance = self._makeOne(config)
- args = instance.get_execv_args()
- self.assertEqual(len(args), 2)
- self.assertEqual(args[0], '/bin/sh')
- self.assertEqual(args[1], ['/bin/sh', 'foo'])
- def test_get_execv_args_rel(self):
- executable = 'sh foo'
- options = DummyOptions()
- config = DummyPConfig(options, 'sh', executable)
- instance = self._makeOne(config)
- args = instance.get_execv_args()
- self.assertEqual(len(args), 2)
- self.assertEqual(args[0], '/bin/sh')
- self.assertEqual(args[1], ['sh', 'foo'])
- def test_record_spawnerr(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.record_spawnerr('foo')
- self.assertEqual(instance.spawnerr, 'foo')
- self.assertEqual(options.logger.data[0], 'spawnerr: foo')
- def test_spawn_already_running(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'sh', '/bin/sh')
- instance = self._makeOne(config)
- instance.pid = True
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.RUNNING
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.logger.data[0], "process 'sh' already running")
- self.assertEqual(instance.state, ProcessStates.RUNNING)
- def test_spawn_fail_check_execv_args(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'bad', '/bad/filename')
- instance = self._makeOne(config)
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.BACKOFF
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(instance.spawnerr, 'bad filename')
- self.assertEqual(options.logger.data[0], "spawnerr: bad filename")
- self.assertTrue(instance.delay)
- self.assertTrue(instance.backoff)
- from supervisor.states import ProcessStates
- self.assertEqual(instance.state, ProcessStates.BACKOFF)
- self.assertEqual(len(L), 2)
- event1 = L[0]
- event2 = L[1]
- self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
- self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
- def test_spawn_fail_make_pipes_emfile(self):
- options = DummyOptions()
- options.make_pipes_error = errno.EMFILE
- config = DummyPConfig(options, 'good', '/good/filename')
- instance = self._makeOne(config)
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.BACKOFF
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(instance.spawnerr,
- "too many open files to spawn 'good'")
- self.assertEqual(options.logger.data[0],
- "spawnerr: too many open files to spawn 'good'")
- self.assertTrue(instance.delay)
- self.assertTrue(instance.backoff)
- from supervisor.states import ProcessStates
- self.assertEqual(instance.state, ProcessStates.BACKOFF)
- self.assertEqual(len(L), 2)
- event1, event2 = L
- self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
- self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
- def test_spawn_fail_make_pipes_other(self):
- options = DummyOptions()
- options.make_pipes_error = 1
- config = DummyPConfig(options, 'good', '/good/filename')
- instance = self._makeOne(config)
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.BACKOFF
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(instance.spawnerr,
- 'unknown error making dispatchers: EPERM')
- self.assertEqual(options.logger.data[0],
- "spawnerr: unknown error making dispatchers: EPERM")
- self.assertTrue(instance.delay)
- self.assertTrue(instance.backoff)
- from supervisor.states import ProcessStates
- self.assertEqual(instance.state, ProcessStates.BACKOFF)
- self.assertEqual(len(L), 2)
- event1, event2 = L
- self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
- self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
- def test_spawn_fail_make_dispatchers_eisdir(self):
- options = DummyOptions()
- config = DummyPConfig(options, name='cat', command='/bin/cat',
- stdout_logfile='/a/directory') # not a file
- def raise_eisdir(envelope):
- raise IOError(errno.EISDIR)
- config.make_dispatchers = raise_eisdir
- instance = self._makeOne(config)
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.BACKOFF
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(instance.spawnerr,
- "unknown error making dispatchers: EISDIR")
- self.assertEqual(options.logger.data[0],
- "spawnerr: unknown error making dispatchers: EISDIR")
- self.assertTrue(instance.delay)
- self.assertTrue(instance.backoff)
- from supervisor.states import ProcessStates
- self.assertEqual(instance.state, ProcessStates.BACKOFF)
- self.assertEqual(len(L), 2)
- event1, event2 = L
- self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
- self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
- def test_spawn_fork_fail_eagain(self):
- options = DummyOptions()
- options.fork_error = errno.EAGAIN
- config = DummyPConfig(options, 'good', '/good/filename')
- instance = self._makeOne(config)
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.BACKOFF
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(instance.spawnerr,
- "Too many processes in process table to spawn 'good'")
- self.assertEqual(options.logger.data[0],
- "spawnerr: Too many processes in process table to spawn 'good'")
- self.assertEqual(len(options.parent_pipes_closed), 6)
- self.assertEqual(len(options.child_pipes_closed), 6)
- self.assertTrue(instance.delay)
- self.assertTrue(instance.backoff)
- from supervisor.states import ProcessStates
- self.assertEqual(instance.state, ProcessStates.BACKOFF)
- self.assertEqual(len(L), 2)
- event1, event2 = L
- self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
- self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
- def test_spawn_fork_fail_other(self):
- options = DummyOptions()
- options.fork_error = 1
- config = DummyPConfig(options, 'good', '/good/filename')
- instance = self._makeOne(config)
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.BACKOFF
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(instance.spawnerr,
- 'unknown error during fork: EPERM')
- self.assertEqual(options.logger.data[0],
- "spawnerr: unknown error during fork: EPERM")
- self.assertEqual(len(options.parent_pipes_closed), 6)
- self.assertEqual(len(options.child_pipes_closed), 6)
- self.assertTrue(instance.delay)
- self.assertTrue(instance.backoff)
- from supervisor.states import ProcessStates
- self.assertEqual(instance.state, ProcessStates.BACKOFF)
- self.assertEqual(len(L), 2)
- event1, event2 = L
- self.assertEqual(event1.__class__, events.ProcessStateStartingEvent)
- self.assertEqual(event2.__class__, events.ProcessStateBackoffEvent)
- def test_spawn_as_child_setuid_ok(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.parent_pipes_closed, None)
- self.assertEqual(options.child_pipes_closed, None)
- self.assertEqual(options.pgrp_set, True)
- self.assertEqual(len(options.duped), 3)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- self.assertEqual(options.privsdropped, 1)
- self.assertEqual(options.execv_args,
- ('/good/filename', ['/good/filename']) )
- self.assertEqual(options.execve_called, True)
- # if the real execve() succeeds, the code that writes the
- # "was not spawned" message won't be reached. this assertion
- # is to test that no other errors were written.
- self.assertEqual(options.written,
- {2: "supervisor: child process was not spawned\n"})
- def test_spawn_as_child_setuid_fail(self):
- options = DummyOptions()
- options.forkpid = 0
- options.setuid_msg = 'failure reason'
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.parent_pipes_closed, None)
- self.assertEqual(options.child_pipes_closed, None)
- self.assertEqual(options.pgrp_set, True)
- self.assertEqual(len(options.duped), 3)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- self.assertEqual(options.written,
- {2: "supervisor: couldn't setuid to 1: failure reason\n"
- "supervisor: child process was not spawned\n"})
- self.assertEqual(options.privsdropped, None)
- self.assertEqual(options.execve_called, False)
- self.assertEqual(options._exitcode, 127)
- def test_spawn_as_child_cwd_ok(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'good', '/good/filename',
- directory='/tmp')
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.parent_pipes_closed, None)
- self.assertEqual(options.child_pipes_closed, None)
- self.assertEqual(options.pgrp_set, True)
- self.assertEqual(len(options.duped), 3)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- self.assertEqual(options.execv_args,
- ('/good/filename', ['/good/filename']) )
- self.assertEqual(options.changed_directory, True)
- self.assertEqual(options.execve_called, True)
- # if the real execve() succeeds, the code that writes the
- # "was not spawned" message won't be reached. this assertion
- # is to test that no other errors were written.
- self.assertEqual(options.written,
- {2: "supervisor: child process was not spawned\n"})
- def test_spawn_as_child_sets_umask(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'good', '/good/filename', umask=002)
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.execv_args,
- ('/good/filename', ['/good/filename']) )
- self.assertEqual(options.umaskset, 002)
- self.assertEqual(options.execve_called, True)
- # if the real execve() succeeds, the code that writes the
- # "was not spawned" message won't be reached. this assertion
- # is to test that no other errors were written.
- self.assertEqual(options.written,
- {2: "supervisor: child process was not spawned\n"})
- def test_spawn_as_child_cwd_fail(self):
- options = DummyOptions()
- options.forkpid = 0
- options.chdir_error = 2
- config = DummyPConfig(options, 'good', '/good/filename',
- directory='/tmp')
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.parent_pipes_closed, None)
- self.assertEqual(options.child_pipes_closed, None)
- self.assertEqual(options.pgrp_set, True)
- self.assertEqual(len(options.duped), 3)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- self.assertEqual(options.execv_args, None)
- out = {2: "supervisor: couldn't chdir to /tmp: ENOENT\n"
- "supervisor: child process was not spawned\n"}
- self.assertEqual(options.written, out)
- self.assertEqual(options._exitcode, 127)
- self.assertEqual(options.changed_directory, False)
- self.assertEqual(options.execve_called, False)
- def test_spawn_as_child_execv_fail_oserror(self):
- options = DummyOptions()
- options.forkpid = 0
- options.execv_error = 1
- config = DummyPConfig(options, 'good', '/good/filename')
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.parent_pipes_closed, None)
- self.assertEqual(options.child_pipes_closed, None)
- self.assertEqual(options.pgrp_set, True)
- self.assertEqual(len(options.duped), 3)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- out = {2: "supervisor: couldn't exec /good/filename: EPERM\n"
- "supervisor: child process was not spawned\n"}
- self.assertEqual(options.written, out)
- self.assertEqual(options.privsdropped, None)
- self.assertEqual(options._exitcode, 127)
- def test_spawn_as_child_execv_fail_runtime_error(self):
- options = DummyOptions()
- options.forkpid = 0
- options.execv_error = 2
- config = DummyPConfig(options, 'good', '/good/filename')
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.parent_pipes_closed, None)
- self.assertEqual(options.child_pipes_closed, None)
- self.assertEqual(options.pgrp_set, True)
- self.assertEqual(len(options.duped), 3)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- msg = options.written[2] # dict, 2 is fd #
- head = "supervisor: couldn't exec /good/filename:"
- self.assertTrue(msg.startswith(head))
- self.assertTrue("exceptions.RuntimeError" in msg)
- self.assertEqual(options.privsdropped, None)
- self.assertEqual(options._exitcode, 127)
- def test_spawn_as_child_uses_pconfig_environment(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'cat', '/bin/cat',
- environment={'_TEST_':'1'})
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.execv_args, ('/bin/cat', ['/bin/cat']) )
- self.assertEqual(options.execv_environment['_TEST_'], '1')
- def test_spawn_as_child_environment_supervisor_envvars(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'cat', '/bin/cat')
- instance = self._makeOne(config)
- class Dummy:
- name = 'dummy'
- instance.group = Dummy()
- instance.group.config = Dummy()
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.execv_args, ('/bin/cat', ['/bin/cat']) )
- self.assertEqual(
- options.execv_environment['SUPERVISOR_ENABLED'], '1')
- self.assertEqual(
- options.execv_environment['SUPERVISOR_PROCESS_NAME'], 'cat')
- self.assertEqual(
- options.execv_environment['SUPERVISOR_GROUP_NAME'], 'dummy')
- self.assertEqual(
- options.execv_environment['SUPERVISOR_SERVER_URL'],
- 'http://localhost:9001')
- def test_spawn_as_child_stderr_redirected(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- config.redirect_stderr = True
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(options.parent_pipes_closed, None)
- self.assertEqual(options.child_pipes_closed, None)
- self.assertEqual(options.pgrp_set, True)
- self.assertEqual(len(options.duped), 2)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- self.assertEqual(options.privsdropped, 1)
- self.assertEqual(options.execv_args,
- ('/good/filename', ['/good/filename']) )
- self.assertEqual(options.execve_called, True)
- # if the real execve() succeeds, the code that writes the
- # "was not spawned" message won't be reached. this assertion
- # is to test that no other errors were written.
- self.assertEqual(options.written,
- {2: "supervisor: child process was not spawned\n"})
- def test_spawn_as_parent(self):
- options = DummyOptions()
- options.forkpid = 10
- config = DummyPConfig(options, 'good', '/good/filename')
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, 10)
- self.assertEqual(instance.dispatchers[4].__class__, DummyDispatcher)
- self.assertEqual(instance.dispatchers[5].__class__, DummyDispatcher)
- self.assertEqual(instance.dispatchers[7].__class__, DummyDispatcher)
- self.assertEqual(instance.pipes['stdin'], 4)
- self.assertEqual(instance.pipes['stdout'], 5)
- self.assertEqual(instance.pipes['stderr'], 7)
- self.assertEqual(options.parent_pipes_closed, None)
- self.assertEqual(len(options.child_pipes_closed), 6)
- self.assertEqual(options.logger.data[0], "spawned: 'good' with pid 10")
- self.assertEqual(instance.spawnerr, None)
- self.assertTrue(instance.delay)
- self.assertEqual(instance.config.options.pidhistory[10], instance)
- from supervisor.states import ProcessStates
- self.assertEqual(instance.state, ProcessStates.STARTING)
- def test_spawn_redirect_stderr(self):
- options = DummyOptions()
- options.forkpid = 10
- config = DummyPConfig(options, 'good', '/good/filename',
- redirect_stderr=True)
- instance = self._makeOne(config)
- result = instance.spawn()
- self.assertEqual(result, 10)
- self.assertEqual(instance.dispatchers[4].__class__, DummyDispatcher)
- self.assertEqual(instance.dispatchers[5].__class__, DummyDispatcher)
- self.assertEqual(instance.pipes['stdin'], 4)
- self.assertEqual(instance.pipes['stdout'], 5)
- self.assertEqual(instance.pipes['stderr'], None)
- def test_write(self):
- executable = '/bin/cat'
- options = DummyOptions()
- config = DummyPConfig(options, 'output', executable)
- instance = self._makeOne(config)
- sent = 'a' * (1 << 13)
- self.assertRaises(OSError, instance.write, sent)
- options.forkpid = 1
- instance.spawn()
- instance.write(sent)
- stdin_fd = instance.pipes['stdin']
- self.assertEqual(sent, instance.dispatchers[stdin_fd].input_buffer)
- instance.killing = True
- self.assertRaises(OSError, instance.write, sent)
- def test_write_dispatcher_closed(self):
- executable = '/bin/cat'
- options = DummyOptions()
- config = DummyPConfig(options, 'output', executable)
- instance = self._makeOne(config)
- sent = 'a' * (1 << 13)
- self.assertRaises(OSError, instance.write, sent)
- options.forkpid = 1
- instance.spawn()
- stdin_fd = instance.pipes['stdin']
- instance.dispatchers[stdin_fd].close()
- self.assertRaises(OSError, instance.write, sent)
- def test_write_stdin_fd_none(self):
- executable = '/bin/cat'
- options = DummyOptions()
- config = DummyPConfig(options, 'output', executable)
- instance = self._makeOne(config)
- options.forkpid = 1
- instance.spawn()
- stdin_fd = instance.pipes['stdin']
- instance.dispatchers[stdin_fd].close()
- instance.pipes['stdin'] = None
- try:
- instance.write('foo')
- self.fail('nothing raised')
- except OSError, exc:
- self.assertEqual(exc.args[0], errno.EPIPE)
- self.assertEqual(exc.args[1], 'Process has no stdin channel')
- def test_write_dispatcher_flush_raises_epipe(self):
- executable = '/bin/cat'
- options = DummyOptions()
- config = DummyPConfig(options, 'output', executable)
- instance = self._makeOne(config)
- sent = 'a' * (1 << 13)
- self.assertRaises(OSError, instance.write, sent)
- options.forkpid = 1
- instance.spawn()
- stdin_fd = instance.pipes['stdin']
- instance.dispatchers[stdin_fd].flush_error = errno.EPIPE
- self.assertRaises(OSError, instance.write, sent)
- def _dont_test_spawn_and_kill(self):
- # this is a functional test
- from supervisor.tests.base import makeSpew
- try:
- called = 0
- def foo(*args):
- called = 1
- signal.signal(signal.SIGCHLD, foo)
- executable = makeSpew()
- options = DummyOptions()
- config = DummyPConfig(options, 'spew', executable)
- instance = self._makeOne(config)
- result = instance.spawn()
- msg = options.logger.data[0]
- self.assertTrue(msg.startswith("spawned: 'spew' with pid"))
- self.assertEqual(len(instance.pipes), 6)
- self.assertTrue(instance.pid)
- self.assertEqual(instance.pid, result)
- origpid = instance.pid
- while 1:
- try:
- data = os.popen('ps').read()
- break
- except IOError, why:
- if why.args[0] != errno.EINTR:
- raise
- # try again ;-)
- time.sleep(0.1) # arbitrary, race condition possible
- self.assertTrue(data.find(repr(origpid)) != -1 )
- msg = instance.kill(signal.SIGTERM)
- time.sleep(0.1) # arbitrary, race condition possible
- self.assertEqual(msg, None)
- pid, sts = os.waitpid(-1, os.WNOHANG)
- data = os.popen('ps').read()
- self.assertEqual(data.find(repr(origpid)), -1) # dubious
- finally:
- try:
- os.remove(executable)
- except:
- pass
- signal.signal(signal.SIGCHLD, signal.SIG_DFL)
- def test_stop(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.pid = 11
- dispatcher = DummyDispatcher(writable=True)
- instance.dispatchers = {'foo':dispatcher}
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.RUNNING
- instance.laststopreport = time.time()
- instance.stop()
- self.assertEqual(instance.administrative_stop, 1)
- self.assertEqual(instance.laststopreport, 0)
- self.assertTrue(instance.delay)
- self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
- 'signal SIGTERM')
- self.assertEqual(instance.killing, 1)
- self.assertEqual(options.kills[11], signal.SIGTERM)
- def test_stop_not_in_stoppable_state_error(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.pid = 11
- dispatcher = DummyDispatcher(writable=True)
- instance.dispatchers = {'foo':dispatcher}
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.STOPPED
- try:
- instance.stop()
- self.fail('nothing raised')
- except AssertionError, exc:
- self.assertEqual(exc.args[0], 'Assertion failed for test: '
- 'STOPPED not in RUNNING STARTING STOPPING')
- def test_stop_report_logs_nothing_if_not_stopping_state(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.pid = 11
- dispatcher = DummyDispatcher(writable=True)
- instance.dispatchers = {'foo':dispatcher}
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.STOPPED
- instance.stop_report()
- self.assertEqual(len(options.logger.data), 0)
- def test_stop_report_logs_throttled_by_laststopreport(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.pid = 11
- dispatcher = DummyDispatcher(writable=True)
- instance.dispatchers = {'foo':dispatcher}
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.STOPPING
- self.assertEqual(instance.laststopreport, 0)
- instance.stop_report()
- self.assertEqual(len(options.logger.data), 1)
- self.assertEqual(options.logger.data[0], 'waiting for test to stop')
- self.assertNotEqual(instance.laststopreport, 0)
- instance.stop_report()
- self.assertEqual(len(options.logger.data), 1) # throttled
- def test_give_up(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- L = []
- from supervisor.states import ProcessStates
- from supervisor import events
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- instance.state = ProcessStates.BACKOFF
- instance.give_up()
- self.assertEqual(instance.system_stop, 1)
- self.assertFalse(instance.delay)
- self.assertFalse(instance.backoff)
- self.assertEqual(instance.state, ProcessStates.FATAL)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateFatalEvent)
- def test_kill_nopid(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.kill(signal.SIGTERM)
- self.assertEqual(options.logger.data[0],
- 'attempted to kill test with sig SIGTERM but it wasn\'t running')
- self.assertEqual(instance.killing, 0)
- def test_kill_error(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- options.kill_error = 1
- instance = self._makeOne(config)
- L = []
- from supervisor.states import ProcessStates
- from supervisor import events
- events.subscribe(events.ProcessStateEvent,
- lambda x: L.append(x))
- instance.pid = 11
- instance.state = ProcessStates.RUNNING
- instance.kill(signal.SIGTERM)
- self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
- 'signal SIGTERM')
- self.assertTrue(options.logger.data[1].startswith(
- 'unknown problem killing test'))
- self.assertEqual(instance.killing, 0)
- self.assertEqual(len(L), 2)
- event1 = L[0]
- event2 = L[1]
- self.assertEqual(event1.__class__, events.ProcessStateStoppingEvent)
- self.assertEqual(event2.__class__, events.ProcessStateUnknownEvent)
- def test_kill_from_starting(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.pid = 11
- L = []
- from supervisor.states import ProcessStates
- from supervisor import events
- events.subscribe(events.ProcessStateEvent,lambda x: L.append(x))
- instance.state = ProcessStates.STARTING
- instance.kill(signal.SIGTERM)
- self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
- 'signal SIGTERM')
- self.assertEqual(instance.killing, 1)
- self.assertEqual(options.kills[11], signal.SIGTERM)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateStoppingEvent)
- def test_kill_from_running(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.pid = 11
- L = []
- from supervisor.states import ProcessStates
- from supervisor import events
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- instance.state = ProcessStates.RUNNING
- instance.kill(signal.SIGTERM)
- self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
- 'signal SIGTERM')
- self.assertEqual(instance.killing, 1)
- self.assertEqual(options.kills[11], signal.SIGTERM)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateStoppingEvent)
- def test_kill_from_stopping(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.pid = 11
- L = []
- from supervisor.states import ProcessStates
- from supervisor import events
- events.subscribe(events.Event,lambda x: L.append(x))
- instance.state = ProcessStates.STOPPING
- instance.kill(signal.SIGKILL)
- self.assertEqual(options.logger.data[0], 'killing test (pid 11) with '
- 'signal SIGKILL')
- self.assertEqual(instance.killing, 1)
- self.assertEqual(options.kills[11], signal.SIGKILL)
- self.assertEqual(L, []) # no event because we didn't change state
- def test_kill_from_backoff(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- L = []
- from supervisor.states import ProcessStates
- from supervisor import events
- events.subscribe(events.Event, L.append)
- instance.state = ProcessStates.BACKOFF
- instance.kill(signal.SIGKILL)
- self.assertEqual(options.logger.data[0],
- 'Attempted to kill test, which is in BACKOFF state.')
- self.assertEqual(instance.killing, 0)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateStoppedEvent)
- def test_kill_from_stopping_w_killasgroup(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test', killasgroup=True)
- instance = self._makeOne(config)
- instance.pid = 11
- L = []
- from supervisor.states import ProcessStates
- from supervisor import events
- events.subscribe(events.Event,lambda x: L.append(x))
- instance.state = ProcessStates.STOPPING
- instance.kill(signal.SIGKILL)
- self.assertEqual(options.logger.data[0], 'killing test (pid 11) '
- 'process group with signal SIGKILL')
- self.assertEqual(instance.killing, 1)
- self.assertEqual(options.kills[-11], signal.SIGKILL)
- self.assertEqual(L, []) # no event because we didn't change state
- def test_stopasgroup(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test', stopasgroup=True)
- instance = self._makeOne(config)
- instance.pid = 11
- L = []
- from supervisor.states import ProcessStates
- from supervisor import events
- events.subscribe(events.Event,lambda x: L.append(x))
- instance.state = ProcessStates.RUNNING
- instance.kill(signal.SIGTERM)
- self.assertEqual(options.logger.data[0], 'killing test (pid 11) '
- 'process group with signal SIGTERM')
- self.assertEqual(instance.killing, 1)
- self.assertEqual(options.kills[-11], signal.SIGTERM)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateStoppingEvent)
- self.assertEqual(event.extra_values, [('pid', 11)])
- self.assertEqual(event.from_state, ProcessStates.RUNNING)
- def test_finish_stopping_state(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere',
- stdout_logfile='/tmp/foo')
- instance = self._makeOne(config)
- instance.waitstatus = (123, 1) # pid, waitstatus
- instance.config.options.pidhistory[123] = instance
- instance.killing = True
- pipes = {'stdout':'','stderr':''}
- instance.pipes = pipes
- from supervisor.states import ProcessStates
- from supervisor import events
- instance.state = ProcessStates.STOPPING
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- instance.pid = 123
- instance.finish(123, 1)
- self.assertFalse(instance.killing)
- self.assertEqual(instance.pid, 0)
- self.assertEqual(options.parent_pipes_closed, pipes)
- self.assertEqual(instance.pipes, {})
- self.assertEqual(instance.dispatchers, {})
- self.assertEqual(options.logger.data[0], 'stopped: notthere '
- '(terminated by SIGHUP)')
- self.assertEqual(instance.exitstatus, -1)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateStoppedEvent)
- self.assertEqual(event.extra_values, [('pid', 123)])
- self.assertEqual(event.from_state, ProcessStates.STOPPING)
- def test_finish_running_state_exit_expected(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere',
- stdout_logfile='/tmp/foo')
- instance = self._makeOne(config)
- instance.config.options.pidhistory[123] = instance
- pipes = {'stdout':'','stderr':''}
- instance.pipes = pipes
- instance.config.exitcodes =[-1]
- from supervisor.states import ProcessStates
- from supervisor import events
- instance.state = ProcessStates.RUNNING
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- instance.pid = 123
- instance.finish(123, 1)
- self.assertFalse(instance.killing)
- self.assertEqual(instance.pid, 0)
- self.assertEqual(options.parent_pipes_closed, pipes)
- self.assertEqual(instance.pipes, {})
- self.assertEqual(instance.dispatchers, {})
- self.assertEqual(options.logger.data[0],
- 'exited: notthere (terminated by SIGHUP; expected)')
- self.assertEqual(instance.exitstatus, -1)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__,
- events.ProcessStateExitedEvent)
- self.assertEqual(event.expected, True)
- self.assertEqual(event.extra_values, [('expected', True), ('pid', 123)])
- self.assertEqual(event.from_state, ProcessStates.RUNNING)
- def test_finish_starting_state_laststart_in_future(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere',
- stdout_logfile='/tmp/foo')
- instance = self._makeOne(config)
- instance.config.options.pidhistory[123] = instance
- pipes = {'stdout':'','stderr':''}
- instance.pipes = pipes
- instance.config.exitcodes =[-1]
- instance.laststart = time.time() + 3600 # 1 hour into the future
- from supervisor.states import ProcessStates
- from supervisor import events
- instance.state = ProcessStates.STARTING
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- instance.pid = 123
- instance.finish(123, 1)
- self.assertFalse(instance.killing)
- self.assertEqual(instance.pid, 0)
- self.assertEqual(options.parent_pipes_closed, pipes)
- self.assertEqual(instance.pipes, {})
- self.assertEqual(instance.dispatchers, {})
- self.assertEqual(options.logger.data[0],
- "process 'notthere' (123) laststart time is in the "
- "future, don't know how long process was running so "
- "assuming it did not exit too quickly")
- self.assertEqual(options.logger.data[1],
- 'exited: notthere (terminated by SIGHUP; expected)')
- self.assertEqual(instance.exitstatus, -1)
- self.assertEqual(len(L), 2)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateRunningEvent)
- self.assertEqual(event.expected, True)
- self.assertEqual(event.extra_values, [('pid', 123)])
- self.assertEqual(event.from_state, ProcessStates.STARTING)
- event = L[1]
- self.assertEqual(event.__class__, events.ProcessStateExitedEvent)
- self.assertEqual(event.expected, True)
- self.assertEqual(event.extra_values, [('expected', True), ('pid', 123)])
- self.assertEqual(event.from_state, ProcessStates.RUNNING)
- def test_finish_starting_state_exited_too_quickly(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere',
- stdout_logfile='/tmp/foo', startsecs=10)
- instance = self._makeOne(config)
- instance.config.options.pidhistory[123] = instance
- pipes = {'stdout':'','stderr':''}
- instance.pipes = pipes
- instance.config.exitcodes =[-1]
- instance.laststart = time.time()
- from supervisor.states import ProcessStates
- from supervisor import events
- instance.state = ProcessStates.STARTING
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- instance.pid = 123
- instance.finish(123, 1)
- self.assertFalse(instance.killing)
- self.assertEqual(instance.pid, 0)
- self.assertEqual(options.parent_pipes_closed, pipes)
- self.assertEqual(instance.pipes, {})
- self.assertEqual(instance.dispatchers, {})
- self.assertEqual(options.logger.data[0],
- 'exited: notthere (terminated by SIGHUP; not expected)')
- self.assertEqual(instance.exitstatus, None)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateBackoffEvent)
- self.assertEqual(event.from_state, ProcessStates.STARTING)
- def test_finish_running_state_laststart_in_future(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere',
- stdout_logfile='/tmp/foo')
- instance = self._makeOne(config)
- instance.config.options.pidhistory[123] = instance
- pipes = {'stdout':'','stderr':''}
- instance.pipes = pipes
- instance.config.exitcodes =[-1]
- instance.laststart = time.time() + 3600 # 1 hour into the future
- from supervisor.states import ProcessStates
- from supervisor import events
- instance.state = ProcessStates.RUNNING
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- instance.pid = 123
- instance.finish(123, 1)
- self.assertFalse(instance.killing)
- self.assertEqual(instance.pid, 0)
- self.assertEqual(options.parent_pipes_closed, pipes)
- self.assertEqual(instance.pipes, {})
- self.assertEqual(instance.dispatchers, {})
- self.assertEqual(options.logger.data[0],
- "process 'notthere' (123) laststart time is in the "
- "future, don't know how long process was running so "
- "assuming it did not exit too quickly")
- self.assertEqual(options.logger.data[1],
- 'exited: notthere (terminated by SIGHUP; expected)')
- self.assertEqual(instance.exitstatus, -1)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__,
- events.ProcessStateExitedEvent)
- self.assertEqual(event.expected, True)
- self.assertEqual(event.extra_values, [('expected', True), ('pid', 123)])
- self.assertEqual(event.from_state, ProcessStates.RUNNING)
- def test_finish_with_current_event_sends_rejected(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- events.subscribe(events.EventRejectedEvent, lambda x: L.append(x))
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere',
- stdout_logfile='/tmp/foo', startsecs=10)
- instance = self._makeOne(config)
- from supervisor.states import ProcessStates
- instance.state = ProcessStates.RUNNING
- event = DummyEvent()
- instance.event = event
- instance.finish(123, 1)
- self.assertEqual(len(L), 2)
- event1, event2 = L
- self.assertEqual(event1.__class__,
- events.ProcessStateExitedEvent)
- self.assertEqual(event2.__class__, events.EventRejectedEvent)
- self.assertEqual(event2.process, instance)
- self.assertEqual(event2.event, event)
- self.assertEqual(instance.event, None)
- def test_set_uid_no_uid(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.set_uid()
- self.assertEqual(options.privsdropped, None)
- def test_set_uid(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test', uid=1)
- instance = self._makeOne(config)
- msg = instance.set_uid()
- self.assertEqual(options.privsdropped, 1)
- self.assertEqual(msg, None)
- def test_cmp_bypriority(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'notthere', '/notthere',
- stdout_logfile='/tmp/foo',
- priority=1)
- instance = self._makeOne(config)
- config = DummyPConfig(options, 'notthere1', '/notthere',
- stdout_logfile='/tmp/foo',
- priority=2)
- instance1 = self._makeOne(config)
- config = DummyPConfig(options, 'notthere2', '/notthere',
- stdout_logfile='/tmp/foo',
- priority=3)
- instance2 = self._makeOne(config)
- L = [instance2, instance, instance1]
- L.sort()
- self.assertEqual(L, [instance, instance1, instance2])
- def test_transition_stopped_to_starting_supervisor_stopping(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates, SupervisorStates
- options = DummyOptions()
- options.mood = SupervisorStates.SHUTDOWN
- # this should not be spawned, as supervisor is shutting down
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.laststart = 0
- process.state = ProcessStates.STOPPED
- process.transition()
- self.assertEqual(process.state, ProcessStates.STOPPED)
- self.assertEqual(L, [])
- def test_transition_stopped_to_starting_supervisor_running(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates, SupervisorStates
- options = DummyOptions()
- options.mood = SupervisorStates.RUNNING
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.laststart = 0
- process.state = ProcessStates.STOPPED
- process.transition()
- self.assertEqual(process.state, ProcessStates.STARTING)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateStartingEvent)
- def test_transition_exited_to_starting_supervisor_stopping(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates, SupervisorStates
- options = DummyOptions()
- options.mood = SupervisorStates.SHUTDOWN
- # this should not be spawned, as supervisor is shutting down
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- from supervisor.datatypes import RestartUnconditionally
- pconfig.autorestart = RestartUnconditionally
- process = self._makeOne(pconfig)
- process.laststart = 1
- process.system_stop = 1
- process.state = ProcessStates.EXITED
- process.transition()
- self.assertEqual(process.state, ProcessStates.EXITED)
- self.assertEqual(process.system_stop, 1)
- self.assertEqual(L, [])
- def test_transition_exited_to_starting_uncond_supervisor_running(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates
- options = DummyOptions()
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- from supervisor.datatypes import RestartUnconditionally
- pconfig.autorestart = RestartUnconditionally
- process = self._makeOne(pconfig)
- process.laststart = 1
- process.state = ProcessStates.EXITED
- process.transition()
- self.assertEqual(process.state, ProcessStates.STARTING)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateStartingEvent)
- def test_transition_exited_to_starting_condit_supervisor_running(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates
- options = DummyOptions()
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- from supervisor.datatypes import RestartWhenExitUnexpected
- pconfig.autorestart = RestartWhenExitUnexpected
- process = self._makeOne(pconfig)
- process.laststart = 1
- process.state = ProcessStates.EXITED
- process.exitstatus = 'bogus'
- process.transition()
- self.assertEqual(process.state, ProcessStates.STARTING)
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateStartingEvent)
- def test_transition_exited_to_starting_condit_fls_supervisor_running(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates
- options = DummyOptions()
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- from supervisor.datatypes import RestartWhenExitUnexpected
- pconfig.autorestart = RestartWhenExitUnexpected
- process = self._makeOne(pconfig)
- process.laststart = 1
- process.state = ProcessStates.EXITED
- process.exitstatus = 0
- process.transition()
- self.assertEqual(process.state, ProcessStates.EXITED)
- self.assertEqual(L, [])
- def test_transition_backoff_to_starting_supervisor_stopping(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates, SupervisorStates
- options = DummyOptions()
- options.mood = SupervisorStates.SHUTDOWN
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.laststart = 1
- process.delay = 0
- process.backoff = 0
- process.state = ProcessStates.BACKOFF
- process.transition()
- self.assertEqual(process.state, ProcessStates.BACKOFF)
- self.assertEqual(L, [])
- def test_transition_backoff_to_starting_supervisor_running(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates, SupervisorStates
- options = DummyOptions()
- options.mood = SupervisorStates.RUNNING
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.laststart = 1
- process.delay = 0
- process.backoff = 0
- process.state = ProcessStates.BACKOFF
- process.transition()
- self.assertEqual(process.state, ProcessStates.STARTING)
- self.assertEqual(len(L), 1)
- self.assertEqual(L[0].__class__, events.ProcessStateStartingEvent)
- def test_transition_backoff_to_starting_supervisor_running_notyet(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates, SupervisorStates
- options = DummyOptions()
- options.mood = SupervisorStates.RUNNING
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.laststart = 1
- process.delay = sys.maxint
- process.backoff = 0
- process.state = ProcessStates.BACKOFF
- process.transition()
- self.assertEqual(process.state, ProcessStates.BACKOFF)
- self.assertEqual(L, [])
- def test_transition_starting_to_running(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates
- options = DummyOptions()
- # this should go from STARTING to RUNNING via transition()
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.backoff = 1
- process.delay = 1
- process.system_stop = 0
- process.laststart = 1
- process.pid = 1
- process.stdout_buffer = 'abc'
- process.stderr_buffer = 'def'
- process.state = ProcessStates.STARTING
- process.transition()
- # this implies RUNNING
- self.assertEqual(process.backoff, 0)
- self.assertEqual(process.delay, 0)
- self.assertEqual(process.system_stop, 0)
- self.assertEqual(options.logger.data[0],
- 'success: process entered RUNNING state, process has '
- 'stayed up for > than 10 seconds (startsecs)')
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateRunningEvent)
- def test_transition_backoff_to_fatal(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates
- options = DummyOptions()
- # this should go from BACKOFF to FATAL via transition()
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.laststart = 1
- process.backoff = 10000
- process.delay = 1
- process.system_stop = 0
- process.stdout_buffer = 'abc'
- process.stderr_buffer = 'def'
- process.state = ProcessStates.BACKOFF
- process.transition()
- # this implies FATAL
- self.assertEqual(process.backoff, 0)
- self.assertEqual(process.delay, 0)
- self.assertEqual(process.system_stop, 1)
- self.assertEqual(options.logger.data[0],
- 'gave up: process entered FATAL state, too many start'
- ' retries too quickly')
- self.assertEqual(len(L), 1)
- event = L[0]
- self.assertEqual(event.__class__, events.ProcessStateFatalEvent)
- def test_transition_stops_unkillable_notyet(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates
- options = DummyOptions()
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.delay = sys.maxint
- process.state = ProcessStates.STOPPING
- process.transition()
- self.assertEqual(process.state, ProcessStates.STOPPING)
- self.assertEqual(L, [])
- def test_transition_stops_unkillable(self):
- from supervisor import events
- L = []
- events.subscribe(events.ProcessStateEvent, lambda x: L.append(x))
- from supervisor.states import ProcessStates
- options = DummyOptions()
- pconfig = DummyPConfig(options, 'process', 'process','/bin/process')
- process = self._makeOne(pconfig)
- process.delay = 0
- process.pid = 1
- process.killing = 0
- process.state = ProcessStates.STOPPING
- process.transition()
- self.assertEqual(process.killing, 1)
- self.assertNotEqual(process.delay, 0)
- self.assertEqual(process.state, ProcessStates.STOPPING)
- self.assertEqual(options.logger.data[0],
- "killing 'process' (1) with SIGKILL")
- import signal
- self.assertEqual(options.kills[1], signal.SIGKILL)
- self.assertEqual(L, [])
- def test_change_state_doesnt_notify_if_no_state_change(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.state = 10
- self.assertEqual(instance.change_state(10), False)
- def test_change_state_sets_backoff_and_delay(self):
- from supervisor.states import ProcessStates
- options = DummyOptions()
- config = DummyPConfig(options, 'test', '/test')
- instance = self._makeOne(config)
- instance.state = 10
- instance.change_state(ProcessStates.BACKOFF)
- self.assertEqual(instance.backoff, 1)
- self.assertTrue(instance.delay > 0)
- class FastCGISubprocessTests(unittest.TestCase):
- def _getTargetClass(self):
- from supervisor.process import FastCGISubprocess
- return FastCGISubprocess
- def _makeOne(self, *arg, **kw):
- return self._getTargetClass()(*arg, **kw)
- def tearDown(self):
- from supervisor.events import clear
- clear()
- def test_no_group(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- self.assertRaises(NotImplementedError, instance.spawn)
- def test_no_socket_manager(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- instance.group = DummyProcessGroup(DummyPGroupConfig(options))
- self.assertRaises(NotImplementedError, instance.spawn)
- def test_prepare_child_fds(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- sock_config = DummySocketConfig(7)
- gconfig = DummyFCGIGroupConfig(options, 'whatever', 999, None,
- sock_config)
- instance.group = DummyFCGIProcessGroup(gconfig)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(len(options.duped), 3)
- self.assertEqual(options.duped[7], 0)
- self.assertEqual(options.duped[instance.pipes['child_stdout']], 1)
- self.assertEqual(options.duped[instance.pipes['child_stderr']], 2)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- def test_prepare_child_fds_stderr_redirected(self):
- options = DummyOptions()
- options.forkpid = 0
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- config.redirect_stderr = True
- instance = self._makeOne(config)
- sock_config = DummySocketConfig(13)
- gconfig = DummyFCGIGroupConfig(options, 'whatever', 999, None,
- sock_config)
- instance.group = DummyFCGIProcessGroup(gconfig)
- result = instance.spawn()
- self.assertEqual(result, None)
- self.assertEqual(len(options.duped), 2)
- self.assertEqual(options.duped[13], 0)
- self.assertEqual(len(options.fds_closed), options.minfds - 3)
- def test_before_spawn_gets_socket_ref(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- sock_config = DummySocketConfig(7)
- gconfig = DummyFCGIGroupConfig(options, 'whatever', 999, None,
- sock_config)
- instance.group = DummyFCGIProcessGroup(gconfig)
- self.assertTrue(instance.fcgi_sock is None)
- instance.before_spawn()
- self.assertFalse(instance.fcgi_sock is None)
- def test_after_finish_removes_socket_ref(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- instance.fcgi_sock = 'hello'
- instance.after_finish()
- self.assertTrue(instance.fcgi_sock is None)
- #Patch Subprocess.finish() method for this test to verify override
- @patch.object(Subprocess, 'finish', Mock(return_value=sentinel.finish_result))
- def test_finish_override(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- instance.after_finish = Mock()
- result = instance.finish(sentinel.pid, sentinel.sts)
- self.assertEqual(sentinel.finish_result, result,
- 'FastCGISubprocess.finish() did not pass thru result')
- self.assertEqual(1, instance.after_finish.call_count,
- 'FastCGISubprocess.after_finish() not called once')
- finish_mock = Subprocess.finish
- self.assertEqual(1, finish_mock.call_count,
- 'Subprocess.finish() not called once')
- pid_arg = finish_mock.call_args[0][1]
- sts_arg = finish_mock.call_args[0][2]
- self.assertEqual(sentinel.pid, pid_arg,
- 'Subprocess.finish() pid arg was not passed')
- self.assertEqual(sentinel.sts, sts_arg,
- 'Subprocess.finish() sts arg was not passed')
- #Patch Subprocess.spawn() method for this test to verify override
- @patch.object(Subprocess, 'spawn', Mock(return_value=sentinel.ppid))
- def test_spawn_override_success(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- instance.before_spawn = Mock()
- result = instance.spawn()
- self.assertEqual(sentinel.ppid, result,
- 'FastCGISubprocess.spawn() did not pass thru result')
- self.assertEqual(1, instance.before_spawn.call_count,
- 'FastCGISubprocess.before_spawn() not called once')
- spawn_mock = Subprocess.spawn
- self.assertEqual(1, spawn_mock.call_count,
- 'Subprocess.spawn() not called once')
- #Patch Subprocess.spawn() method for this test to verify error handling
- @patch.object(Subprocess, 'spawn', Mock(return_value=None))
- def test_spawn_error(self):
- options = DummyOptions()
- config = DummyPConfig(options, 'good', '/good/filename', uid=1)
- instance = self._makeOne(config)
- instance.before_spawn = Mock()
- instance.fcgi_sock = 'nuke me on error'
- result = instance.spawn()
- self.assertEqual(None, result,
- 'FastCGISubprocess.spawn() did return None on error')
- self.assertEqual(1, instance.before_spawn.call_count,
- 'FastCGISubprocess.before_spawn() not called once')
- self.assertEqual(None, instance.fcgi_sock,
- 'FastCGISubprocess.spawn() did not remove sock ref on error')
- class ProcessGroupBaseTests(unittest.TestCase):
- def _getTargetClass(self):
- from supervisor.process import ProcessGroupBase
- return ProcessGroupBase
- def _makeOne(self, *args, **kw):
- return self._getTargetClass()(*args, **kw)
- def test_get_unstopped_processes(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- group = self._makeOne(gconfig)
- group.processes = { 'process1': process1 }
- unstopped = group.get_unstopped_processes()
- self.assertEqual(unstopped, [process1])
- def test_stop_all(self):
- from supervisor.states import ProcessStates
- options = DummyOptions()
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPED)
- pconfig2 = DummyPConfig(options, 'process2', 'process2','/bin/process2')
- process2 = DummyProcess(pconfig2, state=ProcessStates.RUNNING)
- pconfig3 = DummyPConfig(options, 'process3', 'process3','/bin/process3')
- process3 = DummyProcess(pconfig3, state=ProcessStates.STARTING)
- pconfig4 = DummyPConfig(options, 'process4', 'process4','/bin/process4')
- process4 = DummyProcess(pconfig4, state=ProcessStates.BACKOFF)
- process4.delay = 1000
- process4.backoff = 10
- gconfig = DummyPGroupConfig(
- options,
- pconfigs=[pconfig1, pconfig2, pconfig3, pconfig4])
- group = self._makeOne(gconfig)
- group.processes = {'process1': process1, 'process2': process2,
- 'process3':process3, 'process4':process4}
- group.stop_all()
- self.assertEqual(process1.stop_called, False)
- self.assertEqual(process2.stop_called, True)
- self.assertEqual(process3.stop_called, True)
- self.assertEqual(process4.stop_called, False)
- self.assertEqual(process4.state, ProcessStates.FATAL)
- def test_get_dispatchers(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)
- process1.dispatchers = {4:None}
- pconfig2 = DummyPConfig(options, 'process2', 'process2','/bin/process2')
- process2 = DummyProcess(pconfig2, state=ProcessStates.STOPPING)
- process2.dispatchers = {5:None}
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1, pconfig2])
- group = self._makeOne(gconfig)
- group.processes = { 'process1': process1, 'process2': process2 }
- result= group.get_dispatchers()
- self.assertEqual(result, {4:None, 5:None})
- def test_reopenlogs(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- group = self._makeOne(gconfig)
- group.processes = {'process1': process1}
- group.reopenlogs()
- self.assertEqual(process1.logs_reopened, True)
- def test_removelogs(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- group = self._makeOne(gconfig)
- group.processes = {'process1': process1}
- group.removelogs()
- self.assertEqual(process1.logsremoved, True)
- def test_ordering_and_comparison(self):
- options = DummyOptions()
- gconfig1 = DummyPGroupConfig(options)
- group1 = self._makeOne(gconfig1)
- gconfig2 = DummyPGroupConfig(options)
- group2 = self._makeOne(gconfig2)
- config3 = DummyPGroupConfig(options)
- group3 = self._makeOne(config3)
- group1.config.priority = 5
- group2.config.priority = 1
- group3.config.priority = 5
- L = [group1, group2]
- L.sort()
- self.assertEqual(L, [group2, group1])
- self.assertNotEqual(group1, group2)
- self.assertEqual(group1, group3)
- class ProcessGroupTests(ProcessGroupBaseTests):
- def _getTargetClass(self):
- from supervisor.process import ProcessGroup
- return ProcessGroup
- def test_repr(self):
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- group = self._makeOne(gconfig)
- s = repr(group)
- self.assertTrue(s.startswith(
- '<supervisor.process.ProcessGroup instance at'), s)
- self.assertTrue(s.endswith('named whatever>'), s)
- def test_transition(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- group = self._makeOne(gconfig)
- group.processes = {'process1': process1}
- group.transition()
- self.assertEqual(process1.transitioned, True)
- class FastCGIProcessGroupTests(unittest.TestCase):
- def _getTargetClass(self):
- from supervisor.process import FastCGIProcessGroup
- return FastCGIProcessGroup
- def _makeOne(self, config, **kwargs):
- cls = self._getTargetClass()
- return cls(config, **kwargs)
- def test___init__without_socket_error(self):
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- gconfig.socket_config = None
- class DummySocketManager(object):
- def __init__(self, config, logger): pass
- def get_socket(self): pass
- self._makeOne(gconfig, socketManager=DummySocketManager)
- # doesn't fail with exception
- def test___init__with_socket_error(self):
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- gconfig.socket_config = None
- class DummySocketManager(object):
- def __init__(self, config, logger): pass
- def get_socket(self):
- raise KeyError(5)
- def config(self):
- return 'config'
- self.assertRaises(
- ValueError,
- self._makeOne, gconfig, socketManager=DummySocketManager
- )
- class EventListenerPoolTests(ProcessGroupBaseTests):
- def setUp(self):
- from supervisor.events import clear
- clear()
- def tearDown(self):
- from supervisor.events import clear
- clear()
- def _getTargetClass(self):
- from supervisor.process import EventListenerPool
- return EventListenerPool
- def test_ctor(self):
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- class EventType:
- pass
- gconfig.pool_events = (EventType,)
- pool = self._makeOne(gconfig)
- from supervisor import events
- self.assertEqual(len(events.callbacks), 2)
- self.assertEqual(events.callbacks[0],
- (EventType, pool._acceptEvent))
- self.assertEqual(events.callbacks[1],
- (events.EventRejectedEvent, pool.handle_rejected))
- self.assertEqual(pool.serial, -1)
- def test__eventEnvelope(self):
- options = DummyOptions()
- options.identifier = 'thesupervisorname'
- gconfig = DummyPGroupConfig(options)
- gconfig.name = 'thepoolname'
- pool = self._makeOne(gconfig)
- from supervisor import events
- result = pool._eventEnvelope(
- events.EventTypes.PROCESS_COMMUNICATION_STDOUT, 80, 20, 'payload\n')
- header, payload = result.split('\n', 1)
- headers = header.split()
- self.assertEqual(headers[0], 'ver:3.0')
- self.assertEqual(headers[1], 'server:thesupervisorname')
- self.assertEqual(headers[2], 'serial:80')
- self.assertEqual(headers[3], 'pool:thepoolname')
- self.assertEqual(headers[4], 'poolserial:20')
- self.assertEqual(headers[5], 'eventname:PROCESS_COMMUNICATION_STDOUT')
- self.assertEqual(headers[6], 'len:8')
- self.assertEqual(payload, 'payload\n')
- def test_handle_rejected_no_overflow(self):
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- pool.processes = {'process1': process1}
- pool.event_buffer = [None, None]
- class DummyEvent1:
- serial = 'abc'
- class DummyEvent2:
- process = process1
- event = DummyEvent1()
- dummyevent = DummyEvent2()
- dummyevent.serial = 1
- pool.handle_rejected(dummyevent)
- self.assertEqual(pool.event_buffer, [dummyevent.event, None, None])
- def test_handle_rejected_event_buffer_overflowed(self):
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- gconfig.buffer_size = 3
- pool = self._makeOne(gconfig)
- pool.processes = {'process1': process1}
- class DummyEvent:
- def __init__(self, serial):
- self.serial = serial
- class DummyRejectedEvent:
- def __init__(self, serial):
- self.process = process1
- self.event = DummyEvent(serial)
- event_a = DummyEvent('a')
- event_b = DummyEvent('b')
- event_c = DummyEvent('c')
- rej_event = DummyRejectedEvent('rejected')
- pool.event_buffer = [event_a, event_b, event_c]
- pool.handle_rejected(rej_event)
- serials = [ x.serial for x in pool.event_buffer ]
- # we popped a, and we inserted the rejected event into the 1st pos
- self.assertEqual(serials, ['rejected', 'b', 'c'])
- self.assertEqual(pool.config.options.logger.data[0],
- 'pool whatever event buffer overflowed, discarding event a')
- def test_dispatch_pipe_error(self):
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- from supervisor.states import EventListenerStates
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- process1 = pool.processes['process1']
- process1.write_error = errno.EPIPE
- process1.listener_state = EventListenerStates.READY
- event = DummyEvent()
- pool._acceptEvent(event)
- pool.dispatch()
- self.assertEqual(process1.listener_state, EventListenerStates.READY)
- self.assertEqual(pool.event_buffer, [event])
- self.assertEqual(options.logger.data[0],
- 'rebuffering event abc for pool whatever (bufsize 0)')
- def test__acceptEvent_attaches_pool_serial_and_serial(self):
- from supervisor.process import GlobalSerial
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- process1 = pool.processes['process1']
- from supervisor.states import EventListenerStates
- process1.listener_state = EventListenerStates.READY
- event = DummyEvent(None)
- pool._acceptEvent(event)
- self.assertEqual(event.serial, GlobalSerial.serial)
- self.assertEqual(event.pool_serials['whatever'], pool.serial)
- def test_repr(self):
- options = DummyOptions()
- gconfig = DummyPGroupConfig(options)
- pool = self._makeOne(gconfig)
- s = repr(pool)
- self.assertTrue(s.startswith(
- '<supervisor.process.EventListenerPool instance at'))
- self.assertTrue(s.endswith('named whatever>'))
- def test_transition_nobody_ready(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STARTING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- pool.processes = {'process1': process1}
- event = DummyEvent()
- event.serial = 'a'
- from supervisor.states import EventListenerStates
- process1.listener_state = EventListenerStates.BUSY
- pool._acceptEvent(event)
- pool.transition()
- self.assertEqual(process1.transitioned, True)
- self.assertEqual(pool.event_buffer, [event])
- def test_transition_event_proc_not_running(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STARTING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- pool.processes = {'process1': process1}
- event = DummyEvent()
- from supervisor.states import EventListenerStates
- event.serial = 1
- process1.listener_state = EventListenerStates.READY
- pool._acceptEvent(event)
- pool.transition()
- self.assertEqual(process1.transitioned, True)
- self.assertEqual(pool.event_buffer, [event])
- self.assertEqual(process1.stdin_buffer, '')
- self.assertEqual(process1.listener_state, EventListenerStates.READY)
- def test_transition_event_proc_running(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- pool.processes = {'process1': process1}
- event = DummyEvent()
- from supervisor.states import EventListenerStates
- process1.listener_state = EventListenerStates.READY
- class DummyGroup:
- config = gconfig
- process1.group = DummyGroup
- pool._acceptEvent(event)
- pool.transition()
- self.assertEqual(process1.transitioned, True)
- self.assertEqual(pool.event_buffer, [])
- header, payload = process1.stdin_buffer.split('\n', 1)
- self.assertEqual(payload, 'dummy event', payload)
- self.assertEqual(process1.listener_state, EventListenerStates.BUSY)
- self.assertEqual(process1.event, event)
- def test_transition_event_proc_running_with_dispatch_throttle_notyet(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- pool.dispatch_throttle = 5
- pool.last_dispatch = time.time()
- pool.processes = {'process1': process1}
- event = DummyEvent()
- from supervisor.states import EventListenerStates
- process1.listener_state = EventListenerStates.READY
- class DummyGroup:
- config = gconfig
- process1.group = DummyGroup
- pool._acceptEvent(event)
- pool.transition()
- self.assertEqual(process1.transitioned, True)
- self.assertEqual(pool.event_buffer, [event]) # not popped
- def test_transition_event_proc_running_with_dispatch_throttle_ready(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- pool.dispatch_throttle = 5
- pool.last_dispatch = time.time() - 1000
- pool.processes = {'process1': process1}
- event = DummyEvent()
- from supervisor.states import EventListenerStates
- process1.listener_state = EventListenerStates.READY
- class DummyGroup:
- config = gconfig
- process1.group = DummyGroup
- pool._acceptEvent(event)
- pool.transition()
- self.assertEqual(process1.transitioned, True)
- self.assertEqual(pool.event_buffer, [])
- header, payload = process1.stdin_buffer.split('\n', 1)
- self.assertEqual(payload, 'dummy event', payload)
- self.assertEqual(process1.listener_state, EventListenerStates.BUSY)
- self.assertEqual(process1.event, event)
- def test__dispatchEvent_notready(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.STOPPED)
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- pool.processes = {'process1': process1}
- event = DummyEvent()
- pool._acceptEvent(event)
- self.assertEqual(pool._dispatchEvent(event), False)
- def test__dispatchEvent_proc_write_raises_non_EPIPE_OSError(self):
- options = DummyOptions()
- from supervisor.states import ProcessStates
- pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
- process1 = DummyProcess(pconfig1, state=ProcessStates.RUNNING)
- def raise_epipe(envelope):
- raise OSError(errno.EAGAIN)
- process1.write = raise_epipe
- gconfig = DummyPGroupConfig(options, pconfigs=[pconfig1])
- pool = self._makeOne(gconfig)
- pool.processes = {'process1': process1}
- event = DummyEvent()
- from supervisor.states import EventListenerStates
- process1.listener_state = EventListenerStates.READY
- class DummyGroup:
- config = gconfig
- process1.group = DummyGroup
- pool._acceptEvent(event)
- self.assertRaises(OSError, pool._dispatchEvent, event)
- class test_new_serial(unittest.TestCase):
- def _callFUT(self, inst):
- from supervisor.process import new_serial
- return new_serial(inst)
- def test_inst_serial_is_maxint(self):
- class Inst(object):
- def __init__(self):
- self.serial = sys.maxint
- inst = Inst()
- result = self._callFUT(inst)
- self.assertEqual(inst.serial, 0)
- self.assertEqual(result, 0)
- def test_inst_serial_is_not_maxint(self):
- class Inst(object):
- def __init__(self):
- self.serial = 1
- inst = Inst()
- result = self._callFUT(inst)
- self.assertEqual(inst.serial, 2)
- self.assertEqual(result, 2)
|