From 702929615d50c775ba5166eea1a2a63c9619b7ca Mon Sep 17 00:00:00 2001 From: emdee Date: Mon, 26 Sep 2022 00:58:57 +0000 Subject: [PATCH] Add integration test suite --- tests/LICENCE.txt | 19 + tests/socks.py | 391 +++++++++ tests/support_testing.py | 792 +++++++++++++++++ tests/tests_wrapper.py | 1771 ++++++++++++++++++++++++++++++++++++++ wrapper/tox.py | 4 +- 5 files changed, 2975 insertions(+), 2 deletions(-) create mode 100644 tests/LICENCE.txt create mode 100644 tests/socks.py create mode 100644 tests/support_testing.py create mode 100644 tests/tests_wrapper.py diff --git a/tests/LICENCE.txt b/tests/LICENCE.txt new file mode 100644 index 0000000..46bb64f --- /dev/null +++ b/tests/LICENCE.txt @@ -0,0 +1,19 @@ +# @file echo.py +# @author Wei-Ning Huang (AZ) +# +# Copyright (C) 2013 - 2014 Wei-Ning Huang (AZ) +# All Rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/tests/socks.py b/tests/socks.py new file mode 100644 index 0000000..748fa8e --- /dev/null +++ b/tests/socks.py @@ -0,0 +1,391 @@ +"""SocksiPy - Python SOCKS module. +Version 1.00 + +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. + + +This module provides a standard socket-like interface for Python +for tunneling connections through SOCKS proxies. + +""" + +""" + +Minor modifications made by Christopher Gilbert (http://motomastyle.com/) +for use in PyLoris (http://pyloris.sourceforge.net/) + +Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) +mainly to merge bug fixes found in Sourceforge + +Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/) + +""" + +import socket +import struct +import sys + +PROXY_TYPE_SOCKS4 = 1 +PROXY_TYPE_SOCKS5 = 2 +PROXY_TYPE_HTTP = 3 + +_defaultproxy = None +_orgsocket = socket.socket + +class ProxyError(Exception): pass +class GeneralProxyError(ProxyError): pass +class Socks5AuthError(ProxyError): pass +class Socks5Error(ProxyError): pass +class Socks4Error(ProxyError): pass +class HTTPError(ProxyError): pass + +_generalerrors = ("success", + "invalid data", + "not connected", + "not available", + "bad proxy type", + "bad input") + +_socks5errors = ("succeeded", + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported", + "Unknown error") + +_socks5autherrors = ("succeeded", + "authentication is required", + "all offered authentication methods were rejected", + "unknown username or invalid password", + "unknown error") + +_socks4errors = ("request granted", + "request rejected or failed", + "request rejected because SOCKS server cannot connect to identd on the client", + "request rejected because the client program and identd report different user-ids", + "unknown error") + +def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets a default proxy which all further socksocket objects will use, + unless explicitly changed. + """ + global _defaultproxy + _defaultproxy = (proxytype, addr, port, rdns, username, password) + +def wrapmodule(module): + """wrapmodule(module) + Attempts to replace a module's socket library with a SOCKS socket. Must set + a default proxy using setdefaultproxy(...) first. + This will only work on modules that import socket directly into the namespace; + most of the Python Standard Library falls into this category. + """ + if _defaultproxy != None: + module.socket.socket = socksocket + else: + raise GeneralProxyError((4, "no proxy specified")) + +class socksocket(socket.socket): + """socksocket([family[, type[, proto]]]) -> socket object + Open a SOCKS enabled socket. The parameters are the same as + those of the standard socket init. In order for SOCKS to work, + you must specify family=AF_INET, type=SOCK_STREAM and proto=0. + """ + + def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None): + _orgsocket.__init__(self, family, type, proto, _sock) + if _defaultproxy != None: + self.__proxy = _defaultproxy + else: + self.__proxy = (None, None, None, None, None, None) + self.__proxysockname = None + self.__proxypeername = None + + def __recvall(self, count): + """__recvall(count) -> data + Receive EXACTLY the number of bytes requested from the socket. + Blocks until the required number of bytes have been received. + """ + data = self.recv(count) + while len(data) < count: + d = self.recv(count-len(data)) + if not d: raise GeneralProxyError((0, "connection closed unexpectedly")) + data = data + d + return data + + def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None): + """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets the proxy to be used. + proxytype - The type of the proxy to be used. Three types + are supported: PROXY_TYPE_SOCKS4 (including socks4a), + PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP + addr - The address of the server (IP or DNS). + port - The port of the server. Defaults to 1080 for SOCKS + servers and 8080 for HTTP proxy servers. + rdns - Should DNS queries be preformed on the remote side + (rather than the local side). The default is True. + Note: This has no effect with SOCKS4 servers. + username - Username to authenticate with to the server. + The default is no authentication. + password - Password to authenticate with to the server. + Only relevant when username is also provided. + """ + self.__proxy = (proxytype, addr, port, rdns, username, password) + + def __negotiatesocks5(self, destaddr, destport): + """__negotiatesocks5(self,destaddr,destport) + Negotiates a connection through a SOCKS5 server. + """ + # First we'll send the authentication packages we support. + if (self.__proxy[4]!=None) and (self.__proxy[5]!=None): + # The username/password details were supplied to the + # setproxy method so we support the USERNAME/PASSWORD + # authentication (in addition to the standard none). + self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02)) + else: + # No username/password were entered, therefore we + # only support connections with no authentication. + self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00)) + # We'll receive the server's response to determine which + # method was selected + chosenauth = self.__recvall(2) + if chosenauth[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + # Check the chosen authentication method + if chosenauth[1:2] == chr(0x00).encode(): + # No authentication is required + pass + elif chosenauth[1:2] == chr(0x02).encode(): + # Okay, we need to perform a basic username/password + # authentication. + self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5]) + authstat = self.__recvall(2) + if authstat[0:1] != chr(0x01).encode(): + # Bad response + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if authstat[1:2] != chr(0x00).encode(): + # Authentication failed + self.close() + raise Socks5AuthError((3, _socks5autherrors[3])) + # Authentication succeeded + else: + # Reaching here is always bad + self.close() + if chosenauth[1] == chr(0xFF).encode(): + raise Socks5AuthError((2, _socks5autherrors[2])) + else: + raise GeneralProxyError((1, _generalerrors[1])) + # Now we can request the actual connection + req = struct.pack('BBB', 0x05, 0x01, 0x00) + # If the given destination address is an IP address, we'll + # use the IPv4 address request even if remote resolving was specified. + try: + ipaddr = socket.inet_aton(destaddr) + req = req + chr(0x01).encode() + ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. + if self.__proxy[3]: + # Resolve remotely + ipaddr = None + if type(destaddr) != type(b''): # python3 + destaddr_bytes = destaddr.encode(encoding='idna') + else: + destaddr_bytes = destaddr + req = req + chr(0x03).encode() + chr(len(destaddr_bytes)).encode() + destaddr_bytes + else: + # Resolve locally + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + req = req + chr(0x01).encode() + ipaddr + req = req + struct.pack(">H", destport) + self.sendall(req) + # Get the response + resp = self.__recvall(4) + if resp[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + elif resp[1:2] != chr(0x00).encode(): + # Connection failed + self.close() + if ord(resp[1:2])<=8: + raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) + else: + raise Socks5Error((9, _socks5errors[9])) + # Get the bound address/port + elif resp[3:4] == chr(0x01).encode(): + boundaddr = self.__recvall(4) + elif resp[3:4] == chr(0x03).encode(): + resp = resp + self.recv(1) + boundaddr = self.__recvall(ord(resp[4:5])) + else: + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + boundport = struct.unpack(">H", self.__recvall(2))[0] + self.__proxysockname = (boundaddr, boundport) + if ipaddr != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def getproxysockname(self): + """getsockname() -> address info + Returns the bound IP address and port number at the proxy. + """ + return self.__proxysockname + + def getproxypeername(self): + """getproxypeername() -> address info + Returns the IP and port number of the proxy. + """ + return _orgsocket.getpeername(self) + + def getpeername(self): + """getpeername() -> address info + Returns the IP address and port number of the destination + machine (note: getproxypeername returns the proxy) + """ + return self.__proxypeername + + def __negotiatesocks4(self,destaddr,destport): + """__negotiatesocks4(self,destaddr,destport) + Negotiates a connection through a SOCKS4 server. + """ + # Check if the destination address provided is an IP address + rmtrslv = False + try: + ipaddr = socket.inet_aton(destaddr) + except socket.error: + # It's a DNS name. Check where it should be resolved. + if self.__proxy[3]: + ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) + rmtrslv = True + else: + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + # Construct the request packet + req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr + # The username parameter is considered userid for SOCKS4 + if self.__proxy[4] != None: + req = req + self.__proxy[4] + req = req + chr(0x00).encode() + # DNS name if remote resolving is required + # NOTE: This is actually an extension to the SOCKS4 protocol + # called SOCKS4A and may not be supported in all cases. + if rmtrslv: + req = req + destaddr + chr(0x00).encode() + self.sendall(req) + # Get the response from the server + resp = self.__recvall(8) + if resp[0:1] != chr(0x00).encode(): + # Bad data + self.close() + raise GeneralProxyError((1,_generalerrors[1])) + if resp[1:2] != chr(0x5A).encode(): + # Server returned an error + self.close() + if ord(resp[1:2]) in (91, 92, 93): + self.close() + raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) + else: + raise Socks4Error((94, _socks4errors[4])) + # Get the bound address/port + self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0]) + if rmtrslv != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def __negotiatehttp(self, destaddr, destport): + """__negotiatehttp(self,destaddr,destport) + Negotiates a connection through an HTTP server. + """ + # If we need to resolve locally, we do this now + if not self.__proxy[3]: + addr = socket.gethostbyname(destaddr) + else: + addr = destaddr + self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode()) + # We read the response until we get the string "\r\n\r\n" + resp = self.recv(1) + while resp.find("\r\n\r\n".encode()) == -1: + recv = self.recv(1) + if not recv: + raise GeneralProxyError((1, _generalerrors[1])) + resp = resp + recv + # We just need the first line to check if the connection + # was successful + statusline = resp.splitlines()[0].split(" ".encode(), 2) + if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + try: + statuscode = int(statusline[1]) + except ValueError: + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if statuscode != 200: + self.close() + raise HTTPError((statuscode, statusline[2])) + self.__proxysockname = ("0.0.0.0", 0) + self.__proxypeername = (addr, destport) + + def connect(self, destpair): + """connect(self, despair) + Connects to the specified destination through a proxy. + destpar - A tuple of the IP/DNS address and the port number. + (identical to socket's connect). + To select the proxy server use setproxy(). + """ + # Do a minimal input check first + if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int): + raise GeneralProxyError((5, _generalerrors[5])) + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = int(self.__proxy[2]) + else: + portnum = 1080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks5(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_SOCKS4: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self,(self.__proxy[1], portnum)) + self.__negotiatesocks4(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_HTTP: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 8080 + _orgsocket.connect(self,(self.__proxy[1], portnum)) + self.__negotiatehttp(destpair[0], destpair[1]) + elif self.__proxy[0] == None: + _orgsocket.connect(self, (destpair[0], destpair[1])) + else: + raise GeneralProxyError((4, _generalerrors[4])) diff --git a/tests/support_testing.py b/tests/support_testing.py new file mode 100644 index 0000000..1fb0a31 --- /dev/null +++ b/tests/support_testing.py @@ -0,0 +1,792 @@ +# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*- + +import os +import sys +import argparse +import re +import logging +import urllib +import json +from ctypes import * +from io import BytesIO +import time, contextlib +import unittest +from random import Random +random = Random() + +try: + import pycurl +except ImportError: + pycurl = None +try: + import requests +except ImportError: + requests = None + +from PyQt5 import QtCore, QtWidgets +from qtpy.QtWidgets import QApplication + +import wrapper +from wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS +try: + from user_data.settings import get_user_config_path +except ImportError: + get_user_config_path = None + +# LOG=util.log +global LOG +LOG = logging.getLogger('app.'+'ts') + +def LOG_ERROR(l): print('ERRORc: '+l) +def LOG_WARN(l): print('WARNc: ' +l) +def LOG_INFO(l): print('INFOc: ' +l) +def LOG_DEBUG(l): print('DEBUGc: '+l) +def LOG_TRACE(l): pass # print('TRACE+ '+l) + +try: + from trepan.interfaces import server as Mserver + from trepan.api import debug +except: + print('trepan3 TCP server NOT available.') +else: + print('trepan3 TCP server available.') + def trepan_handler(num=None, f=None): + connection_opts={'IO': 'TCP', 'PORT': 6666} + intf = Mserver.ServerInterface(connection_opts=connection_opts) + dbg_opts = { 'interface': intf } + print('Starting TCP server listening on port 6666.') + debug(dbg_opts=dbg_opts) + return + +# self._audio_thread.isAlive +iTHREAD_TIMEOUT = 1 +iTHREAD_SLEEP = 1 +iTHREAD_JOINS = 5 +CONNECT_TIMEOUT = 20.0 + +lToxSamplerates = [8000, 12000, 16000, 24000, 48000] +lToxSampleratesK = [8, 12, 16, 24, 48] +lBOOLEANS = [ + 'local_discovery_enabled', + 'udp_enabled', + 'ipv6_enabled', + 'compact_mode', + 'allow_inline', + 'notifications', + 'sound_notifications', + 'hole_punching_enabled', + 'dht_announcements_enabled', + 'save_history', + 'download_nodes_list'] +lBOOLEANS = [ + 'local_discovery_enabled', + 'udp_enabled', + 'ipv6_enabled', + 'compact_mode', + 'allow_inline', + 'notifications', + 'sound_notifications', + 'hole_punching_enabled', + 'dht_announcements_enabled', + 'save_history', + 'download_nodes_list' + 'core_logging', + ] + + +def bAreWeConnected(): + # FixMe: Linux + sFile = f"/proc/{os.getpid()}/net/route" + if not os.path.isfile(sFile): return None + i = 0 + for elt in open(sFile, "r").readlines(): + if elt.startswith('Iface'): continue + if elt.startswith('lo'): continue + i += 1 + return i > 0 + +lNEW = [ # ngc_jfreeg2: + ('104.244.74.69', 38445, # tox.plastiras.org + '5E47BA1DC3913EB2CBF2D64CE4F23D8BFE5391BFABE5C43C5BAD13F0A414CD77'), + ('172.93.52.70', 33445, + '79CADA4974B0926F286F025CD5FFDF3E654A3758C53E0273ECFC4D12C21DCA48'), + ] + +lGOOD = [ + ("95.79.50.56", + 33445, + "8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832"), + ("85.143.221.42", + 33445, + "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"), + ("tox.verdict.gg", + 33445, + "1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976"), + ("tox.initramfs.io", + 33445, + "3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25"), + ("144.217.167.73", + 33445, + "7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C"), + ("tox.abilinski.com", + 33445, + "10C00EB250C3233E343E2AEBA07115A5C28920E9C8D29492F6D00B29049EDC7E"), + ("tox.novg.net", + 33445, + "D527E5847F8330D628DAB1814F0A422F6DC9D0A300E6C357634EE2DA88C35463"), + ("198.199.98.108", + 33445, + "BEF0CFB37AF874BD17B9A8F9FE64C75521DB95A37D33C5BDB00E9CF58659C04F"), + ('82.196.15.215', 33445, # tox.kurnevsky.net + "82EF82BA33445A1F91A7DB27189ECFC0C013E06E3DA71F588ED692BED625EC23"), + ("81.169.136.229", + 33445, + "E0DB78116AC6500398DDBA2AEEF3220BB116384CAB714C5D1FCD61EA2B69D75E"), + ("tox2.abilinski.com", + 33445, + "7A6098B590BDC73F9723FC59F82B3F9085A64D1B213AAF8E610FD351930D052D"), + ("bg.tox.dcntrlzd.network", + 33445, + "20AD2A54D70E827302CDF5F11D7C43FA0EC987042C36628E64B2B721A1426E36"), + ("46.101.197.175", + 33445, + "CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707"), + ("tox1.mf-net.eu", + 33445, + "B3E5FA80DC8EBD1149AD2AB35ED8B85BD546DEDE261CA593234C619249419506"), + ("tox2.mf-net.eu", + 33445, + "70EA214FDE161E7432530605213F18F7427DC773E276B3E317A07531F548545F"), + ("195.201.7.101", + 33445, + "B84E865125B4EC4C368CD047C72BCE447644A2DC31EF75BD2CDA345BFD310107"), + ("gt.sot-te.ch", + 33445, + "F4F4856F1A311049E0262E9E0A160610284B434F46299988A9CB42BD3D494618"), + ("5.19.249.240", + 38296, + "DA98A4C0CD7473A133E115FEA2EBDAEEA2EF4F79FD69325FC070DA4DE4BA3238"), + ("188.225.9.167", + 33445, + "1911341A83E02503AB1FD6561BD64AF3A9D6C3F12B5FBB656976B2E678644A67"), + ("122.116.39.151", + 33445, + "5716530A10D362867C8E87EE1CD5362A233BAFBBA4CF47FA73B7CAD368BD5E6E"), + ("195.123.208.139", + 33445, + "534A589BA7427C631773D13083570F529238211893640C99D1507300F055FE73"), + ("104.225.141.59", + 43334, + "933BA20B2E258B4C0D475B6DECE90C7E827FE83EFA9655414E7841251B19A72C"), + ("139.162.110.188", + 33445, + "F76A11284547163889DDC89A7738CF271797BF5E5E220643E97AD3C7E7903D55"), + ("198.98.49.206", + 33445, + "28DB44A3CEEE69146469855DFFE5F54DA567F5D65E03EFB1D38BBAEFF2553255"), + ("172.105.109.31", + 33445, + "D46E97CF995DC1820B92B7D899E152A217D36ABE22730FEA4B6BF1BFC06C617C"), + ("ru.tox.dcntrlzd.network", + 33445, + "DBB2E896990ECC383DA2E68A01CA148105E34F9B3B9356F2FE2B5096FDB62762"), + ("91.146.66.26", + 33445, + "B5E7DAC610DBDE55F359C7F8690B294C8E4FCEC4385DE9525DBFA5523EAD9D53"), + ("tox01.ky0uraku.xyz", + 33445, + "FD04EB03ABC5FC5266A93D37B4D6D6171C9931176DC68736629552D8EF0DE174"), + ("tox02.ky0uraku.xyz", + 33445, + "D3D6D7C0C7009FC75406B0A49E475996C8C4F8BCE1E6FC5967DE427F8F600527"), + ("tox.plastiras.org", + 33445, + "8E8B63299B3D520FB377FE5100E65E3322F7AE5B20A0ACED2981769FC5B43725"), + ("141.95.108.234", + 33445, + "2DEF3156812324B1593A6442C937EAE0A8BD98DE529D2D4A7DD4BA6CB3ECF262"), + ("kusoneko.moe", + 33445, + "BE7ED53CD924813507BA711FD40386062E6DC6F790EFA122C78F7CDEEE4B6D1B"), + ("tox2.plastiras.org", + 33445, + "B6626D386BE7E3ACA107B46F48A5C4D522D29281750D44A0CBA6A2721E79C951"), + ("tox.plastiras.org", + 33445, + "8E8B63299B3D520FB377FE5100E65E3322F7AE5B20A0ACED2981769FC5B43725"), + ("141.95.108.234", + 33445, + "2DEF3156812324B1593A6442C937EAE0A8BD98DE529D2D4A7DD4BA6CB3ECF262"), + ("kusoneko.moe", + 33445, + "BE7ED53CD924813507BA711FD40386062E6DC6F790EFA122C78F7CDEEE4B6D1B"), + ('84.22.115.205', 33445, # tox.verdict.gg + '1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976'), +] + +lRELAYS = [ + ("tox02.ky0uraku.xyz",33445, "D3D6D7C0C7009FC75406B0A49E475996C8C4F8BCE1E6FC5967DE427F8F600527"), + ("tox.plastiras.org", 443, "8E8B63299B3D520FB377FE5100E65E3322F7AE5B20A0ACED2981769FC5B43725"), + ("46.101.197.175", 33445, "CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707"), + ("122.116.39.151", 3389, "5716530A10D362867C8E87EE1CD5362A233BAFBBA4CF47FA73B7CAD368BD5E6E"), + ("172.105.109.31", 33445, "D46E97CF995DC1820B92B7D899E152A217D36ABE22730FEA4B6BF1BFC06C617C"), + ("178.62.250.138", 33445, "788236D34978D1D5BD822F0A5BEBD2C53C64CC31CD3149350EE27D4D9A2F9B6B"), + ("198.46.138.44", 33445, "F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67"), + ("51.15.37.145", 33445, "6FC41E2BD381D37E9748FC0E0328CE086AF9598BECC8FEB7DDF2E440475F300E"), + ("130.133.110.14", 33445, "461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F"), + ("198.98.51.198", 33445, "1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F"), + ("108.61.165.198", 33445, "8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832"), + ("185.25.116.107", 33445, "DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43"), + ("5.189.176.217", 5190, "2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F"), + ("217.182.143.254", 2306, "7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147"), + ("104.223.122.15", 33445, "0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A"), + ("d4rk4.ru", 1813, "53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039"), + ("104.233.104.126", 33445, "EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414"), + ("51.254.84.212", 33445, "AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D"), + ("88.99.133.52", 33445, "2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211"), + ("185.58.206.164", 33445, "24156472041E5F220D1FA11D9DF32F7AD697D59845701CDD7BE7D1785EB9DB39"), + ("92.54.84.70", 33445, "5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802"), + ("195.93.190.6", 33445, "FB4CE0DDEFEED45F26917053E5D24BDDA0FA0A3D83A672A9DA2375928B37023D"), + ("tox.uplinklabs.net", 33445, "1A56EA3EDF5DF4C0AEABBF3C2E4E603890F87E983CAC8A0D532A335F2C6E3E1F"), + ("95.215.44.78", 33445, "672DBE27B4ADB9D5FB105A6BB648B2F8FDB89B3323486A7A21968316E012023C"), + ("163.172.136.118", 33445, "2C289F9F37C20D09DA83565588BF496FAB3764853FA38141817A72E3F18ACA0B"), + ("sorunome.de", 33445, "02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46"), + ("37.97.185.116", 33445, "E59A0E71ADA20D35BD1B0957059D7EF7E7792B3D680AE25C6F4DBBA09114D165"), + ("193.124.186.205", 5228, "9906D65F2A4751068A59D30505C5FC8AE1A95E0843AE9372EAFA3BAB6AC16C2C"), + ("80.87.193.193", 33445, "B38255EE4B054924F6D79A5E6E5889EC94B6ADF6FE9906F97A3D01E3D083223A"), + ("hibiki.eve.moe", 33445, "D3EB45181B343C2C222A5BCF72B760638E15ED87904625AAD351C594EEFAE03E"), + ("46.229.52.198", 33445, "813C8F4187833EF0655B10F7752141A352248462A567529A38B6BBF73E979307"), + ("144.217.86.39", 33445, "7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C"), + ("77.37.160.178", 33440, "CE678DEAFA29182EFD1B0C5B9BC6999E5A20B50A1A6EC18B91C8EBB591712416"), + ("85.21.144.224", 33445, "8F738BBC8FA9394670BCAB146C67A507B9907C8E564E28C2B59BEBB2FF68711B"), + ("95.215.46.114", 33445, "5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23"), +] + +lBAD = [('xxx.garbage.zzz', 33445, + '0123456789114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976',), + ('www.google.com', 33445, # no port + '0123456789114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976',), + ('www.google.com', 80, # no ToX port + '0123456789114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976',)] + +lLOCAL = [# /etc/init.d/tox-bootstrapd.conf + ('127.0.0.1', 33446, + 'EC8F7405F79F281569B6C66D9F03490973AB99BC9175C44FBEF4C3428A63B80D'), + ('127.0.0.1', 33447, + 'EC8F7405F79F281569B6C66D9F03490973AB99BC9175C44FBEF4C3428A63B80D'), + ('127.0.0.1', 3389, + 'EC8F7405F79F281569B6C66D9F03490973AB99BC9175C44FBEF4C3428A63B80D'), + # /var/local/src/c-toxcore/build/DHT_bootstrap + ('127.0.0.1', 33445, + '6DB2DEA1785F4A7AEAB52F1F4BC90D6266FBC375DEF316DED59F4E814F97F438'), + # /var/local/src/toxcore/build/DHT_bootstrap + ('127.0.0.1', 33445, + 'A22E68642917F424E5B38E98CACE38A4906B67228D83E507084400B597D5722E'), + ] + +lNO_PROXY = ['localhost', '127.0.0.1'] + +def assert_main_thread(): + # this "instance" method is very useful! + app_thread = QtWidgets.QApplication.instance().thread() + curr_thread = QtCore.QThread.currentThread() + if app_thread != curr_thread: + raise RuntimeError('attempt to call MainWindow.append_message from non-app thread') + +@contextlib.contextmanager +def ignoreStdout(): + devnull = os.open(os.devnull, os.O_WRONLY) + old_stdout = os.dup(1) + sys.stdout.flush() + os.dup2(devnull, 1) + os.close(devnull) + try: + yield + finally: + os.dup2(old_stdout, 1) + os.close(old_stdout) + +@contextlib.contextmanager +def ignoreStderr(): + devnull = os.open(os.devnull, os.O_WRONLY) + old_stderr = os.dup(2) + sys.stderr.flush() + os.dup2(devnull, 2) + os.close(devnull) + try: + yield + finally: + os.dup2(old_stderr, 2) + os.close(old_stderr) + +with ignoreStderr(): + import pyaudio + oPyA = pyaudio.PyAudio() + +def on_log(iTox, level, filename, line, func, message, *data): + # LOG.debug(repr((level, filename, line, func, message,))) + tox_log_cb(level, filename, line, func, message) + +def tox_log_cb(level, filename, line, func, message, *args): + """ + * @param level The severity of the log message. + * @param filename The source file from which the message originated. + * @param line The source line from which the message originated. + * @param func The function from which the message originated. + * @param message The log message. + * @param user_data The user data pointer passed to tox_new in options. + """ + if type(func) == bytes: + func = str(func, 'utf-8') + message = str(message, 'UTF-8') + filename = str(filename, 'UTF-8') + + if filename == 'network.c': + if line == 660: return + # root WARNING 3network.c#944:b'send_packet'attempted to send message with network family 10 (probably IPv6) on IPv4 socket + if line == 944: return + i = message.find('07 = GET_NODES') + if i > 0: + return + if filename == 'TCP_common.c': return + + i = message.find(' | ') + if i > 0: + message = message[:i] + # message = filename +'#' +str(line) +':'+func +' '+message + + name = 'core' + # old level is meaningless + level = 10 # LOG.level + + # LOG._log(LOG.level, f"{level}: {message}", list()) + + i = message.find('(0: OK)') + if i > 0: + level = 10 # LOG.debug + else: + i = message.find('(1: ') + if i > 0: + level = 30 # LOG.warn + else: + level = 20 # LOG.info + + o = LOG.makeRecord(filename, level, func, line, message, list(), None) + LOG.handle(o) + return + + elif level == 1: + LOG.critical(f"{level}: {message}") + elif level == 2: + LOG.error(f"{level}: {message}") + elif level == 3: + LOG.warn(f"{level}: {message}") + elif level == 4: + LOG.info(f"{level}: {message}") + elif level == 5: + LOG.debug(f"{level}: {message}") + else: + LOG.trace(f"{level}: {message}") + +def vAddLoggerCallback(tox_options, callback=None): + if callback is None: + wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback( + tox_options._options_pointer, + POINTER(None)()) + tox_options.self_logger_cb = None + return + + c_callback = CFUNCTYPE(None, c_void_p, c_int, c_char_p, c_int, c_char_p, c_char_p, c_void_p) + tox_options.self_logger_cb = c_callback(callback) + wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback( + tox_options._options_pointer, + tox_options.self_logger_cb) + +def get_video_indexes(): + # Linux + return [str(l[5:]) for l in os.listdir('/dev/') if l.startswith('video')] + +def get_audio(): + input_devices = output_devices = 0 + for i in range(oPyA.get_device_count()): + device = oPyA.get_device_info_by_index(i) + if device["maxInputChannels"]: + input_devices += 1 + if device["maxOutputChannels"]: + output_devices += 1 + # {'index': 21, 'structVersion': 2, 'name': 'default', 'hostApi': 0, 'maxInputChannels': 64, 'maxOutputChannels': 64, 'defaultLowInputLatency': 0.008707482993197279, 'defaultLowOutputLatency': 0.008707482993197279, 'defaultHighInputLatency': 0.034829931972789115, 'defaultHighOutputLatency': 0.034829931972789115, 'defaultSampleRate': 44100.0} + audio = {'input': oPyA.get_default_input_device_info()['index'] if input_devices else -1, + 'output': oPyA.get_default_output_device_info()['index'] if output_devices else -1, + 'enabled': input_devices and output_devices} + return audio + +def oMainArgparser(_=None): + if not os.path.exists('/proc/sys/net/ipv6'): + bIpV6 = 'False' + else: + bIpV6 = 'True' + lIpV6Choices=[bIpV6, 'False'] + + # need: + # 'audio_input': oPyA.get_default_input_device_info()['index'] + # 'audio_output': oPyA.get_default_output_device_info()['index'] + audio = get_audio() + # unfinished + + logfile = os.path.join(os.environ.get('TMPDIR', '/tmp'), 'tests_toxygen.log') + + parser = argparse.ArgumentParser() + parser.add_argument('--proxy_host', '--proxy-host', type=str, + default='', + help='proxy host') + parser.add_argument('--proxy_port', '--proxy-port', default=0, type=int, + help='proxy port') + parser.add_argument('--proxy_type', '--proxy-type', default=0, type=int, + choices=[0,1,2], + help='proxy type 1=http, 2=socks') + parser.add_argument('--udp_enabled', type=str, default='True', + choices=['True', 'False'], + help='En/Disable udp') + parser.add_argument('--ipv6_enabled', type=str, default=bIpV6, + choices=lIpV6Choices, + help=f"En/Disable ipv6 - default {bIpV6}") + parser.add_argument('--download_nodes_list', type=str, default='False', + choices=['True', 'False'], + help='Download nodes list') + parser.add_argument('--nodes_json', type=str, + default='') + parser.add_argument('--network', type=str, + choices=['old', 'new', 'local', 'newlocal'], + default='new') + parser.add_argument('--download_nodes_url', type=str, + default='https://nodes.tox.chat/json') + parser.add_argument('--logfile', default=logfile, + help='Filename for logging') + parser.add_argument('--loglevel', default=logging.INFO, type=int, + # choices=[logging.info,logging.trace,logging.debug,logging.error] + help='Threshold for logging (lower is more) default: 20') + parser.add_argument('--tcp_port', '--tcp-port', default=0, type=int, + help='tcp port') + parser.add_argument('--mode', type=int, default=2, + help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0') + parser.add_argument('--sleep', type=str, default='time', + # could expand this to tk, gtk, gevent... + choices=['qt','gevent','time'], + help='Sleep method - one of qt, gevent , time') + return parser + +def vOargsToxPreamble(oArgs, Tox, ToxTest): + kwargs = dict(level=oArgs.loglevel, + format='%(levelname)-8s %(message)s') + if oArgs.logfile: + kwargs['filename'] = oArgs.logfile + logging.basicConfig(**kwargs) + + methods = set([x for x in dir(Tox) if not x[0].isupper() + and not x[0] == '_']) + docs = "".join([getattr(ToxTest, x).__doc__ for x in dir(ToxTest) + if getattr(ToxTest, x).__doc__ is not None]) + + tested = set(re.findall(r't:(.*?)\n', docs)) + not_tested = methods.difference(tested) + + logging.info('Test Coverage: %.2f%%' % (len(tested) * 100.0 / len(methods))) + if len(not_tested): + logging.info('Not tested:\n %s' % "\n ".join(sorted(list(not_tested)))) + +def signal_handler(num, f): + from trepan.interfaces import server as Mserver + from trepan.api import debug + connection_opts={'IO': 'TCP', 'PORT': 6666} + intf = Mserver.ServerInterface(connection_opts=connection_opts) + dbg_opts = {'interface': intf} + LOG.info('Starting TCP server listening on port 6666.') + debug(dbg_opts=dbg_opts) + return + +def merge_args_into_settings(args, settings): + from user_data.settings import clean_settings + if args: + for key in settings.keys(): + # proxy_type proxy_port proxy_host + not_key = 'not_' +key + if hasattr(args, key): + val = getattr(args, key) + if type(val) == bytes: + # proxy_host - ascii? + # filenames - ascii? + val = str(val, 'UTF-8') + settings[key] = val + elif hasattr(args, not_key): + val = not getattr(args, not_key) + settings[key] = val + clean_settings(settings) + return + +def lSdSamplerates(iDev): + try: + import sounddevice as sd + except ImportError: + return [] + samplerates = (32000, 44100, 48000, 96000, ) + device = iDev + supported_samplerates = [] + for fs in samplerates: + try: + sd.check_output_settings(device=device, samplerate=fs) + except Exception as e: + # LOG.debug(f"Sample rate not supported {fs}" +' '+str(e)) + pass + else: + supported_samplerates.append(fs) + return supported_samplerates + +def should_we_pick_up_proxy_from_environ(): + retval = dict() + if os.environ.get('socks_proxy', ''): + # socks_proxy takes precedence over https/http + proxy = os.environ.get('socks_proxy', '') + i = proxy.find('//') + if i >= 0: proxy = proxy[i+2:] + retval['proxy_host'] = proxy.split(':')[0] + retval['proxy_port'] = proxy.split(':')[-1] + retval['proxy_type'] = 2 + retval['udp_enabled'] = False + elif os.environ.get('https_proxy', ''): + # https takes precedence over http + proxy = os.environ.get('https_proxy', '') + i = proxy.find('//') + if i >= 0: proxy = proxy[i+2:] + retval['proxy_host'] = proxy.split(':')[0] + retval['proxy_port'] = proxy.split(':')[-1] + retval['proxy_type'] = 1 + retval['udp_enabled'] = False + elif os.environ.get('http_proxy', ''): + proxy = os.environ.get('http_proxy', '') + i = proxy.find('//') + if i >= 0: proxy = proxy[i+2:] + retval['proxy_host'] = proxy.split(':')[0] + retval['proxy_port'] = proxy.split(':')[-1] + retval['proxy_type'] = 1 + retval['udp_enabled'] = False + return retval + +def download_url(url, app): + if not bAreWeConnected(): return '' + + settings = app._settings + if pycurl: + LOG.debug('nodes loading with pycurl: ' + str(url)) + buffer = BytesIO() + c = pycurl.Curl() + c.setopt(c.URL, url) + c.setopt(c.WRITEDATA, buffer) + # Follow redirect. + c.setopt(c.FOLLOWLOCATION, True) + + # cookie jar + cjar = os.path.join(os.environ['HOME'], '.local', 'jar.cookie') + if os.path.isfile(cjar): + c.setopt(c.COOKIEFILE, cjar) + # LARGS+=( --cookie-jar --junk-session-cookies ) + + #? c.setopt(c.ALTSVC_CTRL, 16) + + c.setopt(c.NOPROXY, ','.join(lNO_PROXY)) + #? c.setopt(c.CAINFO, certifi.where()) + if settings['proxy_type'] == 2 and settings['proxy_host']: + socks_proxy = 'socks5h://'+settings['proxy_host']+':'+str(settings['proxy_port']) + settings['udp_enabled'] = False + c.setopt(c.PROXY, socks_proxy) + c.setopt(c.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5_HOSTNAME) + elif settings['proxy_type'] == 1 and settings['proxy_host']: + https_proxy = 'https://'+settings['proxy_host']+':'+str(settings['proxy_port']) + c.setopt(c.PROXY, https_proxy) + elif settings['proxy_type'] == 1 and settings['proxy_host']: + http_proxy = 'http://'+settings['proxy_host']+':'+str(settings['proxy_port']) + c.setopt(c.PROXY, http_proxy) + c.setopt(c.PROTOCOLS, c.PROTO_HTTPS) + try: + c.perform() + c.close() + #? assert c.getinfo(c.RESPONSE_CODE) < 300 + result = buffer.getvalue() + # Body is a byte string. + LOG_INFO('nodes loaded with pycurl: ' + str(url)) + return result + except Exception as ex: + LOG_ERROR('TOX nodes loading error with pycurl: ' + str(ex)) + # drop through + + if requests: + LOG_DEBUG('nodes loading with requests: ' + str(url)) + try: + headers = dict() + headers['Content-Type'] = 'application/json' + proxies = dict() + if settings['proxy_type'] == 2 and settings['proxy_host']: + socks_proxy = 'socks5://'+settings['proxy_host']+':'+str(settings['proxy_port']) + settings['udp_enabled'] = False + proxies['https'] = socks_proxy + elif settings['proxy_type'] == 1 and settings['proxy_host']: + https_proxy = 'https://'+settings['proxy_host']+':'+str(settings['proxy_port']) + proxies['https'] = https_proxy + elif settings['proxy_type'] == 1 and settings['proxy_host']: + http_proxy = 'http://'+settings['proxy_host']+':'+str(settings['proxy_port']) + proxies['http'] = http_proxy + req = requests.get(url, + headers=headers, + proxies=proxies, + timeout=CONNECT_TIMEOUT) + # max_retries=3 + assert req.status_code < 300 + result = req.content + LOG_INFO('nodes loaded with requests: ' + str(url)) + return result + except Exception as ex: + LOG_ERROR('TOX nodes loading error with requests: ' + str(ex)) + # drop through + + if not settings['proxy_type']: # no proxy + LOG_DEBUG('nodes loading with no proxy: ' + str(url)) + try: + req = urllib.request.Request(url) + req.add_header('Content-Type', 'application/json') + response = urllib.request.urlopen(req) + result = response.read() + LOG_INFO('nodes loaded with no proxy: ' + str(url)) + return result + except Exception as ex: + LOG_ERROR('TOX nodes loading ' + str(ex)) + return '' + else: # proxy + from PyQt5 import QtNetwork + + LOG_DEBUG(f"TOX nodes loading with QT proxy: {url}") + netman = QtNetwork.QNetworkAccessManager() + proxy = QtNetwork.QNetworkProxy() + proxy.setType( + QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 \ + else QtNetwork.QNetworkProxy.HttpProxy ) + proxy.setHostName(settings['proxy_host']) + proxy.setPort(settings['proxy_port']) + netman.setProxy(proxy) + try: + request = QtNetwork.QNetworkRequest() + request.setUrl(QtCore.QUrl(url)) + reply = netman.get(request) + + while not reply.isFinished(): + QtCore.QThread.msleep(1) + QtCore.QCoreApplication.processEvents() + result = bytes(reply.readAll().data()) + LOG_INFO('TOX nodes loading with QT proxy: ' + str(url)) + return result + except Exception as ex: + LOG_ERROR('TOX nodes loading error with proxy: ' + str(ex)) + return '' + +def _get_nodes_path(oArgs=None): + if oArgs and hasattr(oArgs, 'nodes_json') and oArgs.nodes_json: + LOG.debug("_get_nodes_path: " +oArgs.nodes_json) + return oArgs.nodes_json + default = os.path.join(get_user_config_path(), 'toxygen_nodes.json') + LOG.debug("_get_nodes_path: " +default) + return default + +DEFAULT_NODES_COUNT = 4 + +def generate_nodes(nodes_count=DEFAULT_NODES_COUNT, oArgs=None): + sFile = _get_nodes_path(oArgs=oArgs) + return generate_nodes_from_file(sFile, nodes_count) + +def generate_nodes_from_file(sFile, nodes_count=DEFAULT_NODES_COUNT): + if not os.path.exists(sFile): + LOG.error("generate_nodes_from_file file not found " +sFile) + return [] + LOG.info("generate_nodes_from_file " +sFile) + with open(sFile, 'rt') as fl: + json_nodes = json.loads(fl.read())['nodes'] + nodes = [(node['ipv4'], node['port'], node['public_key'],) for + node in json_nodes if node['ipv4'] != 'NONE'] + sorted_nodes = nodes + random.shuffle(sorted_nodes) + if nodes_count is not None and len(sorted_nodes) > nodes_count: + sorted_nodes = sorted_nodes[-nodes_count:] + return sorted_nodes + +def tox_bootstrapd_port(): + port = 33446 + sFile = '/etc/tox-bootstrapd.conf' + if os.path.exists(sFile): + with open(sFile, 'rt') as oFd: + for line in oFd.readlines(): + if line.startswith('port = '): + port = int(line[7:]) + return port + +def bootstrap_local(self, elts): + if os.path.exists('/run/tox-bootstrapd/tox-bootstrapd.pid'): + LOG.debug('/run/tox-bootstrapd/tox-bootstrapd.pid') + iRet = True + else: + iRet = os.system("netstat -nle4|grep -q :33") + if iRet > 0: + LOG.warn('bootstraping local No local DHT running') + LOG.info('bootstraping local') + return bootstrap_good(self, elts) + +def bootstrap_good(self, lelts): + LOG.info('bootstraping udp') + for elt in ['bob', 'alice']: + for largs in lelts: + try: + oRet = getattr(self, elt).bootstrap(largs[0], + int(largs[1]), + largs[2]) + except Exception as e: + LOG.error('bootstrap to ' +largs[0] +':' +str(largs[1]) \ + +' ' +str(e)) + continue + if not oRet: + LOG.warn('bootstrap failed to ' +largs[0] +' : ' +str(oRet)) + else: + if getattr(self, elt).self_get_connection_status() != TOX_CONNECTION['NONE']: + LOG.debug('bootstrap to ' +largs[0] +' connected') + return + +def bootstrap_tcp(self, lelts): + LOG.info('bootstraping tcp') + for elt in ['alice', 'bob']: + for largs in lelts: + try: + oRet = getattr(self, elt).add_tcp_relay(largs[0], + int(largs[1]), + largs[2]) + except Exception as e: + LOG.error('bootstrap_tcp to ' +largs[0] +' : ' +str(e)) + continue + if not oRet: + LOG.warn('bootstrap_tcp failed to ' +largs[0] +' : ' +str(oRet)) + else: + if getattr(self, elt).self_get_connection_status() != TOX_CONNECTION['NONE']: + LOG.debug('bootstrap_tcp to ' +largs[0] +' connected') + break + +def caseFactory(cases): + """We want the tests run in order.""" + if len(cases) > 1: + ordered_cases = sorted(cases, key=lambda f: findsource(f)[1]) + else: + ordered_cases = cases + return ordered_cases + +def suiteFactory(*testcases): + """We want the tests run in order.""" + linen = lambda f: getattr(tc, f).__code__.co_firstlineno + lncmp = lambda a, b: linen(a) - linen(b) + + test_suite = unittest.TestSuite() + for tc in testcases: + test_suite.addTest(unittest.makeSuite(tc, sortUsing=lncmp)) + return test_suite diff --git a/tests/tests_wrapper.py b/tests/tests_wrapper.py new file mode 100644 index 0000000..36ff7b9 --- /dev/null +++ b/tests/tests_wrapper.py @@ -0,0 +1,1771 @@ +# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*- +# +# @file tests.py +# @author Wei-Ning Huang (AZ) +# +# Copyright (C) 2013 - 2014 Wei-Ning Huang (AZ) +# All Rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +"""Originanly from https://github.com/oxij/PyTox c-toxcore-02 branch +which itself was forked from https://github.com/aitjcize/PyTox/ + +Modified to work with +""" + +import hashlib +import os +import re +import sys +import unittest +import traceback +import logging +import random +import threading +import ctypes +from ctypes import * + +import faulthandler +faulthandler.enable() + +import warnings +warnings.filterwarnings('ignore') + +try: + import pycurl + import certifi + from io import BytesIO +except ImportError: + pycurl = None + +try: + import coloredlogs + os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red' +except ImportError as e: + # logging.log(logging.DEBUG, f"coloredlogs not available: {e}") + coloredlogs = None + +try: + import color_runner +except ImportError as e: + color_runner = None + +import wrapper +from wrapper.tox import Tox +import wrapper.toxcore_enums_and_consts as enums +from wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS, \ + TOX_MESSAGE_TYPE, TOX_SECRET_KEY_SIZE, TOX_FILE_CONTROL, TOX_ADDRESS_SIZE + +try: + import support_testing as ts + import omain + from support_testing import lGOOD, lLOCAL +except ImportError: + import tests.support_testing as ts + from tests import omain + from tests.support_testing import lGOOD, lLOCAL + +try: + from toxygen_tests import test_sound_notification + bIS_NOT_TOXYGEN = False +except ImportError: + bIS_NOT_TOXYGEN = True + +# from PyQt5 import QtCore +if 'QtCore' in globals(): + def qt_sleep(fSec): + if fSec > .000001: QtCore.QThread.sleep(fSec) + QtCore.QCoreApplication.processEvents() + sleep = qt_sleep +elif 'gevent' in globals(): + sleep = gevent.sleep +else: + import time + sleep = time.sleep + +global LOG +LOG = logging.getLogger('TestS') +# just print to stdout so there is no complications from logging. +def LOG_ERROR(l): print('EROR+ '+l) +def LOG_WARN(l): print('WARN+ '+l) +def LOG_INFO(l): print('INFO+ '+l) +def LOG_DEBUG(l): print('DEBUG+ '+l) +def LOG_TRACE(l): pass # print('TRAC+ '+l) + +ADDR_SIZE = 38 * 2 +CLIENT_ID_SIZE = 32 * 2 +THRESHOLD = 15 + +global oTOX_OPTIONS +oTOX_OPTIONS = {} + +bIS_LOCAL = 'new' in sys.argv or 'newlocal' in sys.argv + +# Patch unittest for Python version <= 2.6 +if not hasattr(unittest, 'skip'): + def unittest_skip(reason): + def _wrap1(func): + def _wrap2(self, *args, **kwargs): + pass + return _wrap2 + return _wrap1 + unittest.skip = unittest_skip + +class ToxOptions(): + def __init__(self): + self.ipv6_enabled = True + self.udp_enabled = True + self.proxy_type = 0 + self.proxy_host = '' + self.proxy_port = 0 + self.start_port = 0 + self.end_port = 0 + self.tcp_port = 0 + self.savedata_type = 0 # 1=toxsave, 2=secretkey + self.savedata_data = b'' + self.savedata_length = 0 + self.local_discovery_enabled = False + self.dht_announcements_enabled = True + self.hole_punching_enabled = False + self.experimental_thread_safety = False + +class App(): + def __init__(self): + self.mode = 0 +oAPP = App() + +class AliceTox(Tox): + + def __init__(self, opts, app=None): + + super(AliceTox, self).__init__(opts, app=app) + self._address = self.self_get_address() + self.name = 'alice' + self._opts = opts + self._app = app + +class BobTox(Tox): + + def __init__(self, opts, app=None): + super(BobTox, self).__init__(opts, app=app) + self._address = self.self_get_address() + self.name = 'bob' + self._opts = opts + self._app = app + +class BaseThread(threading.Thread): + + def __init__(self, name=None, target=None): + if name: + super().__init__(name=name, target=target) + else: + super().__init__(target=target) + self._stop_thread = False + self.name = name + + def stop_thread(self, timeout=-1): + self._stop_thread = True + if timeout < 0: + timeout = ts.iTHREAD_TIMEOUT + i = 0 + while i < ts.iTHREAD_JOINS: + self.join(timeout) + if not self.is_alive(): break + i = i + 1 + else: + LOG.warn(f"{self.name} BLOCKED") + +class ToxIterateThread(BaseThread): + + def __init__(self, tox): + super().__init__(name='ToxIterateThread') + self._tox = tox + + def run(self): + while not self._stop_thread: + self._tox.iterate() + sleep(self._tox.iteration_interval() / 1000) + +class ToxSuite(unittest.TestCase): + global oTOX_OARGS + + def run(self, result=None): + """ Stop after first error """ + if not result.errors: + super(ToxSuite, self).run(result) + + def prepare(self, *args, **kw): + assert oTOX_OPTIONS + assert oTOX_OARGS + opts = oToxygenToxOptions(oTOX_OARGS) + print(repr(opts)) + if not hasattr(self, 'alice'): + self.alice = AliceTox(opts, app=oAPP) + self.alice.oArgs = opts + if not hasattr(self, 'bob'): + self.bob = BobTox(opts, app=oAPP) + self.bob.oArgs = opts + if not bIS_LOCAL and not ts.bAreWeConnected(): + LOG.warn(f"prepare not local and NOT CONNECTED") + + self.lUdp = [] + self.lTcp = [] + if oTOX_OARGS.network in ['newlocal', 'localnew']: + oTOX_OARGS.network = 'newlocal' + self.lUdp = lLOCAL + self.bTest = True + elif oTOX_OARGS.network in ['test', 'new']: + self.lUdp = ts.lNEW + self.bTest = True + else: + self.bTest = False + self.lUdp = ts.lGOOD + if oTOX_OARGS.proxy_port > 0: + self.lTcp = ts.lRELAYS + else: + self.lUdp = lGOOD + + def get_connection_status(self): + # if not self.connected + if self.bob.self_get_connection_status() == TOX_CONNECTION['NONE']: + return False + if self.alice.self_get_connection_status() == TOX_CONNECTION['NONE']: + return False + return True + + def setUp(self): + """ + t:on_log + """ + + if not hasattr(self, 'alice') or not hasattr(self, 'bob'): + self.prepare() + if not hasattr(self.bob, '_main_loop'): + self.bob._main_loop = ToxIterateThread(self.bob) + self.bob._main_loop.start() + # LOG.debug(f"self.bob._main_loop: {threading.enumerate()!r}") + + + def tearDown(self): + """ + t:kill + """ + self.bob._main_loop.stop_thread() + if False: + self.alice.kill() + self.bob.kill() + del self.bob + del self.alice + + def loop(self, n): + """ + t:iterate + t:iteration_interval + """ + interval = self.bob.iteration_interval() + for i in range(n): + self.alice.iterate() + self.bob.iterate() + sleep(interval / 1000.0) + + def assert_connection_status(self): + """ + t:self_get_connection_status + """ + assert self.alice.self_get_connection_status() != TOX_CONNECTION['NONE'], \ + 'ERROR: self.alice.self_get_connection_status() is ' + \ + repr(self.alice.self_get_connection_status()) + assert self.bob.self_get_connection_status() != TOX_CONNECTION['NONE'], \ + 'ERROR: self.bob.self_get_connection_status() is ' + \ + repr(self.bob.self_get_connection_status()) + + def call_bootstrap(self): + LOG.debug(f"call_bootstrap") + if oTOX_OARGS.network in ['new', 'newlocal', 'localnew']: + ts.bootstrap_local(self, self.lUdp) + elif self.get_connection_status() is True: + LOG.debug(f"call_bootstrap {self.get_connection_status()}") + elif not ts.bAreWeConnected(): + LOG.warn('we are NOT CONNECTED ') + elif oTOX_OARGS.proxy_port > 0: + random.shuffle(self.lUdp) +# LOG.debug(f"call_bootstrap ts.bootstrap_good {self.lUdp[:2]}") + ts.bootstrap_good(self, self.lUdp[:2]) + random.shuffle(self.lTcp) +# LOG.debug(f"call_bootstrap ts.bootstrap_tcp {self.lTcp[:8]}") + ts.bootstrap_tcp(self, self.lTcp[:8]) + else: + random.shuffle(self.lUdp) +# LOG.debug(f"call_bootstrap ts.bootstrap_good {self.lUdp[:8]}") + ts.bootstrap_good(self, self.lUdp[:8]) + + def loop_until_connected(self): + """ + t:on_self_connection_status + t:self_get_connection_status + """ + i = 0 + bRet = None + + self.bob.mycon_status = False + def bobs_on_self_connection_status(iTox, connection_state, *args): + status = connection_state + try: + if status != TOX_CONNECTION['NONE']: + LOG_INFO(f"BOBS_ON_self_connection_status TRUE {status}") + self.bob.mycon_status = True + else: + LOG_WARN(f"BOBS_ON_self_connection_status FALSE {status}") + self.bob.mycon_status = False + except Exception as e: + LOG_ERROR(f"BOBS_ON_self_connection_status {e}") + else: + if self.bob.self_get_connection_status() != status: + LOG_WARN(f"BOBS_ON_self_connection_status != {status}") + + self.alice.mycon_status = False + def alices_on_self_connection_status(iTox, connection_state, *args): + #FixMe connection_num + status = connection_state + try: + if status != TOX_CONNECTION['NONE']: + LOG_INFO(f"alices_on_self_connection_status TRUE {status}") + self.alice.mycon_status = True + else: + LOG_WARN(f"alices_on_self_connection_status FALSE {status}") + self.alice.mycon_status = False + except Exception as e: + LOG_ERROR(f"alices_on_self_connection_status error={e}") + else: + if self.alice.self_get_connection_status() != status: + LOG_WARN(f"alices_on_self_connection_status != {status}") + + try: + self.alice.callback_self_connection_status(alices_on_self_connection_status) + self.bob.callback_self_connection_status(bobs_on_self_connection_status) + + while i <= THRESHOLD : + if i % 3 == 0: + self.call_bootstrap() + s = '' + if i == 0: s = '\n' + LOG.info(s+"loop_until_connected " \ + +" #" + str(i) \ + +" BOB=" +repr(self.bob.self_get_connection_status()) \ + +" ALICE=" +repr(self.alice.self_get_connection_status()) + +" BOBS=" +repr(self.bob.mycon_status) \ + +" ALICES=" +repr(self.alice.mycon_status) \ + ) + if (self.alice.mycon_status and self.bob.mycon_status): + bRet = True + break + if (self.alice.self_get_connection_status() and + self.bob.self_get_connection_status()): + LOG_WARN(f"loop_until_connected disagree !=" \ + +f' self.bob.mycon_status={self.bob.mycon_status}' + +f' alice.mycon_status={self.alice.mycon_status}') + bRet = True + break + i += 1 + self.loop(100) + else: + bRet = False + finally: + self.alice.callback_self_connection_status(None) + self.bob.callback_self_connection_status(None) + del self.alice.mycon_status + del self.bob.mycon_status + + if bRet or \ + ( self.bob.self_get_connection_status() != TOX_CONNECTION['NONE'] and \ + self.alice.self_get_connection_status() != TOX_CONNECTION['NONE'] ): + LOG.info("loop_until_connected " \ + +f" BOB={self.bob.self_get_connection_status()}" \ + +f" ALICE={self.alice.self_get_connection_status()}") + return True + else: + LOG.warn("loop_until_connected " \ + +f" BOB={self.bob.self_get_connection_status()}" \ + +f" ALICE={self.alice.self_get_connection_status()}") + return bRet + + def wait_obj_attr(self, obj, attr): + i = 0 + while i < THRESHOLD: + if i % 3 == 0: + self.call_bootstrap() + LOG.debug("wait_obj_attr " +obj.name \ + +" for " +attr \ + +" " +str(i) \ + +" " +repr(getattr(obj, attr))) + if getattr(obj, attr): + return True + self.loop(100) + i += 1 + else: + LOG.error("wait_obj_attr count >= " + str(THRESHOLD) + + " for " +attr) + return getattr(obj, attr) + + def wait_objs_attr(self, objs, attr): + i = 0 + while i <= THRESHOLD: + if i % 1 == 0: + self.call_bootstrap() + LOG.debug("wait_objs_attr " +repr(objs) \ + +" for " +repr(attr) \ + +" " +str(i)) + if all([getattr(obj, attr) for obj in objs]): + return True + self.loop(100) + i += 1 + else: + LOG.error(f"wait_obj_attr i >= {THRESHOLD!s}") + + return all([getattr(obj, attr) for obj in objs]) + + def wait_obj_attrs(self, obj, attrs): + i = 0 + while i <= THRESHOLD: + if i % 3 == 0: + self.call_bootstrap() + LOG.debug("wait_obj_attrs " +repr(obj) \ + +" for " +repr(attrs) \ + +" " +str(i)) + if all([getattr(obj, attr) for attr in attrs]): + return True + self.loop(100) + i += 1 + else: + LOG.warn(f"wait_obj_attrs i >= {THRESHOLD!s}") + + return all([getattr(obj, attr) for attr in attrs]) + + def wait_ensure_exec(self, method, args): + i = 0 + oRet = None + while i <= THRESHOLD: + if i % 3 == 0: + self.call_bootstrap() + LOG.debug("wait_ensure_exec " \ + +" " +str(method) + +" " +str(i)) + try: + oRet = method(*args) + if oRet: + LOG.info(f"wait_ensure_exec oRet {oRet!r}") + return True + except ArgumentError as e: + # ArgumentError('This client is currently NOT CONNECTED to the friend.') + # dunno + LOG.warn(f"wait_ensure_exec ArgumentError {e}") + return False + except Exception as e: + LOG.warn(f"wait_ensure_exec EXCEPTION {e}") + return False + sleep(3) + i += 1 + else: + LOG.error(f"wait_ensure_exec i >= {1*THRESHOLD!s}") + return False + + return oRet + + def bob_add_alice_as_friend_norequest(self): + MSG = 'Hi, this is Bob.' + iRet = self.bob.friend_add_norequest(self.alice._address) + self.baid = self.bob.friend_by_public_key(self.alice._address) + assert self.baid >= 0, self.baid + assert self.bob.friend_exists(self.baid), "bob.friend_exists" + assert not self.bob.friend_exists(self.baid + 1) + assert self.baid in self.bob.self_get_friend_list() + assert self.bob.self_get_friend_list_size() >= 1 + return iRet + + def alice_add_bob_as_friend_norequest(self): + MSG = 'Hi Bob, this is Alice.' + iRet = self.alice.friend_add_norequest(self.bob._address) + self.abid = self.alice.friend_by_public_key(self.bob._address) + assert self.abid >= 0, self.abid + assert self.alice.friend_exists(self.abid), "alice.friend_exists" + assert not self.alice.friend_exists(self.abid + 1) + assert self.abid in self.alice.self_get_friend_list() + assert self.alice.self_get_friend_list_size() >= 1 + return iRet + + def bob_just_add_alice_as_friend(self): + """ + t:friend_add + t:on_friend_request + t:friend_by_public_key + """ + MSG = 'Alice, this is Bob.' + sSlot = 'friend_request' + + def alices_on_friend_request(iTox, + public_key, + message_data, + message_data_size, + *largs): + # oTox = Tox(tox_pointer=iTox) + try: + LOG_DEBUG(f"alices_on_friend_request: " +repr(message_data)) + assert str(message_data, 'UTF-8') == MSG + except Exception as e: + LOG_WARN(f"alices_on_friend_request: Exception {e}") + # return + else: + self.alice.friend_added = True + LOG_DEBUG(f"alices_on_friend_request: self.alice.friend_added = True ") + + inum = -1 + try: + self.alice.friend_added = False + self.alice.callback_friend_request(alices_on_friend_request) + inum = self.bob.friend_add(self.alice._address, bytes(MSG, 'UTF-8')) + if not inum >= 0: + LOG.warn('bob.friend_add !>= 0 ' +repr(inum)) + if not self.wait_obj_attr(self.alice, 'friend_added'): + return False + except Exception as e: + LOG.error(f"bob.friend_add EXCEPTION {e}") + return False + + self.baid = self.bob.friend_by_public_key(self.alice._address) + assert self.baid >= 0, self.baid + assert self.bob.friend_exists(self.baid) + assert not self.bob.friend_exists(self.baid + 1) + assert self.baid in self.bob.self_get_friend_list() + assert self.bob.self_get_friend_list_size() >= 1 + return True + + def alice_just_add_bob_as_friend(self): + """ + t:friend_add + t:on_friend_request + t:friend_by_public_key + """ + MSG = 'Bob, this is Alice.' + + def bobs_on_friend_request(iTox, + public_key, + message_data, + message_data_size, + *largs): + # oTox = Tox(tox_pointer=iTox) + try: + LOG_DEBUG(f"BOBS_ON_friend_request: " +repr(message_data)) + assert str(message_data, 'UTF-8') == MSG + LOG_INFO(f"BOBS_ON_friend_request: friend_added = True ") + self.bob.friend_added = True + except Exception as e: + LOG_WARN(f"BOBS_ON_friend_request: Exception {e}") + + inum = -1 + sSlot = 'friend_request' + try: + self.bob.friend_added = False + self.bob.callback_friend_request(bobs_on_friend_request) + inum = self.alice.friend_add(self.bob._address, bytes(MSG, 'UTF-8')) + if not inum >= 0: + LOG.warn('alice.friend_add !>= 0 ' +repr(inum)) + if not self.wait_obj_attr(self.bob, 'friend_added'): + return False + except Exception as e: + LOG.error(f"alice.friend_add {e}") + return False + finally: + self.alice.callback_friend_message(None) + self.abid = self.alice.friend_by_public_key(self.bob._address) + assert self.alice.friend_exists(self.abid) + assert self.alice.self_get_friend_list_size() >= 1 + assert self.abid in self.alice.self_get_friend_list() + return True + + def bob_add_alice_as_friend_and_status(self): + bRetval = self.bob_just_add_alice_as_friend() + if not bRetval: + LOG.error("bob_add_alice_as_friend_and_status FAILED") + return False + LOG.info("bob_add_alice_as_friend_and_status waiting for connections") + + #: Wait until both are online + self.bob.friend_conn_status = False + def bobs_on_friend_connection_status(iTox, friend_id, iStatus, *largs): + LOG_INFO(f"BOBS_ON_friend_connection_status {friend_id} ?>=0" +repr(iStatus)) + if iStatus > 0: + self.bob.friend_conn_status = True + + self.bob.friend_status = False + def bobs_on_friend_status(iTox, friend_id, iStatus, *largs): + LOG_INFO(f"BOBS_ON_friend_status {friend_id} ?>=0" +repr(iStatus)) + if iStatus > 0: + self.bob.friend_status = True + + self.alice.friend_conn_status = False + def alices_on_friend_connection_status(iTox, friend_id, iStatus, *largs): + LOG_INFO(f"alices_on_friend_connection_status {friend_id} ?>=0 " +repr(iStatus)) + if iStatus > 0: + self.alice.friend_conn_status = True + + self.alice.friend_status = False + def alices_on_friend_status(iTox, friend_id, iStatus, *largs): + LOG_INFO(f"alices_on_friend_status {friend_id} ?>=0 " +repr(iStatus)) + if iStatus > 0: + self.alice.friend_status = True + + try: + self.alice.callback_friend_connection_status(alices_on_friend_connection_status) + self.alice.callback_friend_status(alices_on_friend_status) + LOG.info("bob_add_alice_as_friend_and_status waiting for alice connections") + if not self.wait_obj_attrs(self.alice, + ['friend_conn_status', + 'friend_status']): + return False + + self.bob.callback_friend_connection_status(bobs_on_friend_connection_status) + self.bob.callback_friend_status(bobs_on_friend_status) + + LOG.info("bob_add_alice_as_friend_and_status waiting for bob connections") + if not self.wait_obj_attrs(self.bob, + ['friend_conn_status', + 'friend_status']): + return False + except Exception as e: + LOG.error(f"bob_add_alice_as_friend_and_status ERROR {e}") + return False + finally: + self.alice.callback_friend_connection_status(None) + self.bob.callback_friend_connection_status(None) + self.alice.callback_friend_status(None) + self.bob.callback_friend_status(None) + return True + + def friend_delete(self, fname, baid): + #: Test delete friend + assert getattr(self, fname).friend_exists(baid) + getattr(self, fname).friend_delete(baid) + self.loop(50) + assert not self.bob.friend_exists(baid) + + def iNodeInfo(self, sProt, sHost, sPort, key=None, environ=None, bTest=False): + sFile = os.path.join("/tmp", sHost + '.nmap') + if sProt in ['socks', 'socks5', 'tcp4']: + cmd = f"nmap -Pn -n -sT -p T:{sPort} {sHost} | grep /tcp >{sFile}" + else: + cmd = f"nmap -Pn -n -sT -p U:{sPort} {sHost} | grep /tcp >{sFile}" + iRet = os.system(cmd) + LOG.debug(f"iNodeInfo cmd={cmd} {iRet}") + if iRet != 0: + return iRet + assert os.path.exists(sFile), sFile + with open(sFile, 'rt') as oFd: + l = oFd.readlines() + assert len(l) + s = '\n'.join([s.strip() for s in l]) + LOG.debug(f"iNodeInfo: {s}") + return 0 + + def bootstrap_iNodeInfo(self): + if not bIS_LOCAL and not ts.bAreWeConnected(): + LOG.warn(f"bootstrap_iNodeInfo not local and NOT CONNECTED") + return True + env = dict() + if oTOX_OARGS.proxy_type == 2: + protocol='socks' + elif oTOX_OARGS.proxy_type == 1: + protocol='https' + else: + protocol='ipv4' + env = os.environ + + if oTOX_OARGS.network in ['new', 'newlocal', 'localnew']: + lElts = self.lUdp + elif oTOX_OARGS.proxy_port > 0: + lElts = self.lTcp + else: + lElts = self.lUdp + lRetval = [] + random.shuffle(lElts) + for elts in lElts[:8]: + iRet = -1 + try: + iRet = self.iNodeInfo(protocol, *elts) + if iRet != 0: + LOG.warn('iNodeInfo to ' +repr(elts[0]) +' : ' +str(iRet)) + lRetval += [False] + else: + LOG.info(f'bootstrap_iNodeInfo ' + +f" net={oTOX_OARGS.network}" + +f" prot={protocol}" + +f" proxy={oTOX_OARGS.proxy_type}" + +f' {elts[:2]!r}' + ) + lRetval += [True] + except Exception as e: + LOG.error('iNodeInfo to ' +repr(elts[0]) +' : ' +str(e) \ + +'\n' + traceback.format_exc()) + lRetval += [False] + return any(lRetval) + + def warn_if_no_cb(self, alice, sSlot): + if not hasattr(alice, sSlot+'_cb') or \ + not getattr(alice, sSlot+'_cb'): + LOG.warn(f"self.bob.{sSlot}_cb NOT EXIST") + + def warn_if_cb(self, alice, sSlot): + if hasattr(self.bob, sSlot+'_cb') and \ + getattr(self.bob, sSlot+'_cb'): + LOG.warn("self.bob.{sSlot}_cb EXIST") + + # tests are executed in order + def test_tests_logging(self): # works + with self.assertLogs('foo', level='INFO') as cm: + logging.getLogger('foo').info('first message') + logging.getLogger('foo.bar').error('second message') + logging.getLogger('foo.bar.baz').debug('third message') + self.assertEqual(cm.output, ['INFO:foo:first message', + 'ERROR:foo.bar:second message']) + + def test_tests_start(self): # works + LOG.info("test_tests_start " ) + global lLOCAL + port = ts.tox_bootstrapd_port() + + if port: + elt = list(lLOCAL[0]) + elt[1] = port + lLOCAL.insert(0,elt) + + assert len(self.bob._address) == 2*TOX_ADDRESS_SIZE, len(self.bob._address) + assert len(self.alice._address) == 2*TOX_ADDRESS_SIZE, \ + len(self.alice._address) + + def test_bootstrap_local_netstat(self): # works + """ + t:bootstrap + """ + if oTOX_OARGS.network not in ['new', 'newlocal', 'local']: + return + + port = ts.tox_bootstrapd_port() + if not port: + return + iStatus = os.system(f"""netstat -nle4 | grep :{port}""") + if iStatus == 0: + LOG.info(f"bootstrap_local_netstat port {port} iStatus={iStatus}") + else: + LOG.warn(f"bootstrap_local_netstat NOT {port} iStatus={iStatus}") + + def test_bootstrap_local_bash(self): # works + """ + t:bootstrap + """ + if oTOX_OARGS.network not in ['new', 'test', 'newlocal', 'local']: + return + + o = oTOX_OARGS.network + sFile = bootstrap_node_info.__file__ + assert os.path.exists(sFile) + port = ts.tox_bootstrapd_port() + sExe = sys.executable + iStatus = os.system(sExe +f""" {sFile} --test ipv4 localhost {port}""") + if iStatus == 0: + LOG.info(f"bootstrap_local_bash connected {o} iStatus={iStatus}") + else: + LOG.warn(f"bootstrap_local_bash NOT CONNECTED {o} iStatus={iStatus}") + + @unittest.skipIf(not bIS_LOCAL, "local test") + def test_bootstrap_local(self): # works + """ + t:bootstrap + """ + # get port from /etc/tox-bootstrapd.conf 33445 + self.call_bootstrap() + # ts.bootstrap_local(self, self.lUdp) + i = 0 + iStatus = -1 + while i < 10: + i = i + 1 + iStatus = self.bob.self_get_connection_status() + if iStatus != TOX_CONNECTION['NONE']: + break + sleep(3) + else: + pass + + o1 = self.alice.self_get_dht_id() + assert len(o1) == 64 + o2 = self.bob.self_get_dht_id() + assert len(o2) == 64 + + if o1 != o2: + LOG.warn(f"bootstrap_local DHT NOT same {o1} {o2} iStatus={iStatus}") + + iStatus = self.bob.self_get_connection_status() + if iStatus != TOX_CONNECTION['NONE']: + LOG.info(f"bootstrap_local connected iStatus={iStatus}") + return True + iStatus = self.alice.self_get_connection_status() + if iStatus != TOX_CONNECTION['NONE']: + LOG.info(f"bootstrap_local connected iStatus={iStatus}") + return True + LOG.warn(f"bootstrap_local NOT CONNECTED iStatus={iStatus}") + return False + + def test_bootstrap_iNodeInfo(self): # works + assert self.bootstrap_iNodeInfo() + + def test_self_get_secret_key(self): # works + """ + t:self_get_secret_key + """ + # test_self_get_secret_key + CRYPTO_SECRET_KEY_SIZE = 32 + secret_key = create_string_buffer(CRYPTO_SECRET_KEY_SIZE) + oRet0 = self.alice.self_get_secret_key(secret_key) + assert oRet0, repr(oRet0) + LOG.info('test_self_get_secret_key ' +repr(oRet0)) + assert len(str(oRet0)) + del secret_key + + def test_self_get_public_keys(self): # works + """ + t:self_get_secret_key + t:self_get_public_key + """ + + LOG.info('test_self_get_public_keys self.alice.self_get_secret_key') + oRet0 = self.alice.self_get_secret_key() + assert len(oRet0) + LOG.info('test_self_get_public_keys ' +repr(oRet0)) + oRet1 = self.alice.self_get_public_key() + assert len(oRet1) + LOG.info('test_self_get_public_keys ' +repr(oRet1)) + assert oRet0 != oRet1, repr(oRet0) +' != ' +repr(oRet1) + + def test_self_name(self): # works + """ + t:self_set_name + t:self_get_name + t:self_get_name_size + """ + self.alice.self_set_name('Alice') + assert self.alice.self_get_name() == 'Alice' + assert self.alice.self_get_name_size() == len('Alice') + + @unittest.skip('loud') + @unittest.skipIf(bIS_NOT_TOXYGEN, 'not testing in toxygen') + def test_sound_notification(self): # works + """ + Plays sound notification + :param type of notification + """ + from toxygen_tests import test_sound_notification + test_sound_notification(self) + + def test_self_get_udp_port(self): # works + """ + t:self_get_udp_port + """ + if hasattr(oTOX_OPTIONS, 'udp_port') and oTOX_OPTIONS.udp_port: + o = self.alice.self_get_udp_port() + assert o > 0 + + def test_self_get_tcp_port(self): # works + """ + t:self_get_tcp_port + """ + if hasattr(oTOX_OPTIONS, 'tcp_port') and oTOX_OPTIONS.tcp_port: + # errors if tcp_port <= 0 + o = self.alice.self_get_tcp_port() + LOG.info('self_get_tcp_port ' +repr(o)) + + + def test_loop_until_connected(self): # works + assert self.loop_until_connected() + + def test_address(self): # works + """ + t:self_get_address + t:self_get_nospam + t:self_set_nospam + t:self_get_keys + """ + assert len(self.alice.self_get_address()) == ADDR_SIZE + assert len(self.bob.self_get_address()) == ADDR_SIZE + + self.alice.self_set_nospam(0x12345678) + assert self.alice.self_get_nospam() == 0x12345678 + self.loop(50) + + if hasattr(self.alice, 'self_get_keys'): + pk, sk = self.alice.self_get_keys() + assert pk == self.alice.self_get_address()[:CLIENT_ID_SIZE] + + def test_get_dht_id(self): # works + """ + t:self_get_dht_id + """ + o1 = self.alice.self_get_dht_id() + assert len(o1) == 64 + o2 = self.bob.self_get_dht_id() + assert len(o2) == 64 + + def test_bob_assert_connection_status(self): # works + if self.bob.self_get_connection_status() == TOX_CONNECTION['NONE']: + RuntimeError("ERROR: NOT CONNECTED " \ + +repr(self.bob.self_get_connection_status())) + + def test_alice_assert_connection_status(self): # works + if self.alice.self_get_connection_status() == TOX_CONNECTION['NONE']: + RuntimeError("ERROR: NOT CONNECTED " \ + +repr(self.alice.self_get_connection_status())) + + def test_status_message(self): # works + MSG = 'Happy' + + self.alice.self_set_status_message(MSG) + self.loop(50) + assert self.alice.self_get_status_message() == MSG, \ + self.alice.self_get_status_message() +' is not ' +MSG + assert self.alice.self_get_status_message_size() == len(MSG) + + def test_bob_add_alice_as_friend_norequest(self): # works + iRet = self.bob_add_alice_as_friend_norequest() + if iRet < 0: + raise RuntimeError(f"bob_add_alice_as_friend_norequest {iRet}") + #: Test last online + assert self.bob.friend_get_last_online(self.baid) is not None + self.bob.friend_delete(self.baid) + + def test_alice_just_add_bob_as_friend_norequest(self): # works + iRet = self.alice_add_bob_as_friend_norequest() + if iRet < 0: + raise RuntimeError(f"bob_add_alice_as_friend_norequest {iRet}") + #: Test last online + assert self.alice.friend_get_last_online(self.abid) is not None + self.alice.friend_delete(self.abid) + + @unittest.skipIf(bIS_LOCAL, "local test") + def test_bob_just_add_alice_as_friend(self): # works + assert self.bob_just_add_alice_as_friend() + #: Test last online + assert self.bob.friend_get_last_online(self.baid) is not None + self.bob.friend_delete(self.baid) + + @unittest.skipIf(bIS_LOCAL, "local test") + def test_alice_just_add_bob_as_friend(self): # works + assert self.alice_just_add_bob_as_friend() + #: Test last online + assert self.alice.friend_get_last_online(self.abid) is not None + self.alice.friend_delete(self.abid) + + def test_both_just_add_as_friend(self): # works + LOG_DEBUG(f"bIS_LOCAL={bIS_LOCAL} oTOX_OARGS.bIS_LOCAL={oTOX_OARGS.bIS_LOCAL}") + if oTOX_OARGS.bIS_LOCAL: + iRet = self.bob_add_alice_as_friend_norequest() + if iRet < 0: + raise RuntimeError(f"bob_add_alice_as_friend_norequest {iRet}") + iRet = self.alice_add_bob_as_friend_norequest() + if iRet < 0: + raise RuntimeError(f"alice_add_bob_as_friend_norequest {iRet}") + else: + assert self.bob_just_add_alice_as_friend() + assert self.alice_just_add_bob_as_friend() + + #: Test last online + assert self.alice.friend_get_last_online(self.abid) is not None + assert self.bob.friend_get_last_online(self.baid) is not None + + self.bob.friend_delete(self.baid) + self.alice.friend_delete(self.abid) + + @unittest.skip('unfinished?') + def test_bob_add_alice_as_friend_and_status(self): + assert self.bob_add_alice_as_friend_and_status() + self.bob.friend_delete(self.baid) + + @unittest.skip('fails') + def test_on_friend_status_message(self): + """ + t:self_set_status_message + t:self_get_status_message + t:self_get_status_message_size + t:friend_set_status_message + t:friend_get_status_message + t:friend_get_status_message_size + t:on_friend_status_message + """ + MSG = 'Happy' + sSlot = 'friend_status_message' + + if oTOX_OARGS.bIS_LOCAL: + iRet = self.bob_add_alice_as_friend_norequest() + if iRet < 0: + raise RuntimeError(f"bob_add_alice_as_friend_norequest {iRet}") + else: + assert self.bob_just_add_alice_as_friend() + + setattr(self.bob, sSlot, False) + def bob_on_friend_status_message(iTox, friend_id, new_status_message, new_status_size, *largs): + setattr(self.bob, sSlot, True) + try: + assert str(new_status_message, 'UTF-8') == MSG + assert friend_id == self.baid + except Exception as e: + LOG_ERROR("BOB_ON_friend_status_message EXCEPTION " +str(e)) + else: + LOG_INFO("BOB_ON_friend_status_message " +repr(friend_id) \ + +repr(new_status_message)) + + try: + self.bob.callback_friend_status_message(bob_on_friend_status_message) + self.warn_if_no_cb(self.bob, sSlot) + self.alice.self_set_status_message(MSG) + assert self.wait_obj_attr(self.bob, sSlot) + + assert self.bob.friend_get_status_message(self.baid) == MSG + assert self.bob.friend_get_status_message_size(self.baid) == len(MSG) + + except AssertionError as e: + raise + except Exception as e: + LOG.error(f"test_on_friend_status_message EXCEPTION {e}") + raise + finally: + self.alice.callback_friend_status(None) + + self.bob.friend_delete(self.baid) + + @unittest.skip('fails') + def test_friend(self): + """ + t:friend_delete + t:friend_exists + t:friend_get_public_key + t:self_get_friend_list + t:self_get_friend_list_size + t:self_set_name + t:friend_get_name + t:friend_get_name_size + t:on_friend_name + """ + + #: Test friend request + if oTOX_OARGS.bIS_LOCAL: + iRet = self.bob_add_alice_as_friend_norequest() + if iRet < 0: + raise RuntimeError(f"bob_add_alice_as_friend_norequest {iRet}") + else: + assert self.bob_just_add_alice_as_friend() + + assert self.bob.friend_get_public_key(self.baid) == \ + self.alice.self_get_address()[:CLIENT_ID_SIZE] + + LOG.info("test_friend alice_just_add_bob_as_friend ") + #? assert self.alice_just_add_bob_as_friend() + assert self.alice_add_bob_as_friend_norequest() >= 0 + + #: Test friend_get_public_key + assert self.alice.friend_get_public_key(self.abid) == \ + self.bob.self_get_address()[:CLIENT_ID_SIZE] + + self.bob.friend_delete(self.baid) + self.alice.friend_delete(self.abid) + + @unittest.skip('fails') + @unittest.skipIf(not bIS_LOCAL and not ts.bAreWeConnected(), 'NOT CONNECTED') + def test_user_status(self): + """ + t:self_get_status + t:self_set_status + t:friend_get_status + t:friend_get_status + t:on_friend_status + """ + sSlot = 'friend_status' + if oTOX_OARGS.bIS_LOCAL: + iRet = self.bob_add_alice_as_friend_norequest() + if iRet < 0: + raise RuntimeError(f"bob_add_alice_as_friend_norequest {iRet}") + else: + assert self.bob_just_add_alice_as_friend() + + sSTATUS = TOX_USER_STATUS['AWAY'] + setattr(self.bob, sSlot, False) + def bobs_on_friend_set_status(iTox, friend_id, new_status, *largs): + LOG_INFO(f"BOBS_ON_friend_set_status " \ + +repr(friend_id) +repr(new_status)) + try: + assert friend_id == self.baid + assert new_status == sSTATUS + except Exception as e: + LOG_WARN(f"BOBS_ON_friend_set_status EXCEPTION {e}") + setattr(self.bob, sSlot, True) + + try: + assert self.loop_until_connected() + if not self.get_connection_status(): + LOG.warn(f"test_user_status NOT CONNECTED") + raise RuntimeError("not connected") + + self.bob.callback_friend_status(bobs_on_friend_set_status) + self.alice.self_set_status(sSTATUS) + self.warn_if_no_cb(self.bob, sSlot) + assert self.wait_obj_attr(self.bob, sSlot) + # wait_obj_attr count >= 15 for friend_status + + except AssertionError as e: + raise + except Exception as e: + LOG.error(f"test_user_status EXCEPTION {e}") + raise + finally: + self.bob.callback_friend_status(None) + self.warn_if_cb(self.bob, sSlot) + + self.alice.self_set_status(TOX_USER_STATUS['NONE']) + assert self.alice.self_get_status() == TOX_USER_STATUS['NONE'] + assert self.bob.friend_get_status(self.baid) == TOX_USER_STATUS['NONE'] + + self.bob.friend_delete(self.baid) + + @unittest.skip('crashes') + def test_connection_status(self): + """ + t:friend_assert_connection_status + t:on_friend_connection_status + """ + LOG.info("test_connection_status ") + if oTOX_OARGS.bIS_LOCAL: + assert self.bob_add_alice_as_friend_norequest() >= 0 + else: + assert self.bob_just_add_alice_as_friend() + + sSlot = 'friend_connection_status' + def bobs_on_friend_connection_status(iTox, friend_id, iStatus, *largs): + setattr(self.bob, sSlot, True) + LOG_INFO(f"BOBS_ON_friend_connection_status " +repr(iStatus)) + try: + assert friend_id == self.baid + except Exception as e: + LOG.error(f"BOBS_ON_friend_connection_status ERROR {e}") + + opts = oToxygenToxOptions(oTOX_OARGS) + try: + setattr(self.bob, sSlot, True) + self.bob.callback_friend_connection_status(bobs_on_friend_connection_status) + + LOG.info("test_connection_status killing alice") + self.alice.kill() #! bang + LOG.info("test_connection_status making alice") + self.alice = Tox(opts, app=oAPP) + LOG.info("test_connection_status maked alice") + + assert self.wait_obj_attr(self.bob, sSlot) + except AssertionError as e: + raise + except Exception as e: + LOG.error(f"BOBS_ON_friend_connection_status {e}") + raise + finally: + self.bob.callback_friend_connection_status(None) + + #? assert self.bob.friend_assert_connection_status(self.aid) is False + self.bob.friend_delete(self.baid) + + @unittest.skip('fails') + def test_friend_name(self): + """ + t:self_set_name + t:friend_get_name + t:friend_get_name_size + t:on_friend_name + """ + + sSlot= 'friend_name' + #: Test friend request + + assert self.bob_just_add_alice_as_friend() + + AID = self.baid + #: Test friend name + NEWNAME = 'Jenny' + + setattr(self.bob, sSlot, False) + def bobs_on_friend_name(iTox, fid, newname, iNameSize, *largs): + LOG_INFO(sSlot +" " +repr(fid)) + try: + assert fid == AID + assert str(newname, 'UTF-8') == NEWNAME + except Exception as e: + LOG.error(f"BOBS_ON_friend_name EXCEPTION {e}") + setattr(self.bob, sSlot, True) + + try: + assert self.loop_until_connected() + + self.bob.callback_friend_name(bobs_on_friend_name) + self.alice.self_set_name(NEWNAME) + assert self.wait_obj_attr(self.bob, sSlot) + self.warn_if_no_cb(self.bob, sSlot) + + assert self.bob.friend_get_name(self.baid) == NEWNAME + assert self.bob.friend_get_name_size(self.baid) == len(NEWNAME) + except AssertionError as e: + raise + except Exception as e: + LOG.error(f"test_friend EXCEPTION {e}") + raise + finally: + self.bob.callback_friend_name(None) + if hasattr(self.bob, sSlot + '_cb') and \ + getattr(self.bob, sSlot + '_cb'): + LOG.warn(sSlot + ' EXISTS') + + self.bob.friend_delete(self.baid) + + @unittest.skip('fails') + # wait_ensure_exec ArgumentError This client is currently not connected to the friend. + def test_friend_message(self): + """ + t:on_friend_action + t:on_friend_message + t:friend_send_message + """ + + #: Test message + MSG = 'Hi, Bob!' + sSlot = 'friend_message' + + if oTOX_OARGS.bIS_LOCAL: + assert self.bob_add_alice_as_friend_norequest() >= 0 + else: + assert self.bob_just_add_alice_as_friend() + if oTOX_OARGS.bIS_LOCAL: + assert self.alice_add_bob_as_friend_norequest() >= 0 + else: + # times out + assert self.alice_just_add_bob_as_friend() + + BID = self.baid + + setattr(self.bob, sSlot, False) + def alices_on_friend_message(self, fid, msg_type, message, iSize, *largs): + try: + assert fid == BID + assert msg_type == TOX_MESSAGE_TYPE['NORMAL'] + assert str(message, 'UTF-8') == MSG + except Exception as e: + LOG_ERROR(f"alices_on_friend_message EXCEPTION {e}") + else: + LOG_INFO(f"alices_on_friend_message " +repr(message)) + setattr(self.bob, sSlot, True) + + try: + assert self.loop_until_connected() + if not self.get_connection_status(): + LOG.warn(f"test_friend_message NOT CONNECTED") + raise RuntimeError("not connected") + + self.alice.callback_friend_message(alices_on_friend_message) + self.warn_if_no_cb(self.alice, sSlot) + if True: + # dunno + assert self.bob.friend_send_message( + self.baid, + TOX_MESSAGE_TYPE['NORMAL'], + bytes(MSG, 'UTF-8')) + else: + assert self.wait_ensure_exec(self.bob.friend_send_message, + [self.baid, + TOX_MESSAGE_TYPE['NORMAL'], + bytes(MSG, 'UTF-8')]) + assert self.wait_obj_attr(self.alice, sSlot) + except ArgumentError as e: + # ArgumentError('This client is currently NOT CONNECTED to the friend.') + # dunno + LOG.error(f"test_friend_message {e}") + except AssertionError as e: + LOG.warn(f"test_friend_message {e}") + raise + except Exception as e: + LOG.error(f"test_friend_message {e}") + raise + finally: + self.alice.callback_friend_message(None) + self.warn_if_cb(self.alice, sSlot) + self.bob.friend_delete(self.baid) + self.alice.friend_delete(self.abid) + + @unittest.skip('fails') + def test_friend_action(self): + """ + t:on_friend_action + t:on_friend_message + t:friend_send_message + """ + + if oTOX_OARGS.bIS_LOCAL: + iRet = self.bob_add_alice_as_friend_norequest() + if iRet < 0: + raise RuntimeError(f"bob_add_alice_as_friend_norequest {iRet}") + else: + assert self.bob_just_add_alice_as_friend() + if True: + # times out + assert self.alice_just_add_bob_as_friend() + else: + assert self.alice_add_bob_as_friend_norequest() >= 0 + + BID = self.baid + #: Test action + ACTION = 'Kick' + def alices_on_friend_action(iTox, fid, msg_type, action, *largs): + LOG_DEBUG(f"alices_on_friend_action") + assert fid == BID + assert msg_type == TOX_MESSAGE_TYPE['ACTION'] + assert action == ACTION + sSlot = 'friend_read_action' + setattr(self.bob, sSlot, True) + + def alices_on_read_reciept(iTox, fid, msg_id, *largs): + LOG_DEBUG(f"alices_on_read_reciept") + sSlot = 'friend_read_receipt' + setattr(self.alice, sSlot, True) + try: + assert fid == BID + except Exception as e: + LOG_ERROR(f"alices_on_read_reciept {e}") + + try: + assert self.loop_until_connected() + if not self.get_connection_status(): + LOG.warn(f"test_friend_message NOT CONNECTED") + raise RuntimeError("not connected") + + sSlot = 'friend_read_action' + setattr(self.bob, sSlot, False) + sSlot = 'friend_read_receipt' + setattr(self.alice, sSlot, False) + + self.alice.callback_friend_read_receipt(alices_on_read_reciept) #was alices_on_friend_action + assert self.wait_ensure_exec(self.bob.friend_send_message, + (self.baid, + TOX_MESSAGE_TYPE['ACTION'], + bytes(ACTION, 'UTF-8'))) + sSlot = 'friend_read_receipt' + assert self.wait_obj_attr(self.alice, sSlot) + except AssertionError as e: + raise + except ArgumentError as e: + # ArgumentError('This client is currently NOT CONNECTED to the friend.') + # dunno + LOG.warn(f"test_friend_action {e}") + except Exception as e: + LOG.error(f"test_friend_action {e}") + raise + finally: + self.alice.callback_friend_read_receipt(None) + + self.bob.friend_delete(self.baid) + self.alice.friend_delete(self.abid) + + @unittest.skip('fails') + def test_alice_typing_status(self): + """ + t:on_friend_read_receipt + t:on_friend_typing + t:self_set_typing + t:friend_get_typing + t:friend_get_last_online + """ + + # works + LOG.info("test_typing_status bob adding alice") + assert self.bob_just_add_alice_as_friend() + self.baid = self.bob.friend_by_public_key(self.alice._address) + assert self.bob.friend_exists(self.baid) + AID = self.baid + + sSlot = 'friend_typing' + LOG.info("test_typing_status alice adding bob") + if False: + # times out + assert self.alice_just_add_bob_as_friend() + else: + assert self.alice_add_bob_as_friend_norequest() >= 0 + self.abid = self.alice.friend_by_public_key(self.bob._address) + assert self.alice.friend_exists(self.abid) + + #: Test typing status + def bob_on_friend_typing(iTox, fid, is_typing, *largs): + setattr(self.bob, sSlot, True) + try: + assert fid == AID + assert is_typing is True + assert self.bob.friend_get_typing(fid) is True + except Exception as e: + LOG.error(f"BOB_ON_friend_typing {e!s}") + raise + else: + LOG_INFO(f"BOB_ON_friend_typing" + str(fid)) + + try: + assert self.loop_until_connected() + if not self.get_connection_status(): + LOG.warn(f"test_friend_message NOT CONNECTED") + if oTOX_OARGS.bIS_LOCAL: return + raise RuntimeError("not connected") + + setattr(self.bob, sSlot, False) + self.bob.callback_friend_typing(bob_on_friend_typing) + self.alice.self_set_typing(self.abid, True) + assert self.wait_obj_attr(self.bob, sSlot) + if not hasattr(self.bob, sSlot+'_cb') or \ + not getattr(self.bob, sSlot+'_cb'): + LOG.warn(f"self.bob.{sSlot}_cb NOT EXIST") + except AssertionError as e: + raise + except Exception as e: + LOG.error(f"test_alice_typing_status error={e}") + raise + finally: + self.bob.callback_friend_typing(None) + self.bob.friend_delete(self.baid) + self.alice.friend_delete(self.abid) + + @unittest.skip('unfinished') + def test_file_transfer(self): # unfinished + """ + t:file_send + t:file_send_chunk + t:file_control + t:file_seek + t:file_get_file_id + t:on_file_recv + t:on_file_recv_control + t:on_file_recv_chunk + t:on_file_chunk_request + """ + + self.bob_add_alice_as_friend_norequest() + self.baid = self.bob.friend_by_public_key(self.alice._address) + BID = self.baid + + FRIEND_NUMBER = self.baid + FILE_NUMBER = 1 + FILE = os.urandom(1024 * 1024) + FILE_NAME = b"/tmp/test.bin" + if not os.path.exists(FILE_NAME): + with open(FILE_NAME, 'wb') as oFd: + oFd.write(FILE) + FILE_SIZE = len(FILE) + OFFSET = 567 + + m = hashlib.md5() + m.update(FILE[OFFSET:]) + FILE_DIGEST = m.hexdigest() + + CONTEXT = { 'FILE': bytes(), 'RECEIVED': 0, 'START': False, 'SENT': 0 } + + def alice_on_file_recv(iTox, fid, file_number, kind, size, filename): + LOG_DEBUG(f"ALICE_ON_file_recv fid={fid} {file_number}") + try: + assert fid == BID + assert size == FILE_SIZE + assert filename == FILE_NAME + retv = self.alice.file_seek(fid, file_number, OFFSET) + assert retv is True + self.alice.file_control(fid, file_number, TOX_FILE_CONTROL['RESUME']) + except Exception as e: + LOG_ERROR(f"ALICE_ON_file_recv {e!s}") + else: + LOG_INFO(f"ALICE_ON_file_recv " + str(fid)) + + def alice_on_file_recv_control(iTox, fid, file_number, control, *largs): + # TOX_FILE_CONTROL = { 'RESUME': 0, 'PAUSE': 1, 'CANCEL': 2,} + LOG_DEBUG(f"ALICE_ON_file_recv_control fid={fid} {file_number} {control}") + try: + assert fid == BID + assert FILE_NUMBER == file_number + if control == Tox.FILE_CONTROL_FINISHED: + # assert CONTEXT['RECEIVED'] == FILE_SIZE + # m = hashlib.md5() + # m.update(CONTEXT['FILE']) + # assert m.hexdigest() == FILE_DIGEST + self.alice.completed = True + except Exception as e: + LOG_ERROR(f"ALICE_ON_file_recv {e!s}") + else: + LOG_INFO(f"ALICE_ON_file_recv " + str(fid)) + + self.alice.completed = False + def alice_on_file_recv_chunk(iTox, fid, file_number, position, iNumBytes, *largs): + LOG_DEBUG(f"ALICE_ON_file_recv_chunk {fid} {file_number}") + # FixMe - use file_number and iNumBytes to get data? + data = '' + try: + assert fid == BID + if data is None: + assert CONTEXT['RECEIVED'] == (FILE_SIZE - OFFSET) + m = hashlib.md5() + m.update(CONTEXT['FILE']) + assert m.hexdigest() == FILE_DIGEST + self.alice.completed = True + self.alice.file_control(fid, file_number, TOX_FILE_CONTROL['CANCEL']) + return + + CONTEXT['FILE'] += data + CONTEXT['RECEIVED'] += len(data) + # if CONTEXT['RECEIVED'] < FILE_SIZE: + # assert self.file_data_remaining( + # fid, file_number, 1) == FILE_SIZE - CONTEXT['RECEIVED'] + except Exception as e: + LOG_ERROR(f"ALICE_ON_file_recv_chunk {e!s}") + else: + LOG_INFO(f"ALICE_ON_file_recv_chunk {fid}") + + # AliceTox.on_file_send_request = on_file_send_request + # AliceTox.on_file_control = on_file_control + # AliceTox.on_file_data = on_file_data + + LOG.info(f"test_file_transfer: baid={self.baid}") + try: + self.alice.callback_file_recv(alice_on_file_recv) + self.alice.callback_file_recv_control(alice_on_file_recv_control) + self.alice.callback_file_recv_chunk(alice_on_file_recv_chunk) + + self.bob.completed = False + def bob_on_file_recv_control2(iTox, fid, file_number, control): + LOG_DEBUG(f"BOB_ON_file_recv_control2 {fid} {file_number} control={control}") + if control == TOX_FILE_CONTROL['RESUME']: + CONTEXT['START'] = True + elif control == TOX_FILE_CONTROL['CANCEL']: + self.bob.completed = True + pass + + def bob_on_file_chunk_request(iTox, fid, file_number, position, length, *largs): + LOG_DEBUG(f"BOB_ON_file_chunk_request {fid} {file_number}") + if length == 0: + return + data = FILE[position:(position + length)] + self.bob.file_send_chunk(fid, file_number, position, data) + + sSlot = 'file_recv_control' + self.bob.callback_file_recv_control(bob_on_file_recv_control2) + self.bob.callback_file_chunk_request(bob_on_file_chunk_request) + + # was FILE_ID = FILE_NAME + FILE_ID = 32*'1' # + FILE_NAME = b'test.in' + + assert self.loop_until_connected() + if not self.get_connection_status(): + LOG.warn(f"test_file_transfer NOT CONNECTED") + raise RuntimeError("not connected") + + i = 0 + iKind = 0 + while i < 2: + i += 1 + try: + FN = self.bob.file_send(self.baid, iKind, FILE_SIZE, FILE_ID, FILE_NAME) + LOG.info(f"test_file_transfer bob.file_send {FN}") + except ArgumentError as e: + LOG.debug(f"test_file_transfer bob.file_send {e} {i}") + # ctypes.ArgumentError: This client is currently not connected to the friend. + raise + else: + break + self.loop(100) + sleep(1) + else: + LOG.error(f"test_file_transfer bob.file_send 2") + raise RuntimeError(f"test_file_transfer bob.file_send {THRESHOLD // 2}") + + # UINT32_MAX + FID = self.bob.file_get_file_id(self.baid, FN) + hexFID = "".join([hex(ord(c))[2:].zfill(2) for c in FILE_NAME]) + assert FID.startswith(hexFID.upper()) + + if not self.wait_objs_attr([self.alice, self.bob], 'completed'): + LOG.warn(f"test_file_transfer not callbacks") + return False + + except (ArgumentError, ValueError,) as e: + # ValueError: non-hexadecimal number found in fromhex() arg at position 0 + LOG_ERROR(f"test_file_transfer: {e}") + raise + + except Exception as e: + LOG_ERROR(f"test_file_transfer:: {e}") + LOG_DEBUG('\n' + traceback.format_exc()) + raise + + finally: + self.alice.callback_file_recv(None) + self.alice.callback_file_recv_control(None) + self.alice.callback_file_recv_chunk(None) + self.bob.callback_file_recv_control(None) + self.bob.callback_file_chunk_request(None) + + LOG_INFO(f"test_file_transfer:: self.wait_objs_attr completed") + + def test_tox_savedata(self): # works sorta + # but "{addr} != {self.alice.self_get_address()}" + """ + t:get_savedata_size + t:get_savedata + """ + + assert self.alice.get_savedata_size() > 0 + data = self.alice.get_savedata() + assert data is not None + addr = self.alice.self_get_address() + # self._address + + try: + self.alice._kill_toxav() + self.alice._kill_tox() + self.alice.kill() + except: pass + + oArgs = oTOX_OARGS + opts = oToxygenToxOptions(oArgs) + opts.savedata_data = data + opts.savedata_length = len(data) + + self.alice = Tox(tox_options=opts) + if addr != self.alice.self_get_address(): + LOG.warn("test_tox_savedata " + + f"{addr} != {self.alice.self_get_address()}") + else: + LOG.info("passed test_tox_savedata") + +### + +def iMain(oArgs): + failfast=True + + ts.vOargsToxPreamble(oArgs, Tox, ToxSuite) + # https://stackoverflow.com/questions/35930811/how-to-sort-unittest-testcases-properly/35930812#35930812 + cases = ts.suiteFactory(*ts.caseFactory([ToxSuite])) + if color_runner: + runner = color_runner.runner.TextTestRunner(verbosity=2,failfast=failfast) + else: + runner = unittest.TextTestRunner(verbosity=2,failfast=failfast) + runner.run(cases) + +def oToxygenToxOptions(oArgs): + data = None + tox_options = wrapper.tox.Tox.options_new() + if oArgs.proxy_type: + tox_options.contents.proxy_type = int(oArgs.proxy_type) + tox_options.contents.proxy_host = bytes(oArgs.proxy_host, 'UTF-8') + tox_options.contents.proxy_port = int(oArgs.proxy_port) + tox_options.contents.udp_enabled = False + else: + tox_options.contents.udp_enabled = oArgs.udp_enabled + if not os.path.exists('/proc/sys/net/ipv6'): + oArgs.ipv6_enabled = False + + tox_options.contents.tcp_port = int(oArgs.tcp_port) + + # overrides + tox_options.contents.local_discovery_enabled = False + tox_options.contents.dht_announcements_enabled = True + tox_options.contents.hole_punching_enabled = False + tox_options.contents.experimental_thread_safety = False + # REQUIRED!! + if oArgs.ipv6_enabled and not os.path.exists('/proc/sys/net/ipv6'): + LOG.warn('Disabling IPV6 because /proc/sys/net/ipv6 does not exist' + repr(oArgs.ipv6_enabled)) + tox_options.contents.ipv6_enabled = False + else: + tox_options.contents.ipv6_enabled = bool(oArgs.ipv6_enabled) + + if data: # load existing profile + tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE'] + tox_options.contents.savedata_data = c_char_p(data) + tox_options.contents.savedata_length = len(data) + else: # create new profile + tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE'] + tox_options.contents.savedata_data = None + tox_options.contents.savedata_length = 0 + + #? tox_options.contents.log_callback = LOG + if tox_options._options_pointer: + # LOG.debug("Adding logging to tox_options._options_pointer ") + ts.vAddLoggerCallback(tox_options, ts.on_log) + else: + LOG.warn("No tox_options._options_pointer " +repr(tox_options._options_pointer)) + + return tox_options + +def oArgparse(lArgv): + parser = ts.oMainArgparser() + parser.add_argument('profile', type=str, nargs='?', default=None, + help='Path to Tox profile') + oArgs = parser.parse_args(lArgv) + + for key in ts.lBOOLEANS: + if key not in oArgs: continue + val = getattr(oArgs, key) + setattr(oArgs, key, bool(val)) + + if hasattr(oArgs, 'sleep'): + if oArgs.sleep == 'qt': + pass # broken or gevent.sleep(idle_period) + elif oArgs.sleep == 'gevent': + pass # broken or gevent.sleep(idle_period) + else: + oArgs.sleep = 'time' + + return oArgs + +def main(lArgs=None): + global oTOX_OARGS + if lArgs is None: lArgs = [] + print(lArgs) + oArgs = oArgparse(lArgs) + global bIS_LOCAL + bIS_LOCAL = oArgs.network in ['newlocal', 'localnew', 'local'] + oTOX_OARGS = oArgs + setattr(oTOX_OARGS, 'bIS_LOCAL', bIS_LOCAL) + print(oArgs) + bIS_LOCAL = True + setattr(oTOX_OARGS, 'bIS_LOCAL', bIS_LOCAL) + # oTOX_OPTIONS = ToxOptions() + global oTOX_OPTIONS + oTOX_OPTIONS = oToxygenToxOptions(oArgs) + if coloredlogs: + # https://pypi.org/project/coloredlogs/ + coloredlogs.install(level=oArgs.loglevel, + logger=LOG, + # %(asctime)s,%(msecs)03d %(hostname)s [%(process)d] + fmt='%(name)s %(levelname)s %(message)s' + ) + else: + logging.basicConfig(level=oArgs.loglevel) # logging.INFO + + return iMain(oArgs) + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) + +# Ran 34 tests in 86.589s OK (skipped=12) diff --git a/wrapper/tox.py b/wrapper/tox.py index 038d7c2..5ce5f17 100644 --- a/wrapper/tox.py +++ b/wrapper/tox.py @@ -273,7 +273,7 @@ class Tox: :return: True on success. """ - LOG_DEBUG(f"tox_bootstrap={address}") + LOG_TRACE(f"tox_bootstrap={address}") address = bytes(address, 'utf-8') tox_err_bootstrap = c_int() try: @@ -314,7 +314,7 @@ class Tox: :return: True on success. """ - LOG_DEBUG(f"tox_add_tcp_relay address={address}") + LOG_TRACE(f"tox_add_tcp_relay address={address}") address = bytes(address, 'utf-8') tox_err_bootstrap = c_int() result = Tox.libtoxcore.tox_add_tcp_relay(self._tox_pointer,