conftest.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. import os
  2. import sys
  3. import urllib.request
  4. import time
  5. import logging
  6. import json
  7. import shutil
  8. import gc
  9. import datetime
  10. import atexit
  11. import pytest
  12. import mock
  13. import gevent
  14. import gevent.event
  15. from gevent import monkey
  16. monkey.patch_all(thread=False, subprocess=False)
  17. def pytest_addoption(parser):
  18. parser.addoption("--slow", action='store_true', default=False, help="Also run slow tests")
  19. def pytest_collection_modifyitems(config, items):
  20. if config.getoption("--slow"):
  21. # --runslow given in cli: do not skip slow tests
  22. return
  23. skip_slow = pytest.mark.skip(reason="need --slow option to run")
  24. for item in items:
  25. if "slow" in item.keywords:
  26. item.add_marker(skip_slow)
  27. # Config
  28. if sys.platform == "win32":
  29. CHROMEDRIVER_PATH = "tools/chrome/chromedriver.exe"
  30. else:
  31. CHROMEDRIVER_PATH = "chromedriver"
  32. SITE_URL = "http://127.0.0.1:43110"
  33. TEST_DATA_PATH = 'src/Test/testdata'
  34. sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + "/../lib")) # External modules directory
  35. sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + "/..")) # Imports relative to src dir
  36. from Config import config
  37. config.argv = ["none"] # Dont pass any argv to config parser
  38. config.parse(silent=True, parse_config=False) # Plugins need to access the configuration
  39. config.action = "test"
  40. # Load plugins
  41. from Plugin import PluginManager
  42. config.data_dir = TEST_DATA_PATH # Use test data for unittests
  43. config.debug = True
  44. os.chdir(os.path.abspath(os.path.dirname(__file__) + "/../..")) # Set working dir
  45. all_loaded = PluginManager.plugin_manager.loadPlugins()
  46. assert all_loaded, "Not all plugin loaded successfully"
  47. config.loadPlugins()
  48. config.parse(parse_config=False) # Parse again to add plugin configuration options
  49. config.action = "test"
  50. config.debug = True
  51. config.debug_socket = True # Use test data for unittests
  52. config.verbose = True # Use test data for unittests
  53. config.tor = "disable" # Don't start Tor client
  54. config.trackers = []
  55. config.data_dir = TEST_DATA_PATH # Use test data for unittests
  56. config.initLogging(console_logging=False)
  57. # Set custom formatter with realative time format (via: https://stackoverflow.com/questions/31521859/python-logging-module-time-since-last-log)
  58. class TimeFilter(logging.Filter):
  59. def filter(self, record):
  60. try:
  61. last = self.last
  62. except AttributeError:
  63. last = record.relativeCreated
  64. delta = datetime.datetime.fromtimestamp(record.relativeCreated / 1000.0) - datetime.datetime.fromtimestamp(last / 1000.0)
  65. record.relative = '{0:.3f}'.format(delta.seconds + delta.microseconds / 1000000.0)
  66. self.last = record.relativeCreated
  67. return True
  68. log = logging.getLogger()
  69. fmt = logging.Formatter(fmt='+%(relative)ss %(levelname)-8s %(name)s %(message)s')
  70. [hndl.addFilter(TimeFilter()) for hndl in log.handlers]
  71. [hndl.setFormatter(fmt) for hndl in log.handlers]
  72. from Site.Site import Site
  73. from Site import SiteManager
  74. from User import UserManager
  75. from File import FileServer
  76. from Connection import ConnectionServer
  77. from Crypt import CryptConnection
  78. from Crypt import CryptBitcoin
  79. from Ui import UiWebsocket
  80. from Tor import TorManager
  81. from Content import ContentDb
  82. from util import RateLimit
  83. from Db import Db
  84. from Debug import Debug
  85. def cleanup():
  86. Db.dbCloseAll()
  87. for dir_path in [config.data_dir, config.data_dir + "-temp"]:
  88. if os.path.isdir(dir_path):
  89. for file_name in os.listdir(dir_path):
  90. ext = file_name.rsplit(".", 1)[-1]
  91. if ext not in ["csr", "pem", "srl", "db", "json", "tmp"]:
  92. continue
  93. file_path = dir_path + "/" + file_name
  94. if os.path.isfile(file_path):
  95. os.unlink(file_path)
  96. atexit.register(cleanup)
  97. @pytest.fixture(scope="session")
  98. def resetSettings(request):
  99. open("%s/sites.json" % config.data_dir, "w").write("{}")
  100. open("%s/filters.json" % config.data_dir, "w").write("{}")
  101. open("%s/users.json" % config.data_dir, "w").write("""
  102. {
  103. "15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc": {
  104. "certs": {},
  105. "master_seed": "024bceac1105483d66585d8a60eaf20aa8c3254b0f266e0d626ddb6114e2949a",
  106. "sites": {}
  107. }
  108. }
  109. """)
  110. @pytest.fixture(scope="session")
  111. def resetTempSettings(request):
  112. data_dir_temp = config.data_dir + "-temp"
  113. if not os.path.isdir(data_dir_temp):
  114. os.mkdir(data_dir_temp)
  115. open("%s/sites.json" % data_dir_temp, "w").write("{}")
  116. open("%s/filters.json" % data_dir_temp, "w").write("{}")
  117. open("%s/users.json" % data_dir_temp, "w").write("""
  118. {
  119. "15E5rhcAUD69WbiYsYARh4YHJ4sLm2JEyc": {
  120. "certs": {},
  121. "master_seed": "024bceac1105483d66585d8a60eaf20aa8c3254b0f266e0d626ddb6114e2949a",
  122. "sites": {}
  123. }
  124. }
  125. """)
  126. def cleanup():
  127. os.unlink("%s/sites.json" % data_dir_temp)
  128. os.unlink("%s/users.json" % data_dir_temp)
  129. os.unlink("%s/filters.json" % data_dir_temp)
  130. request.addfinalizer(cleanup)
  131. @pytest.fixture()
  132. def site(request):
  133. threads_before = [obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet)]
  134. # Reset ratelimit
  135. RateLimit.queue_db = {}
  136. RateLimit.called_db = {}
  137. site = Site("1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT")
  138. # Always use original data
  139. assert "1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT" in site.storage.getPath("") # Make sure we dont delete everything
  140. shutil.rmtree(site.storage.getPath(""), True)
  141. shutil.copytree(site.storage.getPath("") + "-original", site.storage.getPath(""))
  142. # Add to site manager
  143. SiteManager.site_manager.get("1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT")
  144. site.announce = mock.MagicMock(return_value=True) # Don't try to find peers from the net
  145. def cleanup():
  146. site.storage.deleteFiles()
  147. site.content_manager.contents.db.deleteSite(site)
  148. del SiteManager.site_manager.sites["1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT"]
  149. site.content_manager.contents.db.close()
  150. SiteManager.site_manager.sites.clear()
  151. db_path = "%s/content.db" % config.data_dir
  152. os.unlink(db_path)
  153. del ContentDb.content_dbs[db_path]
  154. gevent.killall([obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet) and obj not in threads_before])
  155. request.addfinalizer(cleanup)
  156. site = Site("1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT") # Create new Site object to load content.json files
  157. if not SiteManager.site_manager.sites:
  158. SiteManager.site_manager.sites = {}
  159. SiteManager.site_manager.sites["1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT"] = site
  160. return site
  161. @pytest.fixture()
  162. def site_temp(request):
  163. threads_before = [obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet)]
  164. with mock.patch("Config.config.data_dir", config.data_dir + "-temp"):
  165. site_temp = Site("1TeSTvb4w2PWE81S2rEELgmX2GCCExQGT")
  166. site_temp.announce = mock.MagicMock(return_value=True) # Don't try to find peers from the net
  167. def cleanup():
  168. site_temp.storage.deleteFiles()
  169. site_temp.content_manager.contents.db.deleteSite(site_temp)
  170. site_temp.content_manager.contents.db.close()
  171. db_path = "%s-temp/content.db" % config.data_dir
  172. os.unlink(db_path)
  173. del ContentDb.content_dbs[db_path]
  174. gevent.killall([obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet) and obj not in threads_before])
  175. request.addfinalizer(cleanup)
  176. return site_temp
  177. @pytest.fixture(scope="session")
  178. def user():
  179. user = UserManager.user_manager.get()
  180. if not user:
  181. user = UserManager.user_manager.create()
  182. user.sites = {} # Reset user data
  183. return user
  184. @pytest.fixture(scope="session")
  185. def browser(request):
  186. try:
  187. from selenium import webdriver
  188. print("Starting chromedriver...")
  189. options = webdriver.chrome.options.Options()
  190. options.add_argument("--headless")
  191. options.add_argument("--window-size=1920x1080")
  192. options.add_argument("--log-level=1")
  193. browser = webdriver.Chrome(executable_path=CHROMEDRIVER_PATH, service_log_path=os.path.devnull, options=options)
  194. def quit():
  195. browser.quit()
  196. request.addfinalizer(quit)
  197. except Exception as err:
  198. raise pytest.skip("Test requires selenium + chromedriver: %s" % err)
  199. return browser
  200. @pytest.fixture(scope="session")
  201. def site_url():
  202. try:
  203. urllib.request.urlopen(SITE_URL).read()
  204. except Exception as err:
  205. raise pytest.skip("Test requires zeronet client running: %s" % err)
  206. return SITE_URL
  207. @pytest.fixture(params=['ipv4', 'ipv6'])
  208. def file_server(request):
  209. if request.param == "ipv4":
  210. return request.getfixturevalue("file_server4")
  211. else:
  212. return request.getfixturevalue("file_server6")
  213. @pytest.fixture
  214. def file_server4(request):
  215. time.sleep(0.1)
  216. file_server = FileServer("127.0.0.1", 1544)
  217. file_server.ip_external = "1.2.3.4" # Fake external ip
  218. def listen():
  219. ConnectionServer.start(file_server)
  220. ConnectionServer.listen(file_server)
  221. gevent.spawn(listen)
  222. # Wait for port opening
  223. for retry in range(10):
  224. time.sleep(0.1) # Port opening
  225. try:
  226. conn = file_server.getConnection("127.0.0.1", 1544)
  227. conn.close()
  228. break
  229. except Exception as err:
  230. print("FileServer6 startup error", Debug.formatException(err))
  231. assert file_server.running
  232. file_server.ip_incoming = {} # Reset flood protection
  233. def stop():
  234. file_server.stop()
  235. request.addfinalizer(stop)
  236. return file_server
  237. @pytest.fixture
  238. def file_server6(request):
  239. time.sleep(0.1)
  240. file_server6 = FileServer("::1", 1544)
  241. file_server6.ip_external = 'fca5:95d6:bfde:d902:8951:276e:1111:a22c' # Fake external ip
  242. def listen():
  243. ConnectionServer.start(file_server6)
  244. ConnectionServer.listen(file_server6)
  245. gevent.spawn(listen)
  246. # Wait for port opening
  247. for retry in range(10):
  248. time.sleep(0.1) # Port opening
  249. try:
  250. conn = file_server6.getConnection("::1", 1544)
  251. conn.close()
  252. break
  253. except Exception as err:
  254. print("FileServer6 startup error", Debug.formatException(err))
  255. assert file_server6.running
  256. file_server6.ip_incoming = {} # Reset flood protection
  257. def stop():
  258. file_server6.stop()
  259. request.addfinalizer(stop)
  260. return file_server6
  261. @pytest.fixture()
  262. def ui_websocket(site, user):
  263. class WsMock:
  264. def __init__(self):
  265. self.result = gevent.event.AsyncResult()
  266. def send(self, data):
  267. self.result.set(json.loads(data)["result"])
  268. def getResult(self):
  269. back = self.result.get()
  270. self.result = gevent.event.AsyncResult()
  271. return back
  272. ws_mock = WsMock()
  273. ui_websocket = UiWebsocket(ws_mock, site, None, user, None)
  274. def testAction(action, *args, **kwargs):
  275. ui_websocket.handleRequest({"id": 0, "cmd": action, "params": list(args) if args else kwargs})
  276. return ui_websocket.ws.getResult()
  277. ui_websocket.testAction = testAction
  278. return ui_websocket
  279. @pytest.fixture(scope="session")
  280. def tor_manager():
  281. try:
  282. tor_manager = TorManager(fileserver_port=1544)
  283. tor_manager.start()
  284. assert tor_manager.conn is not None
  285. tor_manager.startOnions()
  286. except Exception as err:
  287. raise pytest.skip("Test requires Tor with ControlPort: %s, %s" % (config.tor_controller, err))
  288. return tor_manager
  289. @pytest.fixture()
  290. def db(request):
  291. db_path = "%s/zeronet.db" % config.data_dir
  292. schema = {
  293. "db_name": "TestDb",
  294. "db_file": "%s/zeronet.db" % config.data_dir,
  295. "maps": {
  296. "data.json": {
  297. "to_table": [
  298. "test",
  299. {"node": "test", "table": "test_importfilter", "import_cols": ["test_id", "title"]}
  300. ]
  301. }
  302. },
  303. "tables": {
  304. "test": {
  305. "cols": [
  306. ["test_id", "INTEGER"],
  307. ["title", "TEXT"],
  308. ["json_id", "INTEGER REFERENCES json (json_id)"]
  309. ],
  310. "indexes": ["CREATE UNIQUE INDEX test_id ON test(test_id)"],
  311. "schema_changed": 1426195822
  312. },
  313. "test_importfilter": {
  314. "cols": [
  315. ["test_id", "INTEGER"],
  316. ["title", "TEXT"],
  317. ["json_id", "INTEGER REFERENCES json (json_id)"]
  318. ],
  319. "indexes": ["CREATE UNIQUE INDEX test_importfilter_id ON test_importfilter(test_id)"],
  320. "schema_changed": 1426195822
  321. }
  322. }
  323. }
  324. if os.path.isfile(db_path):
  325. os.unlink(db_path)
  326. db = Db.Db(schema, db_path)
  327. db.checkTables()
  328. def stop():
  329. db.close()
  330. os.unlink(db_path)
  331. request.addfinalizer(stop)
  332. return db
  333. @pytest.fixture(params=["btctools", "openssl", "libsecp256k1"])
  334. def crypt_bitcoin_lib(request, monkeypatch):
  335. monkeypatch.setattr(CryptBitcoin, "lib_verify_best", request.param)
  336. CryptBitcoin.loadLib(request.param)
  337. return CryptBitcoin
  338. # Workaround for pytest>=0.4.1 bug when logging in atexit handlers (I/O operation on closed file)
  339. @pytest.fixture(scope='session', autouse=True)
  340. def disableLog():
  341. yield None # Wait until all test done
  342. logging.getLogger('').setLevel(logging.getLevelName(logging.CRITICAL))