Merge branch 'v0.6' into v0.6

This commit is contained in:
Marius Kjærstad 2019-03-23 15:14:56 +01:00 committed by GitHub
commit e0efb7fd2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 238 additions and 194 deletions

49
Dockerfile Normal file
View File

@ -0,0 +1,49 @@
# A container for PyBitmessage daemon
FROM ubuntu:xenial
RUN apt-get update
# Install dependencies
RUN apt-get install -yq --no-install-suggests --no-install-recommends \
python-msgpack dh-python python-all-dev build-essential libssl-dev \
python-stdeb fakeroot python-pip libcap-dev
RUN pip install --upgrade pip
EXPOSE 8444 8442
ENV HOME /home/bitmessage
ENV BITMESSAGE_HOME ${HOME}
ENV VER 0.6.3.2
WORKDIR ${HOME}
ADD . ${HOME}
# Install tests dependencies
RUN pip install -r requirements.txt
# Build and install deb
RUN python2 setup.py sdist \
&& py2dsc-deb dist/pybitmessage-${VER}.tar.gz \
&& dpkg -i deb_dist/python-pybitmessage_${VER}-1_amd64.deb
# Create a user
RUN useradd bitmessage && chown -R bitmessage ${HOME}
USER bitmessage
# Generate default config
RUN src/bitmessagemain.py -t && mv keys.dat /tmp
# Clean HOME
RUN rm -rf ${HOME}/*
# Setup environment
RUN mv /tmp/keys.dat . \
&& APIPASS=$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo) \
&& echo "\napiusername: api\napipassword: $APIPASS" \
&& echo "apienabled = true\napiinterface = 0.0.0.0\napiusername = api\napipassword = $APIPASS" >> keys.dat
CMD ["pybitmessage", "-d"]

View File

@ -1,3 +1,4 @@
include COPYING
include README.md
include requirements.txt
recursive-include desktop *

View File

@ -5,23 +5,11 @@ src/addresses.py
"""
# pylint: disable=redefined-outer-name,inconsistent-return-statements
from __future__ import print_function
import hashlib
from binascii import hexlify, unhexlify
from struct import pack, unpack
from debug import logger
from pyelliptic import arithmetic
def convertIntToString(n):
""".. todo:: There is another copy of this function in Bitmessagemain.py"""
a = __builtins__.hex(n)
if a[-1:] == 'L':
a = a[:-1]
if len(a) % 2 == 0:
return unhexlify(a[2:])
return unhexlify('0' + a[2:])
ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
@ -288,7 +276,10 @@ def addBMIfNotPresent(address):
return address if address[:3] == 'BM-' else 'BM-' + address
# TODO: make test case
if __name__ == "__main__":
from pyelliptic import arithmetic
print(
'\nLet us make an address from scratch. Suppose we generate two'
' random 32 byte values and call the first one the signing key'

View File

@ -661,8 +661,7 @@ class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
BMConfigParser().remove_section(address)
with open(state.appdata + 'keys.dat', 'wb') as configfile:
BMConfigParser().write(configfile)
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
queues.UISignalQueue.put(('rerenderMessagelistToLabels', ''))
queues.UISignalQueue.put(('writeNewAddressToTable', ('', '', '')))
shared.reloadMyAddressHashes()
return 'success'

View File

@ -22,10 +22,13 @@ depends.check_dependencies()
import ctypes
import getopt
import multiprocessing
# Used to capture a Ctrl-C keypress so that Bitmessage can shutdown gracefully.
import signal
import socket
import threading
import time
import traceback
from struct import pack
from helper_startup import (
@ -38,7 +41,7 @@ import shared
import knownnodes
import state
import shutdown
import threading
from debug import logger
# Classes
from class_sqlThread import sqlThread
@ -61,9 +64,9 @@ from network.downloadthread import DownloadThread
from network.uploadthread import UploadThread
# Helper Functions
import helper_generic
import helper_threading
def connectToStream(streamNumber):
state.streamsInWhichIAmParticipating.append(streamNumber)
selfInitiatedConnections[streamNumber] = {}
@ -150,6 +153,37 @@ def _fixSocket():
socket.IPV6_V6ONLY = 27
def signal_handler(signum, frame):
"""Single handler for any signal sent to pybitmessage"""
process = multiprocessing.current_process()
thread = threading.current_thread()
logger.error(
'Got signal %i in %s/%s',
signum, process.name, thread.name
)
if process.name == "RegExParser":
# on Windows this isn't triggered, but it's fine,
# it has its own process termination thing
raise SystemExit
if "PoolWorker" in process.name:
raise SystemExit
if thread.name not in ("PyBitmessage", "MainThread"):
return
logger.error("Got signal %i", signum)
# there are possible non-UI variants to run bitmessage which should shutdown
# especially test-mode
if shared.thisapp.daemon or not state.enableGUI:
shutdown.doCleanShutdown()
else:
print('# Thread: %s(%d)' % (thread.name, thread.ident))
for filename, lineno, name, line in traceback.extract_stack(frame):
print('File: "%s", line %d, in %s' % (filename, lineno, name))
if line:
print(' %s' % line.strip())
print('Unfortunately you cannot use Ctrl+C when running the UI'
' because the UI captures the signal.')
# This is a list of current connections (the thread pointers at least)
selfInitiatedConnections = {}
@ -437,8 +471,8 @@ class Main:
os.kill(grandfatherPid, signal.SIGTERM)
def setSignalHandler(self):
signal.signal(signal.SIGINT, helper_generic.signal_handler)
signal.signal(signal.SIGTERM, helper_generic.signal_handler)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# signal.signal(signal.SIGINT, signal.SIG_DFL)
def usage(self):

View File

@ -9,6 +9,7 @@ import random
import string
import sys
import textwrap
import threading
import time
from datetime import datetime, timedelta
from sqlite3 import register_adapter
@ -44,7 +45,6 @@ from account import (
getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount,
GatewayAccount, MailchuckAccount, AccountColor)
import dialogs
from helper_generic import powQueueSize
from network.stats import pendingDownload, pendingUpload
from uisignaler import UISignaler
import knownnodes
@ -107,6 +107,19 @@ def change_translation(newlocale):
logger.error("Failed to set locale to %s", lang, exc_info=True)
# TODO: rewrite
def powQueueSize():
"""Returns the size of queues.workerQueue including current unfinished work"""
queue_len = queues.workerQueue.qsize()
for thread in threading.enumerate():
try:
if thread.name == "singleWorker":
queue_len += thread.busy
except Exception as err:
logger.info('Thread error %s', err)
return queue_len
class MyForm(settingsmixin.SMainWindow):
# the last time that a message arrival sound was played
@ -117,6 +130,7 @@ class MyForm(settingsmixin.SMainWindow):
REPLY_TYPE_SENDER = 0
REPLY_TYPE_CHAN = 1
REPLY_TYPE_UPD = 2
def init_file_menu(self):
QtCore.QObject.connect(self.ui.actionExit, QtCore.SIGNAL(
@ -368,8 +382,6 @@ class MyForm(settingsmixin.SMainWindow):
self.on_context_menuSubscriptions)
def init_sent_popup_menu(self, connectSignal=True):
# Popup menu for the Sent page
self.ui.sentContextMenuToolbar = QtGui.QToolBar()
# Actions
self.actionTrashSentMessage = self.ui.sentContextMenuToolbar.addAction(
_translate(
@ -381,6 +393,9 @@ class MyForm(settingsmixin.SMainWindow):
self.actionForceSend = self.ui.sentContextMenuToolbar.addAction(
_translate(
"MainWindow", "Force send"), self.on_action_ForceSend)
self.actionSentReply = self.ui.sentContextMenuToolbar.addAction(
_translate("MainWindow", "Send update"),
self.on_action_SentReply)
# self.popMenuSent = QtGui.QMenu( self )
# self.popMenuSent.addAction( self.actionSentClipboard )
# self.popMenuSent.addAction( self.actionTrashSentMessage )
@ -2656,15 +2671,13 @@ class MyForm(settingsmixin.SMainWindow):
tableWidget.item(i, 3).setFont(font)
markread = sqlExecuteChunked(
"UPDATE %s SET read = 1 WHERE %s IN({0}) AND read=0" % (
('sent', 'ackdata') if self.getCurrentFolder() == 'sent'
else ('inbox', 'msgid')
), idCount, *msgids
"UPDATE inbox SET read = 1 WHERE msgid IN({0}) AND read=0",
idCount, *msgids
)
if markread > 0:
self.propagateUnreadCount()
# addressAtCurrentRow, self.getCurrentFolder(), None, 0)
# addressAtCurrentRow, self.getCurrentFolder(), None, 0)
def click_NewAddressDialog(self):
dialogs.NewAddressDialog(self)
@ -2999,47 +3012,72 @@ class MyForm(settingsmixin.SMainWindow):
def on_action_InboxReplyChan(self):
self.on_action_InboxReply(self.REPLY_TYPE_CHAN)
def on_action_InboxReply(self, replyType = None):
def on_action_SentReply(self):
self.on_action_InboxReply(self.REPLY_TYPE_UPD)
def on_action_InboxReply(self, reply_type=None):
"""Handle any reply action depending on reply_type"""
# pylint: disable=too-many-locals
tableWidget = self.getCurrentMessagelist()
if not tableWidget:
return
if replyType is None:
replyType = self.REPLY_TYPE_SENDER
if reply_type is None:
reply_type = self.REPLY_TYPE_SENDER
# save this to return back after reply is done
self.replyFromTab = self.ui.tabWidget.currentIndex()
column_to = 1 if reply_type == self.REPLY_TYPE_UPD else 0
column_from = 0 if reply_type == self.REPLY_TYPE_UPD else 1
currentInboxRow = tableWidget.currentRow()
toAddressAtCurrentInboxRow = tableWidget.item(
currentInboxRow, 0).address
currentInboxRow, column_to).address
acct = accountClass(toAddressAtCurrentInboxRow)
fromAddressAtCurrentInboxRow = tableWidget.item(
currentInboxRow, 1).address
currentInboxRow, column_from).address
msgid = str(tableWidget.item(
currentInboxRow, 3).data(QtCore.Qt.UserRole).toPyObject())
queryreturn = sqlQuery(
'''select message from inbox where msgid=?''', msgid)
"SELECT message FROM inbox WHERE msgid=?", msgid
) or sqlQuery("SELECT message FROM sent WHERE ackdata=?", msgid)
if queryreturn != []:
for row in queryreturn:
messageAtCurrentInboxRow, = row
acct.parseMessage(toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow, tableWidget.item(currentInboxRow, 2).subject, messageAtCurrentInboxRow)
acct.parseMessage(
toAddressAtCurrentInboxRow, fromAddressAtCurrentInboxRow,
tableWidget.item(currentInboxRow, 2).subject,
messageAtCurrentInboxRow)
widget = {
'subject': self.ui.lineEditSubject,
'from': self.ui.comboBoxSendFrom,
'message': self.ui.textEditMessage
}
if toAddressAtCurrentInboxRow == str_broadcast_subscribers:
self.ui.tabWidgetSend.setCurrentIndex(
self.ui.tabWidgetSend.indexOf(self.ui.sendDirect)
)
# toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow
elif not BMConfigParser().has_section(toAddressAtCurrentInboxRow):
QtGui.QMessageBox.information(self, _translate("MainWindow", "Address is gone"), _translate(
"MainWindow", "Bitmessage cannot find your address %1. Perhaps you removed it?").arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok)
elif not BMConfigParser().getboolean(toAddressAtCurrentInboxRow, 'enabled'):
QtGui.QMessageBox.information(self, _translate("MainWindow", "Address disabled"), _translate(
"MainWindow", "Error: The address from which you are trying to send is disabled. You\'ll have to enable it on the \'Your Identities\' tab before using it."), QtGui.QMessageBox.Ok)
QtGui.QMessageBox.information(
self, _translate("MainWindow", "Address is gone"),
_translate(
"MainWindow",
"Bitmessage cannot find your address %1. Perhaps you"
" removed it?"
).arg(toAddressAtCurrentInboxRow), QtGui.QMessageBox.Ok)
elif not BMConfigParser().getboolean(
toAddressAtCurrentInboxRow, 'enabled'):
QtGui.QMessageBox.information(
self, _translate("MainWindow", "Address disabled"),
_translate(
"MainWindow",
"Error: The address from which you are trying to send"
" is disabled. You\'ll have to enable it on the"
" \'Your Identities\' tab before using it."
), QtGui.QMessageBox.Ok)
else:
self.setBroadcastEnablementDependingOnWhetherThisIsAMailingListAddress(toAddressAtCurrentInboxRow)
broadcast_tab_index = self.ui.tabWidgetSend.indexOf(
@ -3053,28 +3091,44 @@ class MyForm(settingsmixin.SMainWindow):
}
self.ui.tabWidgetSend.setCurrentIndex(broadcast_tab_index)
toAddressAtCurrentInboxRow = fromAddressAtCurrentInboxRow
if fromAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 1).label or (
isinstance(acct, GatewayAccount) and fromAddressAtCurrentInboxRow == acct.relayAddress):
if fromAddressAtCurrentInboxRow == \
tableWidget.item(currentInboxRow, column_from).label or (
isinstance(acct, GatewayAccount) and
fromAddressAtCurrentInboxRow == acct.relayAddress):
self.ui.lineEditTo.setText(str(acct.fromAddress))
else:
self.ui.lineEditTo.setText(tableWidget.item(currentInboxRow, 1).label + " <" + str(acct.fromAddress) + ">")
self.ui.lineEditTo.setText(
tableWidget.item(currentInboxRow, column_from).label +
" <" + str(acct.fromAddress) + ">"
)
# If the previous message was to a chan then we should send our reply to the chan rather than to the particular person who sent the message.
if acct.type == AccountMixin.CHAN and replyType == self.REPLY_TYPE_CHAN:
logger.debug('original sent to a chan. Setting the to address in the reply to the chan address.')
if toAddressAtCurrentInboxRow == tableWidget.item(currentInboxRow, 0).label:
# If the previous message was to a chan then we should send our
# reply to the chan rather than to the particular person who sent
# the message.
if acct.type == AccountMixin.CHAN and reply_type == self.REPLY_TYPE_CHAN:
logger.debug(
'Original sent to a chan. Setting the to address in the'
' reply to the chan address.')
if toAddressAtCurrentInboxRow == \
tableWidget.item(currentInboxRow, column_to).label:
self.ui.lineEditTo.setText(str(toAddressAtCurrentInboxRow))
else:
self.ui.lineEditTo.setText(tableWidget.item(currentInboxRow, 0).label + " <" + str(acct.toAddress) + ">")
self.ui.lineEditTo.setText(
tableWidget.item(currentInboxRow, column_to).label +
" <" + str(acct.toAddress) + ">"
)
self.setSendFromComboBox(toAddressAtCurrentInboxRow)
quotedText = self.quoted_text(unicode(messageAtCurrentInboxRow, 'utf-8', 'replace'))
quotedText = self.quoted_text(
unicode(messageAtCurrentInboxRow, 'utf-8', 'replace'))
widget['message'].setPlainText(quotedText)
if acct.subject[0:3] in ['Re:', 'RE:']:
widget['subject'].setText(tableWidget.item(currentInboxRow, 2).label)
widget['subject'].setText(
tableWidget.item(currentInboxRow, 2).label)
else:
widget['subject'].setText('Re: ' + tableWidget.item(currentInboxRow, 2).label)
widget['subject'].setText(
'Re: ' + tableWidget.item(currentInboxRow, 2).label)
self.ui.tabWidget.setCurrentIndex(
self.ui.tabWidget.indexOf(self.ui.send)
)
@ -3459,11 +3513,14 @@ class MyForm(settingsmixin.SMainWindow):
for plugin in self.menu_plugins['address']:
self.popMenuSubscriptions.addAction(plugin)
self.popMenuSubscriptions.addSeparator()
self.popMenuSubscriptions.addAction(self.actionMarkAllRead)
if self.getCurrentFolder() != 'sent':
self.popMenuSubscriptions.addAction(self.actionMarkAllRead)
if self.popMenuSubscriptions.isEmpty():
return
self.popMenuSubscriptions.exec_(
self.ui.treeWidgetSubscriptions.mapToGlobal(point))
def widgetConvert (self, widget):
def widgetConvert(self, widget):
if widget == self.ui.tableWidgetInbox:
return self.ui.treeWidgetYourIdentities
elif widget == self.ui.tableWidgetInboxSubscriptions:
@ -3874,8 +3931,10 @@ class MyForm(settingsmixin.SMainWindow):
for plugin in self.menu_plugins['address']:
self.popMenuYourIdentities.addAction(plugin)
self.popMenuYourIdentities.addSeparator()
self.popMenuYourIdentities.addAction(self.actionMarkAllRead)
if self.getCurrentFolder() != 'sent':
self.popMenuYourIdentities.addAction(self.actionMarkAllRead)
if self.popMenuYourIdentities.isEmpty():
return
self.popMenuYourIdentities.exec_(
self.ui.treeWidgetYourIdentities.mapToGlobal(point))
@ -3899,7 +3958,10 @@ class MyForm(settingsmixin.SMainWindow):
for plugin in self.menu_plugins['address']:
self.popMenu.addAction(plugin)
self.popMenu.addSeparator()
self.popMenu.addAction(self.actionMarkAllRead)
if self.getCurrentFolder() != 'sent':
self.popMenu.addAction(self.actionMarkAllRead)
if self.popMenu.isEmpty():
return
self.popMenu.exec_(
self.ui.treeWidgetChans.mapToGlobal(point))
@ -3943,6 +4005,7 @@ class MyForm(settingsmixin.SMainWindow):
self.popMenuSent = QtGui.QMenu(self)
self.popMenuSent.addAction(self.actionSentClipboard)
self.popMenuSent.addAction(self.actionTrashSentMessage)
self.popMenuSent.addAction(self.actionSentReply)
# Check to see if this item is toodifficult and display an additional
# menu option (Force Send) if it is.

View File

@ -651,6 +651,10 @@ class Ui_MainWindow(object):
MainWindow.setTabOrder(self.lineEditSubject, self.textEditMessage)
MainWindow.setTabOrder(self.textEditMessage, self.pushButtonAddSubscription)
# Popup menu actions container for the Sent page
# pylint: disable=attribute-defined-outside-init
self.sentContextMenuToolbar = QtGui.QToolBar()
def updateNetworkSwitchMenuLabel(self, dontconnect=None):
if dontconnect is None:
dontconnect = BMConfigParser().safeGetBoolean(

View File

@ -1,109 +0,0 @@
"""
Helper Generic perform generic operations for threading.
Also perform some conversion operations.
"""
import socket
import sys
import threading
import traceback
import multiprocessing
from binascii import hexlify, unhexlify
import shared
import state
import queues
import shutdown
from debug import logger
def powQueueSize():
curWorkerQueue = queues.workerQueue.qsize()
for thread in threading.enumerate():
try:
if thread.name == "singleWorker":
curWorkerQueue += thread.busy
except Exception as err:
logger.info('Thread error %s', err)
return curWorkerQueue
def convertIntToString(n):
a = __builtins__.hex(n)
if a[-1:] == 'L':
a = a[:-1]
if (len(a) % 2) == 0:
return unhexlify(a[2:])
else:
return unhexlify('0' + a[2:])
def convertStringToInt(s):
return int(hexlify(s), 16)
def allThreadTraceback(frame):
id2name = dict([(th.ident, th.name) for th in threading.enumerate()])
code = []
for threadId, stack in sys._current_frames().items():
code.append(
'\n# Thread: %s(%d)' % (id2name.get(threadId, ''), threadId))
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append(
'File: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(' %s' % (line.strip()))
print('\n'.join(code))
def signal_handler(signal, frame):
process = multiprocessing.current_process()
logger.error(
'Got signal %i in %s/%s',
signal, process.name, threading.current_thread().name
)
if process.name == "RegExParser":
# on Windows this isn't triggered, but it's fine,
# it has its own process termination thing
raise SystemExit
if "PoolWorker" in process.name:
raise SystemExit
if threading.current_thread().name not in ("PyBitmessage", "MainThread"):
return
logger.error("Got signal %i", signal)
if shared.thisapp.daemon or not state.enableGUI: # FIXME redundant?
shutdown.doCleanShutdown()
else:
allThreadTraceback(frame)
print('Unfortunately you cannot use Ctrl+C when running the UI'
' because the UI captures the signal.')
def isHostInPrivateIPRange(host):
if ":" in host: # IPv6
hostAddr = socket.inet_pton(socket.AF_INET6, host)
if hostAddr == ('\x00' * 15) + '\x01':
return False
if hostAddr[0] == '\xFE' and (ord(hostAddr[1]) & 0xc0) == 0x80:
return False
if (ord(hostAddr[0]) & 0xfe) == 0xfc:
return False
elif ".onion" not in host:
if host[:3] == '10.':
return True
if host[:4] == '172.':
if host[6] == '.':
if int(host[4:6]) >= 16 and int(host[4:6]) <= 31:
return True
if host[:8] == '192.168.':
return True
# Multicast
if host[:3] >= 224 and host[:3] <= 239 and host[4] == '.':
return True
return False
def addDataPadding(data, desiredMsgLength=12, paddingChar='\x00'):
return data + paddingChar * (desiredMsgLength - len(data))

View File

@ -130,36 +130,30 @@ class TCPConnection(BMProto, TLSDispatcher): # pylint: disable=too-many-instanc
# We are going to share a maximum number of 1000 addrs (per overlapping
# stream) with our peer. 500 from overlapping streams, 250 from the
# left child stream, and 250 from the right child stream.
maxAddrCount = BMConfigParser().safeGetInt("bitmessagesettings", "maxaddrperstreamsend", 500)
maxAddrCount = BMConfigParser().safeGetInt(
"bitmessagesettings", "maxaddrperstreamsend", 500)
# init
templist = []
addrs = {}
for stream in self.streams:
with knownnodes.knownNodesLock:
if knownnodes.knownNodes[stream]:
filtered = {k: v for k, v in knownnodes.knownNodes[stream].items()
if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)}
elemCount = len(filtered)
if elemCount > maxAddrCount:
elemCount = maxAddrCount
for n, s in enumerate((stream, stream * 2, stream * 2 + 1)):
nodes = knownnodes.knownNodes.get(s)
if not nodes:
continue
# only if more recent than 3 hours
addrs[stream] = helper_random.randomsample(filtered.items(), elemCount)
# sent 250 only if the remote isn't interested in it
if knownnodes.knownNodes[stream * 2] and stream not in self.streams:
filtered = {k: v for k, v in knownnodes.knownNodes[stream * 2].items()
if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)}
elemCount = len(filtered)
if elemCount > maxAddrCount / 2:
elemCount = int(maxAddrCount / 2)
addrs[stream * 2] = helper_random.randomsample(filtered.items(), elemCount)
if knownnodes.knownNodes[(stream * 2) + 1] and stream not in self.streams:
filtered = {k: v for k, v in knownnodes.knownNodes[stream * 2 + 1].items()
if v["lastseen"] > (int(time.time()) - shared.maximumAgeOfNodesThatIAdvertiseToOthers)}
elemCount = len(filtered)
if elemCount > maxAddrCount / 2:
elemCount = int(maxAddrCount / 2)
addrs[stream * 2 + 1] = helper_random.randomsample(filtered.items(), elemCount)
# and having positive or neutral rating
filtered = [
(k, v) for k, v in nodes.iteritems()
if v["lastseen"] > int(time.time()) -
shared.maximumAgeOfNodesThatIAdvertiseToOthers and
v["rating"] >= 0
]
# sent 250 only if the remote isn't interested in it
elemCount = min(
len(filtered),
maxAddrCount / 2 if n else maxAddrCount)
addrs[s] = helper_random.randomsample(filtered, elemCount)
for substream in addrs:
for peer, params in addrs[substream]:
templist.append((substream, peer, params["lastseen"]))

View File

@ -89,6 +89,24 @@ class TestProcessProto(unittest.TestCase):
len(self.process.threads()), self._threads_count)
class TestProcessShutdown(TestProcessProto):
"""Separate test case for SIGTERM"""
def test_shutdown(self):
"""Send to pybitmessage SIGTERM and ensure it stopped"""
self.process.send_signal(signal.SIGTERM)
try:
# longer wait time because it's not a benchmark
self.process.wait(10)
except psutil.TimeoutExpired:
self.fail(
'%s has not stopped in 10 sec' % ' '.join(self._process_cmd))
@classmethod
def tearDownClass(cls):
"""Special teardown because pybitmessage is already stopped"""
cls._cleanup_files()
class TestProcess(TestProcessProto):
"""A test case for pybitmessage process"""
def test_process_name(self):