Merged PeterSurda/teardown-test1:

- reset BMConfigParser before reading config file
  - enable threads name check on posix systems
  - check singleton.lock in TestProcessProto.setUpClass()
This commit is contained in:
Peter Šurda 2020-10-16 14:51:02 +02:00 committed by Dmitri Bogomolov
parent b65f2d154a
commit 96a784b58b
Signed by untrusted user: g1itch
GPG Key ID: 720A756F18DEED13
3 changed files with 113 additions and 28 deletions

View File

@ -124,7 +124,16 @@ class BMConfigParser(ConfigParser.SafeConfigParser):
return [ return [
x for x in BMConfigParser().sections() if x.startswith('BM-')] x for x in BMConfigParser().sections() if x.startswith('BM-')]
def _reset(self):
"""Reset current config. There doesn't appear to be a built in
method for this"""
sections = self.sections()
for x in sections:
self.remove_section(x)
def read(self, filenames): def read(self, filenames):
"""Read config and populate defaults"""
self._reset()
ConfigParser.ConfigParser.read(self, filenames) ConfigParser.ConfigParser.read(self, filenames)
for section in self.sections(): for section in self.sections():
for option in self.options(section): for option in self.options(section):

View File

@ -47,6 +47,7 @@ class TestProcessConfig(TestProcessProto):
def test_config_defaults(self): def test_config_defaults(self):
"""Test settings in the generated config""" """Test settings in the generated config"""
self._stop_process() self._stop_process()
self._kill_process()
config = BMConfigParser() config = BMConfigParser()
config.read(os.path.join(self.home, 'keys.dat')) config.read(os.path.join(self.home, 'keys.dat'))

View File

@ -5,6 +5,7 @@ Common reusable code for tests and tests for pybitmessage process.
import os import os
import signal import signal
import subprocess # nosec import subprocess # nosec
import sys
import tempfile import tempfile
import time import time
import unittest import unittest
@ -25,7 +26,26 @@ class TestProcessProto(unittest.TestCase):
it starts pybitmessage in setUpClass() and stops it in tearDownClass() it starts pybitmessage in setUpClass() and stops it in tearDownClass()
""" """
_process_cmd = ['pybitmessage', '-d'] _process_cmd = ['pybitmessage', '-d']
_threads_count = 15 _threads_count_min = 15
_threads_count_max = 16
_threads_names = [
'PyBitmessage',
'addressGenerato',
'singleWorker',
'SQL',
'objectProcessor',
'singleCleaner',
'singleAPI',
'Asyncore',
'ReceiveQueue_0',
'ReceiveQueue_1',
'ReceiveQueue_2',
'Announcer',
'InvBroadcaster',
'AddrBroadcaster',
'Downloader',
'Uploader'
]
_files = ( _files = (
'keys.dat', 'debug.log', 'messages.dat', 'knownnodes.dat', 'keys.dat', 'debug.log', 'messages.dat', 'knownnodes.dat',
'.api_started', 'unittest.lock' '.api_started', 'unittest.lock'
@ -41,15 +61,28 @@ class TestProcessProto(unittest.TestCase):
cls._cleanup_files() cls._cleanup_files()
os.environ['BITMESSAGE_HOME'] = cls.home os.environ['BITMESSAGE_HOME'] = cls.home
put_signal_file(cls.home, 'unittest.lock') put_signal_file(cls.home, 'unittest.lock')
starttime = int(time.time())
subprocess.Popen( subprocess.Popen(
cls._process_cmd, cls._process_cmd,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # nosec stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # nosec
time.sleep(5) timeout = starttime + 30
try: while time.time() <= timeout:
cls.pid = int(cls._get_readline('singleton.lock')) try:
except TypeError: if os.path.exists(os.path.join(cls.home,
cls.flag = True 'singleton.lock')):
return pstat = os.stat(os.path.join(cls.home, 'singleton.lock'))
if starttime <= pstat.st_mtime and pstat.st_size > 0:
break
except OSError:
break
time.sleep(1)
if time.time() >= timeout:
raise psutil.TimeoutExpired(
"Timeout starting {}".format(cls._process_cmd))
# wait a bit for the program to fully start
# 10 sec should be enough
time.sleep(10)
cls.pid = int(cls._get_readline('singleton.lock'))
cls.process = psutil.Process(cls.pid) cls.process = psutil.Process(cls.pid)
def setUp(self): def setUp(self):
@ -73,6 +106,18 @@ class TestProcessProto(unittest.TestCase):
return False return False
return True return True
@classmethod
def _kill_process(cls, timeout=5):
try:
cls.process.send_signal(signal.SIGKILL)
cls.process.wait(timeout)
# Windows or already dead
except (AttributeError, psutil.NoSuchProcess):
return True
# except psutil.TimeoutExpired propagates, it means something is very
# wrong
return True
@classmethod @classmethod
def _cleanup_files(cls): def _cleanup_files(cls):
cleanup(cls.home, cls._files) cleanup(cls.home, cls._files)
@ -82,7 +127,6 @@ class TestProcessProto(unittest.TestCase):
"""Ensures that pybitmessage stopped and removes files""" """Ensures that pybitmessage stopped and removes files"""
try: try:
if not cls._stop_process(): if not cls._stop_process():
print(open(os.path.join(cls.home, 'debug.log'), 'rb').read())
cls.process.kill() cls.process.kill()
except (psutil.NoSuchProcess, AttributeError): except (psutil.NoSuchProcess, AttributeError):
pass pass
@ -90,25 +134,49 @@ class TestProcessProto(unittest.TestCase):
cls._cleanup_files() cls._cleanup_files()
def _test_threads(self): def _test_threads(self):
# only count for now """Test number and names of threads"""
# because of https://github.com/giampaolo/psutil/issues/613
# PyBitmessage # pylint: disable=invalid-name
# - addressGenerator self.longMessage = True
# - singleWorker
# - SQL try:
# - objectProcessor # using ps for posix platforms
# - singleCleaner # because of https://github.com/giampaolo/psutil/issues/613
# - singleAPI thread_names = subprocess.check_output([
# - Asyncore "ps", "-L", "-o", "comm=", "--pid",
# - ReceiveQueue_0 str(self.process.pid)
# - ReceiveQueue_1 ]).split()
# - ReceiveQueue_2 except: # pylint: disable=bare-except
# - Announcer thread_names = []
# - InvBroadcaster
# - AddrBroadcaster running_threads = len(thread_names)
# - Downloader if 0 < running_threads < 30: # adequacy check
self.assertEqual( extra_threads = []
len(self.process.threads()), self._threads_count) missing_threads = []
for thread_name in thread_names:
if thread_name not in self._threads_names:
extra_threads.append(thread_name)
for thread_name in self._threads_names:
if thread_name not in thread_names:
missing_threads.append(thread_name)
msg = "Missing threads: {}, Extra threads: {}".format(
",".join(missing_threads), ",".join(extra_threads))
else:
running_threads = self.process.num_threads()
if sys.platform.startswith('win'):
running_threads -= 1 # one extra thread on Windows!
msg = "Unexpected running thread count"
self.assertGreaterEqual(
running_threads,
self._threads_count_min,
msg)
self.assertLessEqual(
running_threads,
self._threads_count_max,
msg)
class TestProcessShutdown(TestProcessProto): class TestProcessShutdown(TestProcessProto):
@ -123,7 +191,14 @@ class TestProcessShutdown(TestProcessProto):
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
"""Special teardown because pybitmessage is already stopped""" """Special teardown because pybitmessage is already stopped"""
cls._cleanup_files() try:
if cls.process.is_running():
cls.process.kill()
cls.process.wait(5)
except (psutil.TimeoutExpired, psutil.NoSuchProcess):
pass
finally:
cls._cleanup_files()
class TestProcess(TestProcessProto): class TestProcess(TestProcessProto):