diff --git a/.gitignore b/.gitignore index 8153e385..d3da7bf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ **pyc -**dat **.DS_Store src/build src/dist diff --git a/.travis-kivy.yml b/.travis-kivy.yml new file mode 100644 index 00000000..46ef7963 --- /dev/null +++ b/.travis-kivy.yml @@ -0,0 +1,18 @@ +language: python3.7 +cache: pip3 +dist: bionic +python: + - "3.7" +addons: + apt: + packages: + - build-essential + - libcap-dev + - libmtdev-dev + - xvfb +install: + - pip3 install -r kivy-requirements.txt + - python3 setup.py install + - export PYTHONWARNINGS=all +script: + - xvfb-run python3 tests-kivy.py diff --git a/Dockerfile.kivy-travis b/Dockerfile.kivy-travis new file mode 100644 index 00000000..4dcdf60b --- /dev/null +++ b/Dockerfile.kivy-travis @@ -0,0 +1,64 @@ +FROM ubuntu:bionic AS pybm-kivy-travis-bionic + +ENV DEBIAN_FRONTEND noninteractive +ENV TRAVIS_SKIP_APT_UPDATE 1 + +RUN apt-get update + +RUN apt-get install -yq --no-install-suggests --no-install-recommends \ + software-properties-common + +RUN dpkg --add-architecture i386 + +RUN add-apt-repository ppa:deadsnakes/ppa + +RUN apt-get -y install sudo + +RUN apt-get -y install git + +RUN apt-get install -yq --no-install-suggests --no-install-recommends \ + # travis xenial bionic + python-setuptools libssl-dev libpq-dev python-prctl python-dev \ + python-dev python-virtualenv python-pip virtualenv \ + # Code quality + pylint python-pycodestyle python3-pycodestyle pycodestyle python-flake8 \ + python3-flake8 flake8 python-pyflakes python3-pyflakes pyflakes pyflakes3 \ + curl \ + # Wine + python python-pip wget wine-stable winetricks mingw-w64 wine32 wine64 xvfb \ + # Buildbot + python3-dev libffi-dev python3-setuptools \ + python3-pip \ + # python 3.7 + python3.7 python3.7-dev \ + # .travis-kivy.yml + build-essential libcap-dev tor \ + language-pack-en \ + xclip xsel \ + libzbar-dev + +# cleanup +RUN rm -rf /var/lib/apt/lists/* + +RUN useradd -m -U builder + +RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + +# travis2bash +RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh +RUN chmod +x /usr/local/bin/travis2bash.sh + +# copy sources +COPY . /home/builder/src +RUN chown -R builder.builder /home/builder/src + +USER builder + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR /home/builder/src + + +ENTRYPOINT ["/usr/local/bin/travis2bash.sh", ".travis-kivy.yml"] diff --git a/kivy-requirements.txt b/kivy-requirements.txt new file mode 100644 index 00000000..8d506a5d --- /dev/null +++ b/kivy-requirements.txt @@ -0,0 +1,5 @@ +kivy-garden.qrcode +-e git+https://github.com/kivymd/KivyMD#egg=kivymd +opencv-python +pyzbar +telenium \ No newline at end of file diff --git a/run-kivy-tests-in-docker.sh b/run-kivy-tests-in-docker.sh new file mode 100755 index 00000000..1308aac2 --- /dev/null +++ b/run-kivy-tests-in-docker.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +docker build -t pybm-kivy-travis-bionic -f Dockerfile.kivy-travis . +docker run pybm-kivy-travis-bionic diff --git a/setup.py b/setup.py index 0de2eb8c..5ebace50 100644 --- a/setup.py +++ b/setup.py @@ -69,9 +69,12 @@ if __name__ == "__main__": 'pybitmessage.network', 'pybitmessage.plugins', 'pybitmessage.pyelliptic', - 'pybitmessage.storage' + 'pybitmessage.storage', ] + if sys.version_info[0] == 3: + packages.append('pybitmessage.bitmessagekivy') + # this will silently accept alternative providers of msgpack # if they are already installed diff --git a/src/bitmessagekivy/__init__.py b/src/bitmessagekivy/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/bitmessagekivy/mpybit.py b/src/bitmessagekivy/mpybit.py new file mode 100644 index 00000000..b44b1070 --- /dev/null +++ b/src/bitmessagekivy/mpybit.py @@ -0,0 +1,28 @@ +""" + Dummy implementation for kivy Desktop and android(mobile) interface +""" +# pylint: disable=too-few-public-methods + +from kivy.app import App +from kivy.uix.label import Label + + +class NavigateApp(App): + """Navigation Layout of class""" + + def build(self): + """Method builds the widget""" + # pylint: disable=no-self-use + return Label(text="Hello World !") + + def clickNavDrawer(self): + """method for clicking navigation drawer""" + pass + + def addingtoaddressbook(self): + """method for clicking address book popup""" + pass + + +if __name__ == '__main__': + NavigateApp().run() diff --git a/src/bitmessagekivy/tests/__init__.py b/src/bitmessagekivy/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/bitmessagekivy/tests/common.py b/src/bitmessagekivy/tests/common.py new file mode 100644 index 00000000..553fa020 --- /dev/null +++ b/src/bitmessagekivy/tests/common.py @@ -0,0 +1,37 @@ +""" +This module is used for running test cases ui order. +""" +import unittest + +from pybitmessage import state + + +def make_ordered_test(): + """this method is for comparing and arranging in order""" + order = {} + + def ordered_method(f): + """method for ordering""" + order[f.__name__] = len(order) + return f + + def compare_method(a, b): + """method for comparing order of methods""" + return [1, -1][order[a] < order[b]] + + return ordered_method, compare_method + + +ordered, compare = make_ordered_test() +unittest.defaultTestLoader.sortTestMethodsUsing = compare + + +def skip_screen_checks(x): + """This methos is skipping current screen checks""" + def inner(y): + """Inner function""" + if not state.enableKivy: + return unittest.skip('Kivy not enabled') + else: + x(y) + return inner diff --git a/src/bitmessagekivy/tests/sampleData/keys.dat b/src/bitmessagekivy/tests/sampleData/keys.dat new file mode 100644 index 00000000..940b3e14 --- /dev/null +++ b/src/bitmessagekivy/tests/sampleData/keys.dat @@ -0,0 +1,59 @@ +[bitmessagesettings] +settingsversion = 0 +port = 8444 +timeformat = %%c +blackwhitelist = black +startonlogon = false +minimizetotray = false +showtraynotifications = true +startintray = false +socksproxytype = none +sockshostname = localhost +socksport = 9050 +socksauthentication = false +socksusername = +sockspassword = +keysencrypted = false +messagesencrypted = false +defaultnoncetrialsperbyte = 1000 +defaultpayloadlengthextrabytes = 1000 +minimizeonclose = false +replybelow = False +maxdownloadrate = 0 +maxuploadrate = 0 +stopresendingafterxdays = +stopresendingafterxmonths = +sockslisten = false +userlocale = system +sendoutgoingconnections = True +useidenticons = True +identiconsuffix = qcqQGW6sQtZK +maxacceptablenoncetrialsperbyte = 20000000000 +maxacceptablepayloadlengthextrabytes = 20000000000 +onionhostname = +onionport = 8444 +onionbindip = 127.0.0.1 +smtpdeliver = +hidetrayconnectionnotifications = false +ttl = 367200 + +[BM-2cTpgCn57rYUgqm5BrgmykuV9gK1Ak1THF] +label = test1 +enabled = true +decoy = false +noncetrialsperbyte = 1000 +payloadlengthextrabytes = 1000 +privsigningkey = 5KYCPJ4Vp31UD6k5NWmDKtHhfapW25UJ7V2MjctYxcgL3BpWGA3 +privencryptionkey = 5JLER8q2zyj3KDEgGMv682en2SRUkkWWhUrNuqVYfGNNhHJmdkJ +lastpubkeysendtime = 1623160189 + +[BM-2cVrdzQjCQRqUuET6dc3byVyRTjZcgcJXj] +label = test2 +enabled = true +decoy = false +noncetrialsperbyte = 1000 +payloadlengthextrabytes = 1000 +privsigningkey = 5KhryWvNowFWWA9JRjQnLVStYKwhpKpAG4RtWwzyaQqmK2fTMue +privencryptionkey = 5JKQ9NqX2LRzHBCgyxc1GAL3rDvyDTHPifpL22a6UNN7K6y9BmL +lastpubkeysendtime = 1623160221 + diff --git a/src/bitmessagekivy/tests/sampleData/knownnodes.dat b/src/bitmessagekivy/tests/sampleData/knownnodes.dat new file mode 100644 index 00000000..41ba8a28 --- /dev/null +++ b/src/bitmessagekivy/tests/sampleData/knownnodes.dat @@ -0,0 +1,110 @@ +[ + { + "stream": 1, + "peer": { + "host": "5.45.99.75", + "port": 8444 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + }, + { + "stream": 1, + "peer": { + "host": "75.167.159.54", + "port": 8444 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + }, + { + "stream": 1, + "peer": { + "host": "95.165.168.168", + "port": 8444 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + }, + { + "stream": 1, + "peer": { + "host": "85.180.139.241", + "port": 8444 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + }, + { + "stream": 1, + "peer": { + "host": "158.222.217.190", + "port": 8080 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + }, + { + "stream": 1, + "peer": { + "host": "178.62.12.187", + "port": 8448 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + }, + { + "stream": 1, + "peer": { + "host": "24.188.198.204", + "port": 8111 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + }, + { + "stream": 1, + "peer": { + "host": "109.147.204.113", + "port": 1195 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + }, + { + "stream": 1, + "peer": { + "host": "178.11.46.221", + "port": 8444 + }, + "info": { + "lastseen": 1620741290.255359, + "rating": 0, + "self": false + } + } +] \ No newline at end of file diff --git a/src/bitmessagekivy/tests/sampleData/messages.dat b/src/bitmessagekivy/tests/sampleData/messages.dat new file mode 100644 index 00000000..ad9aae5f Binary files /dev/null and b/src/bitmessagekivy/tests/sampleData/messages.dat differ diff --git a/src/bitmessagekivy/tests/telenium_process.py b/src/bitmessagekivy/tests/telenium_process.py new file mode 100644 index 00000000..d2f630bd --- /dev/null +++ b/src/bitmessagekivy/tests/telenium_process.py @@ -0,0 +1,83 @@ +""" + Base class for telenium test cases which run kivy app as background process +""" + +import os +import shutil +import tempfile +from time import time, sleep + +from telenium.tests import TeleniumTestCase + +_files = ( + 'keys.dat', 'debug.log', 'messages.dat', 'knownnodes.dat', + '.api_started', 'unittest.lock' +) + +tmp_db_file = ( + 'keys.dat', 'messages.dat' +) + + +def cleanup(files=_files): + """Cleanup application files""" + for pfile in files: + try: + os.remove(os.path.join(tempfile.gettempdir(), pfile)) + except OSError: + pass + + +def populate_test_data(): + """Set temp data in tmp directory""" + for file_name in tmp_db_file: + old_source_file = os.path.join( + os.path.abspath(os.path.dirname(__file__)), 'sampleData', file_name) + new_destination_file = os.path.join(os.environ['BITMESSAGE_HOME'], file_name) + shutil.copyfile(old_source_file, new_destination_file) + + +class TeleniumTestProcess(TeleniumTestCase): + """Setting Screen Functionality Testing""" + cmd_entrypoint = [os.path.join(os.path.abspath(os.getcwd()), 'src', 'mock', 'kivy_main.py')] + + @classmethod + def setUpClass(cls): + """Setupclass is for setting temp environment""" + os.environ["BITMESSAGE_HOME"] = tempfile.gettempdir() + populate_test_data() + super(TeleniumTestProcess, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + """Ensures that pybitmessage stopped and removes files""" + # pylint: disable=no-member + try: + cls.cli.app_quit() + except: + pass + + try: + cls.process.kill() + except: + pass + cleanup() + + def drag(self, xpath1, xpath2): + """this method is for dragging""" + self.cli.drag(xpath1, xpath2, 1) + self.cli.sleep(0.3) + + def assertCheckScrollDown(self, selector, timeout=-1): + """this method is for checking scroll""" + start = time() + while True: + scroll_distance = self.cli.getattr(selector, 'scroll_y') + if scroll_distance > 0.0: + self.assertGreaterEqual(scroll_distance, 0.0) + return True + if timeout == -1: + return False + if timeout > 0 and time() - start > timeout: + raise Exception("Timeout") + sleep(0.1) diff --git a/src/bitmessagekivy/tests/test_setting_screen.py b/src/bitmessagekivy/tests/test_setting_screen.py new file mode 100644 index 00000000..5d8f7657 --- /dev/null +++ b/src/bitmessagekivy/tests/test_setting_screen.py @@ -0,0 +1,28 @@ +# pylint: disable=too-few-public-methods + +from .telenium_process import TeleniumTestProcess +from .common import skip_screen_checks + + +class SettingScreen(TeleniumTestProcess): + """Setting Screen Functionality Testing""" + + @skip_screen_checks + def test_setting_screen(self): + """Show Setting Screen""" + print("=====================Test -Show Setting Screen=====================") + self.cli.sleep(3) + # this is for checking current screen + self.assertExists("//Inbox[@name~=\"inbox\"]", timeout=2) + # this is for opening Nav drawer + self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"menu\"]', timeout=2) + # checking state of Nav drawer + self.assertExists("//MDNavigationDrawer[@state~=\"open\"]", timeout=2) + # this is for scrolling Nav drawer + self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") + # assert for checking scroll funcation + self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=3) + # this is for opening setting screen + self.cli.wait_click('//NavigationItem[@text=\"Settings\"]', timeout=1) + # Checking current screen + self.assertExists("//Setting[@name~=\"set\"]", timeout=2) diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index 1f5d4fce..7e82ae89 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -10,8 +10,12 @@ from datetime import datetime from six import string_types from six.moves import configparser -import state -from singleton import Singleton +try: + import state + from singleton import Singleton +except ImportError: + from pybitmessage import state + from pybitmessage.singleton import Singleton SafeConfigParser = configparser.SafeConfigParser diff --git a/src/helper_random.py b/src/helper_random.py index 9a29d5e2..2e6a151b 100644 --- a/src/helper_random.py +++ b/src/helper_random.py @@ -3,7 +3,10 @@ import os import random -from pyelliptic.openssl import OpenSSL +try: + from pyelliptic.openssl import OpenSSL +except ImportError: + from .pyelliptic.openssl import OpenSSL NoneType = type(None) diff --git a/src/mock/__init__.py b/src/mock/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mock/class_addressGenerator.py b/src/mock/class_addressGenerator.py new file mode 100644 index 00000000..907f3810 --- /dev/null +++ b/src/mock/class_addressGenerator.py @@ -0,0 +1,96 @@ +""" +A thread for creating addresses +""" + +import logging +import random +import threading + +from pybitmessage import state +from pybitmessage import queues + +from pybitmessage.bmconfigparser import BMConfigParser + +# from network.threads import StoppableThread + + +fake_addresses = { + 'BM-2cUgQGcTLWAkC6dNsv2Bc8XB3Y1GEesVLV': { + 'privsigningkey': '5KWXwYq1oJMzghUSJaJoWPn8VdeBbhDN8zFot1cBd6ezKKReqBd', + 'privencryptionkey': '5JaeFJs8iPcQT3N8676r3gHKvJ5mTWXy1VLhGCEDqRs4vpvpxV8' + }, + 'BM-2cUd2dm8MVMokruMTcGhhteTpyRZCAMhnA': { + 'privsigningkey': '5JnJ79nkcwjo4Aj7iG8sFMkzYoQqWfpUjTcitTuFJZ1YKHZz98J', + 'privencryptionkey': '5JXgNzTRouFLqSRFJvuHMDHCYPBvTeMPBiHt4Jeb6smNjhUNTYq' + }, + 'BM-2cWyvL54WytfALrJHZqbsDHca5QkrtByAW': { + 'privsigningkey': '5KVE4gLmcfYVicLdgyD4GmnbBTFSnY7Yj2UCuytQqgBBsfwDhpi', + 'privencryptionkey': '5JTw48CGm5CP8fyJUJQMq8HQANQMHDHp2ETUe1dgm6EFpT1egD7' + }, + 'BM-2cTE65PK9Y4AQEkCZbazV86pcQACocnRXd': { + 'privsigningkey': '5KCuyReHx9MB4m5hhEyCWcLEXqc8rxhD1T2VWk8CicPFc8B6LaZ', + 'privencryptionkey': '5KBRpwXdX3n2tP7f583SbFgfzgs6Jemx7qfYqhdH7B1Vhe2jqY6' + }, + 'BM-2cX5z1EgmJ87f2oKAwXdv4VQtEVwr2V3BG': { + 'privsigningkey': '5K5UK7qED7F1uWCVsehudQrszLyMZxFVnP6vN2VDQAjtn5qnyRK', + 'privencryptionkey': '5J5coocoJBX6hy5DFTWKtyEgPmADpSwfQTazMpU7QPeART6oMAu' + } +} + + +class StoppableThread(threading.Thread): + """Base class for application threads with stopThread method""" + name = None + logger = logging.getLogger('default') + + def __init__(self, name=None): + if name: + self.name = name + super(StoppableThread, self).__init__(name=self.name) + self.stop = threading.Event() + self._stopped = False + random.seed() + self.logger.info('Init thread %s', self.name) + + def stopThread(self): + """Stop the thread""" + self._stopped = True + self.stop.set() + + +class FakeAddressGenerator(StoppableThread): + """A thread for creating fake addresses""" + name = "addressGenerator" + address_list = list(fake_addresses.keys()) + + def stopThread(self): + try: + queues.addressGeneratorQueue.put(("stopThread", "data")) + except: + pass + super(FakeAddressGenerator, self).stopThread() + + def run(self): + """ + Process the requests for addresses generation + from `.queues.addressGeneratorQueue` + """ + while state.shutdown == 0: + queueValue = queues.addressGeneratorQueue.get() + try: + address = self.address_list.pop(0) + label = queueValue[3] + + BMConfigParser().add_section(address) + BMConfigParser().set(address, 'label', label) + BMConfigParser().set(address, 'enabled', 'true') + BMConfigParser().set( + address, 'privsigningkey', fake_addresses[address]['privsigningkey']) + BMConfigParser().set( + address, 'privencryptionkey', fake_addresses[address]['privencryptionkey']) + BMConfigParser().save() + + queues.addressGeneratorQueue.task_done() + except IndexError: + self.logger.error( + 'Program error: you can only create 5 fake addresses') diff --git a/src/mock/kivy_main.py b/src/mock/kivy_main.py new file mode 100644 index 00000000..badc1dc1 --- /dev/null +++ b/src/mock/kivy_main.py @@ -0,0 +1,21 @@ +"""Mock kivy app with mock threads.""" + +from pybitmessage import state +from pybitmessage.bitmessagekivy.mpybit import NavigateApp +from class_addressGenerator import FakeAddressGenerator + + +def main(): + """main method for starting threads""" + # Start the address generation thread + addressGeneratorThread = FakeAddressGenerator() + # close the main program even if there are threads left + addressGeneratorThread.daemon = True + addressGeneratorThread.start() + + state.kivyapp = NavigateApp() + state.kivyapp.run() + + +if __name__ == '__main__': + main() diff --git a/src/state.py b/src/state.py index b844bdd3..be81992d 100644 --- a/src/state.py +++ b/src/state.py @@ -32,6 +32,8 @@ enableGUI = True """enable GUI (QT or ncurses)""" enableSTDIO = False """enable STDIO threads""" +enableKivy = False +"""enable kivy app and test cases""" curses = False maximumNumberOfHalfOpenConnections = 0 diff --git a/tests-kivy.py b/tests-kivy.py new file mode 100644 index 00000000..92b10b86 --- /dev/null +++ b/tests-kivy.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +"""Custom tests runner script for tox and python3""" +import random # noseq +import sys +import unittest + + +def unittest_discover(): + """Explicit test suite creation""" + loader = unittest.defaultTestLoader + loader.sortTestMethodsUsing = lambda a, b: random.randint(-1, 1) + return loader.discover('src.bitmessagekivy.tests') + + +if __name__ == "__main__": + result = unittest.TextTestRunner(verbosity=2).run(unittest_discover()) + sys.exit(not result.wasSuccessful())