class TestStart(unittest.TestCase): """Test various starting scenarios.""" layer = ConfigLayer def setUp(self): self.command = Start() self.command.parser = FakeParser() self.args = FakeArgs() self.args.config = make_config() def tearDown(self): try: with open(config.PID_FILE) as fp: master_pid = int(fp.read()) except OSError as error: if error.errno != errno.ENOENT: raise # There is no master, so just ignore this. return kill_watcher(signal.SIGTERM) os.waitpid(master_pid, 0) def test_force_stale_lock(self): # Fake an acquisition of the master lock by another process, which # subsequently goes stale. Start by finding a free process id. Yes, # this could race, but given that we're starting with our own PID and # searching downward, it's less likely. fake_pid = os.getpid() - 1 while fake_pid > 1: try: os.kill(fake_pid, 0) except OSError as error: if error.errno == errno.ESRCH: break fake_pid -= 1 else: raise RuntimeError('Cannot find free PID') # Lock acquisition logic taken from flufl.lock. claim_file = SEP.join(( config.LOCK_FILE, socket.getfqdn(), str(fake_pid), '0')) with open(config.LOCK_FILE, 'w') as fp: fp.write(claim_file) os.link(config.LOCK_FILE, claim_file) expiration_date = datetime.now() - timedelta(minutes=2) t = time.mktime(expiration_date.timetuple()) os.utime(claim_file, (t, t)) # Start without --force; no master will be running. with suppress(SystemExit): self.command.process(self.args) self.assertIsNone(find_master()) self.assertIn('--force', self.command.parser.message) # Start again, this time with --force. self.args.force = True self.command.process(self.args) pid = find_master() self.assertIsNotNone(pid)
class TestStart(unittest.TestCase): """Test various starting scenarios.""" layer = ConfigLayer def setUp(self): self.command = Start() self.command.parser = FakeParser() self.args = FakeArgs() self.args.config = make_config() def tearDown(self): try: with open(config.PID_FILE) as fp: master_pid = int(fp.read()) except OSError as error: if error.errno != errno.ENOENT: raise # There is no master, so just ignore this. return kill_watcher(signal.SIGTERM) os.waitpid(master_pid, 0) def test_force_stale_lock(self): # Fake an acquisition of the master lock by another process, which # subsequently goes stale. Start by finding a free process id. Yes, # this could race, but given that we're starting with our own PID and # searching downward, it's less likely. fake_pid = os.getpid() - 1 while fake_pid > 1: try: os.kill(fake_pid, 0) except OSError as error: if error.errno == errno.ESRCH: break fake_pid -= 1 else: raise RuntimeError('Cannot find free PID') # Lock acquisition logic taken from flufl.lock. claim_file = SEP.join( (config.LOCK_FILE, socket.getfqdn(), str(fake_pid), '0')) with open(config.LOCK_FILE, 'w') as fp: fp.write(claim_file) os.link(config.LOCK_FILE, claim_file) expiration_date = datetime.now() - timedelta(minutes=2) t = time.mktime(expiration_date.timetuple()) os.utime(claim_file, (t, t)) # Start without --force; no master will be running. try: self.command.process(self.args) except SystemExit: pass self.assertEqual(find_master(), None) self.assertTrue('--force' in self.command.parser.message) # Start again, this time with --force. self.args.force = True self.command.process(self.args) pid = find_master() self.assertNotEqual(pid, None)
class TestBinDir(unittest.TestCase): """Test issues related to bin_dir, e.g. issue #3""" layer = ConfigLayer def setUp(self): self.command = Start() self.command.parser = FakeParser() self.args = FakeArgs() self.args.config = make_config() def test_master_is_elsewhere(self): with ExitStack() as resources: # Patch os.fork() so that we can record the failing child process's # id. We need to wait on the child exiting in either case, and # when it fails, no master.pid will be written. bin_dir = resources.enter_context(TemporaryDirectory()) old_master = os.path.join(config.BIN_DIR, 'master') new_master = os.path.join(bin_dir, 'master') shutil.move(old_master, new_master) resources.callback(shutil.move, new_master, old_master) # Starting mailman should fail because 'master' can't be found. # XXX This will print Errno 2 on the console because we're not # silencing the child process's stderr. self.command.process(self.args) # There should be no pid file. args_config = Configuration() args_config.load(self.args.config) self.assertFalse(os.path.exists(args_config.PID_FILE)) os.wait() def test_master_is_elsewhere_and_findable(self): with ExitStack() as resources: bin_dir = resources.enter_context(TemporaryDirectory()) old_master = os.path.join(config.BIN_DIR, 'master') new_master = os.path.join(bin_dir, 'master') shutil.move(old_master, new_master) resources.enter_context( configuration('paths.testing', bin_dir=bin_dir)) resources.callback(shutil.move, new_master, old_master) # Starting mailman should find master in the new bin_dir. self.command.process(self.args) # There should a pid file and the process it describes should be # killable. We might have to wait until the process has started. master_pid = find_master() self.assertIsNotNone(master_pid, 'master did not start') os.kill(master_pid, signal.SIGTERM) os.waitpid(master_pid, 0)