X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=test.py;h=ac9a6cd55b3ccf81b14f926939b359e14586ca70;hb=83c698c925db9dcb2d347c2a625de88d85cfeb21;hp=d4432f6e7a87636efc8c94913382b385177a4841;hpb=ada1b5f109ebaa6f3adb7cd87b007c6db891811c;p=perso%2FImmae%2FProjets%2FCryptomonnaies%2FCryptoportfolio%2FTrader.git diff --git a/test.py b/test.py index d4432f6..ac9a6cd 100644 --- a/test.py +++ b/test.py @@ -7,6 +7,7 @@ from unittest import mock import requests import requests_mock from io import StringIO +import threading import portfolio, market, main, store limits = ["acceptance", "unit"] @@ -33,10 +34,14 @@ class WebMockTestCase(unittest.TestCase): self.patchers = [ mock.patch.multiple(market.Portfolio, - last_date=None, - data=None, - liquidities={}, - report=mock.Mock()), + data=store.LockedVar(None), + liquidities=store.LockedVar({}), + last_date=store.LockedVar(None), + report=mock.Mock(), + worker=None, + worker_notify=None, + worker_started=False, + callback=None), mock.patch.multiple(portfolio.Computation, computations=portfolio.Computation.computations), ] @@ -441,6 +446,99 @@ class poloniexETest(unittest.TestCase): create_order.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params") +@unittest.skipUnless("unit" in limits, "Unit skipped") +class NoopLockTest(unittest.TestCase): + def test_with(self): + noop_lock = store.NoopLock() + with noop_lock: + self.assertTrue(True) + +@unittest.skipUnless("unit" in limits, "Unit skipped") +class LockedVar(unittest.TestCase): + + def test_values(self): + locked_var = store.LockedVar("Foo") + self.assertIsInstance(locked_var.lock, store.NoopLock) + self.assertEqual("Foo", locked_var.val) + + def test_get(self): + with self.subTest(desc="Normal case"): + locked_var = store.LockedVar("Foo") + self.assertEqual("Foo", locked_var.get()) + with self.subTest(desc="Dict"): + locked_var = store.LockedVar({"foo": "bar"}) + self.assertEqual({"foo": "bar"}, locked_var.get()) + self.assertEqual("bar", locked_var.get("foo")) + self.assertIsNone(locked_var.get("other")) + + def test_set(self): + locked_var = store.LockedVar("Foo") + locked_var.set("Bar") + self.assertEqual("Bar", locked_var.get()) + + def test__getattr(self): + dummy = type('Dummy', (object,), {})() + dummy.attribute = "Hey" + + locked_var = store.LockedVar(dummy) + self.assertEqual("Hey", locked_var.attribute) + with self.assertRaises(AttributeError): + locked_var.other + + def test_start_lock(self): + locked_var = store.LockedVar("Foo") + locked_var.start_lock() + self.assertEqual("lock", locked_var.lock.__class__.__name__) + + thread1 = threading.Thread(target=locked_var.set, args=["Bar1"]) + thread2 = threading.Thread(target=locked_var.set, args=["Bar2"]) + thread3 = threading.Thread(target=locked_var.set, args=["Bar3"]) + + with locked_var.lock: + thread1.start() + thread2.start() + thread3.start() + + self.assertEqual("Foo", locked_var.val) + thread1.join() + thread2.join() + thread3.join() + self.assertEqual("Bar", locked_var.get()[0:3]) + + def test_wait_for_notification(self): + with self.assertRaises(RuntimeError): + store.Portfolio.wait_for_notification() + + with mock.patch.object(store.Portfolio, "get_cryptoportfolio") as get,\ + mock.patch.object(store.Portfolio, "report") as report,\ + mock.patch.object(store.time, "sleep") as sleep: + store.Portfolio.start_worker(poll=3) + + store.Portfolio.worker_notify.set() + + store.Portfolio.callback.wait() + + report.print_log.assert_called_once_with("Fetching cryptoportfolio") + get.assert_called_once_with(refetch=True) + sleep.assert_called_once_with(3) + self.assertFalse(store.Portfolio.worker_notify.is_set()) + self.assertTrue(store.Portfolio.worker.is_alive()) + + store.Portfolio.callback.clear() + store.Portfolio.worker_started = False + store.Portfolio.worker_notify.set() + store.Portfolio.callback.wait() + + self.assertFalse(store.Portfolio.worker.is_alive()) + + def test_notify_and_wait(self): + with mock.patch.object(store.Portfolio, "callback") as callback,\ + mock.patch.object(store.Portfolio, "worker_notify") as worker_notify: + store.Portfolio.notify_and_wait() + callback.clear.assert_called_once_with() + worker_notify.set.assert_called_once_with() + callback.wait.assert_called_once_with() + @unittest.skipUnless("unit" in limits, "Unit skipped") class PortfolioTest(WebMockTestCase): def setUp(self): @@ -453,86 +551,131 @@ class PortfolioTest(WebMockTestCase): @mock.patch.object(market.Portfolio, "parse_cryptoportfolio") def test_get_cryptoportfolio(self, parse_cryptoportfolio): - self.wm.get(market.Portfolio.URL, [ - {"text":'{ "foo": "bar" }', "status_code": 200}, - {"text": "System Error", "status_code": 500}, - {"exc": requests.exceptions.ConnectTimeout}, - ]) - market.Portfolio.get_cryptoportfolio() - self.assertIn("foo", market.Portfolio.data) - self.assertEqual("bar", market.Portfolio.data["foo"]) - self.assertTrue(self.wm.called) - self.assertEqual(1, self.wm.call_count) - market.Portfolio.report.log_error.assert_not_called() - market.Portfolio.report.log_http_request.assert_called_once() - parse_cryptoportfolio.assert_called_once_with() - market.Portfolio.report.log_http_request.reset_mock() - parse_cryptoportfolio.reset_mock() - market.Portfolio.data = None - - market.Portfolio.get_cryptoportfolio() - self.assertIsNone(market.Portfolio.data) - self.assertEqual(2, self.wm.call_count) - parse_cryptoportfolio.assert_not_called() - market.Portfolio.report.log_error.assert_not_called() - market.Portfolio.report.log_http_request.assert_called_once() - market.Portfolio.report.log_http_request.reset_mock() - parse_cryptoportfolio.reset_mock() - - market.Portfolio.data = "Foo" - market.Portfolio.get_cryptoportfolio() - self.assertEqual(2, self.wm.call_count) - parse_cryptoportfolio.assert_not_called() - - market.Portfolio.get_cryptoportfolio(refetch=True) - self.assertEqual("Foo", market.Portfolio.data) - self.assertEqual(3, self.wm.call_count) - market.Portfolio.report.log_error.assert_called_once_with("get_cryptoportfolio", - exception=mock.ANY) - market.Portfolio.report.log_http_request.assert_not_called() + with self.subTest(parallel=False): + self.wm.get(market.Portfolio.URL, [ + {"text":'{ "foo": "bar" }', "status_code": 200}, + {"text": "System Error", "status_code": 500}, + {"exc": requests.exceptions.ConnectTimeout}, + ]) + market.Portfolio.get_cryptoportfolio() + self.assertIn("foo", market.Portfolio.data.get()) + self.assertEqual("bar", market.Portfolio.data.get()["foo"]) + self.assertTrue(self.wm.called) + self.assertEqual(1, self.wm.call_count) + market.Portfolio.report.log_error.assert_not_called() + market.Portfolio.report.log_http_request.assert_called_once() + parse_cryptoportfolio.assert_called_once_with() + market.Portfolio.report.log_http_request.reset_mock() + parse_cryptoportfolio.reset_mock() + market.Portfolio.data = store.LockedVar(None) + + market.Portfolio.get_cryptoportfolio() + self.assertIsNone(market.Portfolio.data.get()) + self.assertEqual(2, self.wm.call_count) + parse_cryptoportfolio.assert_not_called() + market.Portfolio.report.log_error.assert_not_called() + market.Portfolio.report.log_http_request.assert_called_once() + market.Portfolio.report.log_http_request.reset_mock() + parse_cryptoportfolio.reset_mock() + + market.Portfolio.data = store.LockedVar("Foo") + market.Portfolio.get_cryptoportfolio() + self.assertEqual(2, self.wm.call_count) + parse_cryptoportfolio.assert_not_called() + + market.Portfolio.get_cryptoportfolio(refetch=True) + self.assertEqual("Foo", market.Portfolio.data.get()) + self.assertEqual(3, self.wm.call_count) + market.Portfolio.report.log_error.assert_called_once_with("get_cryptoportfolio", + exception=mock.ANY) + market.Portfolio.report.log_http_request.assert_not_called() + with self.subTest(parallel=True): + with mock.patch.object(market.Portfolio, "is_worker_thread") as is_worker,\ + mock.patch.object(market.Portfolio, "notify_and_wait") as notify: + with self.subTest(worker=True): + market.Portfolio.data = store.LockedVar(None) + market.Portfolio.worker = mock.Mock() + is_worker.return_value = True + self.wm.get(market.Portfolio.URL, [ + {"text":'{ "foo": "bar" }', "status_code": 200}, + ]) + market.Portfolio.get_cryptoportfolio() + self.assertIn("foo", market.Portfolio.data.get()) + parse_cryptoportfolio.reset_mock() + with self.subTest(worker=False): + market.Portfolio.data = store.LockedVar(None) + market.Portfolio.worker = mock.Mock() + is_worker.return_value = False + market.Portfolio.get_cryptoportfolio() + notify.assert_called_once_with() + parse_cryptoportfolio.assert_not_called() def test_parse_cryptoportfolio(self): - market.Portfolio.data = store.json.loads(self.json_response, parse_int=D, - parse_float=D) - market.Portfolio.parse_cryptoportfolio() - - self.assertListEqual( - ["medium", "high"], - list(market.Portfolio.liquidities.keys())) - - liquidities = market.Portfolio.liquidities - self.assertEqual(10, len(liquidities["medium"].keys())) - self.assertEqual(10, len(liquidities["high"].keys())) - - expected = { - 'BTC': (D("0.2857"), "long"), - 'DGB': (D("0.1015"), "long"), - 'DOGE': (D("0.1805"), "long"), - 'SC': (D("0.0623"), "long"), - 'ZEC': (D("0.3701"), "long"), - } - date = portfolio.datetime(2018, 1, 8) - self.assertDictEqual(expected, liquidities["high"][date]) - - expected = { - 'BTC': (D("1.1102e-16"), "long"), - 'ETC': (D("0.1"), "long"), - 'FCT': (D("0.1"), "long"), - 'GAS': (D("0.1"), "long"), - 'NAV': (D("0.1"), "long"), - 'OMG': (D("0.1"), "long"), - 'OMNI': (D("0.1"), "long"), - 'PPC': (D("0.1"), "long"), - 'RIC': (D("0.1"), "long"), - 'VIA': (D("0.1"), "long"), - 'XCP': (D("0.1"), "long"), - } - self.assertDictEqual(expected, liquidities["medium"][date]) - self.assertEqual(portfolio.datetime(2018, 1, 15), market.Portfolio.last_date) + with self.subTest(description="Normal case"): + market.Portfolio.data = store.LockedVar(store.json.loads( + self.json_response, parse_int=D, parse_float=D)) + market.Portfolio.parse_cryptoportfolio() + + self.assertListEqual( + ["medium", "high"], + list(market.Portfolio.liquidities.get().keys())) + + liquidities = market.Portfolio.liquidities.get() + self.assertEqual(10, len(liquidities["medium"].keys())) + self.assertEqual(10, len(liquidities["high"].keys())) + + expected = { + 'BTC': (D("0.2857"), "long"), + 'DGB': (D("0.1015"), "long"), + 'DOGE': (D("0.1805"), "long"), + 'SC': (D("0.0623"), "long"), + 'ZEC': (D("0.3701"), "long"), + } + date = portfolio.datetime(2018, 1, 8) + self.assertDictEqual(expected, liquidities["high"][date]) + + expected = { + 'BTC': (D("1.1102e-16"), "long"), + 'ETC': (D("0.1"), "long"), + 'FCT': (D("0.1"), "long"), + 'GAS': (D("0.1"), "long"), + 'NAV': (D("0.1"), "long"), + 'OMG': (D("0.1"), "long"), + 'OMNI': (D("0.1"), "long"), + 'PPC': (D("0.1"), "long"), + 'RIC': (D("0.1"), "long"), + 'VIA': (D("0.1"), "long"), + 'XCP': (D("0.1"), "long"), + } + self.assertDictEqual(expected, liquidities["medium"][date]) + self.assertEqual(portfolio.datetime(2018, 1, 15), market.Portfolio.last_date.get()) + + with self.subTest(description="Missing weight"): + data = store.json.loads(self.json_response, parse_int=D, parse_float=D) + del(data["portfolio_2"]["weights"]) + market.Portfolio.data = store.LockedVar(data) + + market.Portfolio.parse_cryptoportfolio() + self.assertListEqual( + ["medium", "high"], + list(market.Portfolio.liquidities.get().keys())) + self.assertEqual({}, market.Portfolio.liquidities.get("medium")) + + with self.subTest(description="All missing weights"): + data = store.json.loads(self.json_response, parse_int=D, parse_float=D) + del(data["portfolio_1"]["weights"]) + del(data["portfolio_2"]["weights"]) + market.Portfolio.data = store.LockedVar(data) + + market.Portfolio.parse_cryptoportfolio() + self.assertEqual({}, market.Portfolio.liquidities.get("medium")) + self.assertEqual({}, market.Portfolio.liquidities.get("high")) + self.assertEqual(datetime.datetime(1,1,1), market.Portfolio.last_date.get()) + @mock.patch.object(market.Portfolio, "get_cryptoportfolio") def test_repartition(self, get_cryptoportfolio): - market.Portfolio.liquidities = { + market.Portfolio.liquidities = store.LockedVar({ "medium": { "2018-03-01": "medium_2018-03-01", "2018-03-08": "medium_2018-03-08", @@ -541,8 +684,8 @@ class PortfolioTest(WebMockTestCase): "2018-03-01": "high_2018-03-01", "2018-03-08": "high_2018-03-08", } - } - market.Portfolio.last_date = "2018-03-08" + }) + market.Portfolio.last_date = store.LockedVar("2018-03-08") self.assertEqual("medium_2018-03-08", market.Portfolio.repartition()) get_cryptoportfolio.assert_called_once_with() @@ -559,9 +702,9 @@ class PortfolioTest(WebMockTestCase): else: self.assertFalse(refetch) self.call_count += 1 - market.Portfolio.last_date = store.datetime.now()\ + market.Portfolio.last_date = store.LockedVar(store.datetime.now()\ - store.timedelta(10)\ - + store.timedelta(self.call_count) + + store.timedelta(self.call_count)) get_cryptoportfolio.side_effect = _get market.Portfolio.wait_for_recent() @@ -572,7 +715,7 @@ class PortfolioTest(WebMockTestCase): sleep.reset_mock() get_cryptoportfolio.reset_mock() - market.Portfolio.last_date = None + market.Portfolio.last_date = store.LockedVar(None) self.call_count = 0 market.Portfolio.wait_for_recent(delta=15) sleep.assert_not_called() @@ -580,13 +723,45 @@ class PortfolioTest(WebMockTestCase): sleep.reset_mock() get_cryptoportfolio.reset_mock() - market.Portfolio.last_date = None + market.Portfolio.last_date = store.LockedVar(None) self.call_count = 0 market.Portfolio.wait_for_recent(delta=1) sleep.assert_called_with(30) self.assertEqual(9, sleep.call_count) self.assertEqual(10, get_cryptoportfolio.call_count) + def test_is_worker_thread(self): + with self.subTest(worker=None): + self.assertFalse(store.Portfolio.is_worker_thread()) + + with self.subTest(worker="not self"),\ + mock.patch("threading.current_thread") as current_thread: + current = mock.Mock() + current_thread.return_value = current + store.Portfolio.worker = mock.Mock() + self.assertFalse(store.Portfolio.is_worker_thread()) + + with self.subTest(worker="self"),\ + mock.patch("threading.current_thread") as current_thread: + current = mock.Mock() + current_thread.return_value = current + store.Portfolio.worker = current + self.assertTrue(store.Portfolio.is_worker_thread()) + + def test_start_worker(self): + with mock.patch.object(store.Portfolio, "wait_for_notification") as notification: + store.Portfolio.start_worker() + notification.assert_called_once_with(poll=30) + + self.assertEqual("lock", store.Portfolio.last_date.lock.__class__.__name__) + self.assertEqual("lock", store.Portfolio.liquidities.lock.__class__.__name__) + store.Portfolio.report.start_lock.assert_called_once_with() + + self.assertIsNotNone(store.Portfolio.worker) + self.assertIsNotNone(store.Portfolio.worker_notify) + self.assertIsNotNone(store.Portfolio.callback) + self.assertTrue(store.Portfolio.worker_started) + @unittest.skipUnless("unit" in limits, "Unit skipped") class AmountTest(WebMockTestCase): def test_values(self): @@ -1203,11 +1378,15 @@ class MarketTest(WebMockTestCase): def test_store_report(self): file_open = mock.mock_open() - with self.subTest(file=None), mock.patch("market.open", file_open): - m = market.Market(self.ccxt, user_id=1) + m = market.Market(self.ccxt, user_id=1) + with self.subTest(file=None),\ + mock.patch.object(m, "report") as report,\ + mock.patch("market.open", file_open): m.store_report() + report.merge.assert_called_with(store.Portfolio.report) file_open.assert_not_called() + report.reset_mock() file_open = mock.mock_open() m = market.Market(self.ccxt, report_path="present", user_id=1) with self.subTest(file="present"),\ @@ -1223,15 +1402,20 @@ class MarketTest(WebMockTestCase): file_open.assert_any_call("present/2018-02-25T00:00:00_1.json", "w") file_open().write.assert_called_once_with("json_content") m.report.to_json.assert_called_once_with() + report.merge.assert_called_with(store.Portfolio.report) + + report.reset_mock() m = market.Market(self.ccxt, report_path="error", user_id=1) with self.subTest(file="error"),\ mock.patch("market.open") as file_open,\ + mock.patch.object(m, "report") as report,\ mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: file_open.side_effect = FileNotFoundError m.store_report() + report.merge.assert_called_with(store.Portfolio.report) self.assertRegex(stdout_mock.getvalue(), "impossible to store report file: FileNotFoundError;") def test_print_orders(self): @@ -1823,16 +2007,20 @@ class TradeTest(WebMockTestCase): value_to = portfolio.Amount("BTC", "1.0") trade = portfolio.Trade(value_from, value_to, "ETH", self.m) - self.assertEqual("buy", trade.order_action(False)) - self.assertEqual("sell", trade.order_action(True)) + trade.inverted = False + self.assertEqual("buy", trade.order_action()) + trade.inverted = True + self.assertEqual("sell", trade.order_action()) value_from = portfolio.Amount("BTC", "0") value_from.linked_to = portfolio.Amount("ETH", "0") value_to = portfolio.Amount("BTC", "-1.0") trade = portfolio.Trade(value_from, value_to, "ETH", self.m) - self.assertEqual("sell", trade.order_action(False)) - self.assertEqual("buy", trade.order_action(True)) + trade.inverted = False + self.assertEqual("sell", trade.order_action()) + trade.inverted = True + self.assertEqual("buy", trade.order_action()) def test_trade_type(self): value_from = portfolio.Amount("BTC", "0.5") @@ -1850,26 +2038,59 @@ class TradeTest(WebMockTestCase): self.assertEqual("short", trade.trade_type) def test_is_fullfiled(self): - value_from = portfolio.Amount("BTC", "0.5") - value_from.linked_to = portfolio.Amount("ETH", "10.0") - value_to = portfolio.Amount("BTC", "1.0") - trade = portfolio.Trade(value_from, value_to, "ETH", self.m) + with self.subTest(inverted=False): + value_from = portfolio.Amount("BTC", "0.5") + value_from.linked_to = portfolio.Amount("ETH", "10.0") + value_to = portfolio.Amount("BTC", "1.0") + trade = portfolio.Trade(value_from, value_to, "ETH", self.m) - order1 = mock.Mock() - order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") + order1 = mock.Mock() + order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") - order2 = mock.Mock() - order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") - trade.orders.append(order1) - trade.orders.append(order2) + order2 = mock.Mock() + order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") + trade.orders.append(order1) + trade.orders.append(order2) + + self.assertFalse(trade.is_fullfiled) + + order3 = mock.Mock() + order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") + trade.orders.append(order3) + + self.assertTrue(trade.is_fullfiled) - self.assertFalse(trade.is_fullfiled) + order1.filled_amount.assert_called_with(in_base_currency=True) + order2.filled_amount.assert_called_with(in_base_currency=True) + order3.filled_amount.assert_called_with(in_base_currency=True) - order3 = mock.Mock() - order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") - trade.orders.append(order3) + with self.subTest(inverted=True): + value_from = portfolio.Amount("BTC", "0.5") + value_from.linked_to = portfolio.Amount("USDT", "1000.0") + value_to = portfolio.Amount("BTC", "1.0") + trade = portfolio.Trade(value_from, value_to, "USDT", self.m) + trade.inverted = True + + order1 = mock.Mock() + order1.filled_amount.return_value = portfolio.Amount("BTC", "0.3") + + order2 = mock.Mock() + order2.filled_amount.return_value = portfolio.Amount("BTC", "0.01") + trade.orders.append(order1) + trade.orders.append(order2) + + self.assertFalse(trade.is_fullfiled) + + order3 = mock.Mock() + order3.filled_amount.return_value = portfolio.Amount("BTC", "0.19") + trade.orders.append(order3) + + self.assertTrue(trade.is_fullfiled) + + order1.filled_amount.assert_called_with(in_base_currency=False) + order2.filled_amount.assert_called_with(in_base_currency=False) + order3.filled_amount.assert_called_with(in_base_currency=False) - self.assertTrue(trade.is_fullfiled) def test_filled_amount(self): value_from = portfolio.Amount("BTC", "0.5") @@ -2529,7 +2750,8 @@ class OrderTest(WebMockTestCase): self.m.report.log_debug_action.assert_called_once() @mock.patch.object(portfolio.Order, "fetch_mouvements") - def test_fetch(self, fetch_mouvements): + @mock.patch.object(portfolio.Order, "mark_finished_order") + def test_fetch(self, mark_finished_order, fetch_mouvements): order = portfolio.Order("buy", portfolio.Amount("ETH", 10), D("0.1"), "BTC", "long", self.m, "trade") order.id = 45 @@ -2539,6 +2761,7 @@ class OrderTest(WebMockTestCase): self.m.report.log_debug_action.assert_called_once() self.m.report.log_debug_action.reset_mock() self.m.ccxt.fetch_order.assert_not_called() + mark_finished_order.assert_not_called() fetch_mouvements.assert_not_called() with self.subTest(debug=False): @@ -2555,17 +2778,19 @@ class OrderTest(WebMockTestCase): self.assertEqual("timestamp", order.timestamp) self.assertEqual(1, len(order.results)) self.m.report.log_debug_action.assert_not_called() + mark_finished_order.assert_called_once() + mark_finished_order.reset_mock() with self.subTest(missing_order=True): self.m.ccxt.fetch_order.side_effect = [ portfolio.OrderNotCached, ] order.fetch() self.assertEqual("closed_unknown", order.status) + mark_finished_order.assert_called_once() @mock.patch.object(portfolio.Order, "fetch") - @mock.patch.object(portfolio.Order, "mark_finished_order") - def test_get_status(self, mark_finished_order, fetch): + def test_get_status(self, fetch): with self.subTest(debug=True): self.m.debug = True order = portfolio.Order("buy", portfolio.Amount("ETH", 10), @@ -2584,10 +2809,8 @@ class OrderTest(WebMockTestCase): return update_status fetch.side_effect = _fetch(order) self.assertEqual("open", order.get_status()) - mark_finished_order.assert_not_called() fetch.assert_called_once() - mark_finished_order.reset_mock() fetch.reset_mock() with self.subTest(debug=False, finished=True): self.m.debug = False @@ -2599,7 +2822,6 @@ class OrderTest(WebMockTestCase): return update_status fetch.side_effect = _fetch(order) self.assertEqual("closed", order.get_status()) - mark_finished_order.assert_called_once() fetch.assert_called_once() def test_run(self): @@ -2782,6 +3004,19 @@ class ReportStoreTest(WebMockTestCase): report_store.set_verbose(False) self.assertFalse(report_store.verbose_print) + def test_merge(self): + report_store1 = market.ReportStore(self.m, verbose_print=False) + report_store2 = market.ReportStore(None, verbose_print=False) + + report_store2.log_stage("1") + report_store1.log_stage("2") + report_store2.log_stage("3") + + report_store1.merge(report_store2) + + self.assertEqual(3, len(report_store1.logs)) + self.assertEqual(["1", "2", "3"], list(map(lambda x: x["stage"], report_store1.logs))) + def test_print_log(self): report_store = market.ReportStore(self.m) with self.subTest(verbose=True),\ @@ -3340,31 +3575,64 @@ class MainTest(WebMockTestCase): self.assertEqual("Exception: boo\n", stdout_mock.getvalue()) def test_main(self): - with mock.patch("main.parse_args") as parse_args,\ - mock.patch("main.parse_config") as parse_config,\ - mock.patch("main.fetch_markets") as fetch_markets,\ - mock.patch("main.process") as process: + with self.subTest(parallel=False): + with mock.patch("main.parse_args") as parse_args,\ + mock.patch("main.parse_config") as parse_config,\ + mock.patch("main.fetch_markets") as fetch_markets,\ + mock.patch("main.process") as process: - args_mock = mock.Mock() - args_mock.config = "config" - args_mock.user = "user" - parse_args.return_value = args_mock + args_mock = mock.Mock() + args_mock.parallel = False + args_mock.config = "config" + args_mock.user = "user" + parse_args.return_value = args_mock - parse_config.return_value = ["pg_config", "report_path"] + parse_config.return_value = ["pg_config", "report_path"] - fetch_markets.return_value = [["config1", 1], ["config2", 2]] + fetch_markets.return_value = [["config1", 1], ["config2", 2]] - main.main(["Foo", "Bar"]) + main.main(["Foo", "Bar"]) - parse_args.assert_called_with(["Foo", "Bar"]) - parse_config.assert_called_with("config") - fetch_markets.assert_called_with("pg_config", "user") + parse_args.assert_called_with(["Foo", "Bar"]) + parse_config.assert_called_with("config") + fetch_markets.assert_called_with("pg_config", "user") - self.assertEqual(2, process.call_count) - process.assert_has_calls([ - mock.call("config1", 1, "report_path", args_mock), - mock.call("config2", 2, "report_path", args_mock), - ]) + self.assertEqual(2, process.call_count) + process.assert_has_calls([ + mock.call("config1", 1, "report_path", args_mock), + mock.call("config2", 2, "report_path", args_mock), + ]) + with self.subTest(parallel=True): + with mock.patch("main.parse_args") as parse_args,\ + mock.patch("main.parse_config") as parse_config,\ + mock.patch("main.fetch_markets") as fetch_markets,\ + mock.patch("main.process") as process,\ + mock.patch("store.Portfolio.start_worker") as start: + + args_mock = mock.Mock() + args_mock.parallel = True + args_mock.config = "config" + args_mock.user = "user" + parse_args.return_value = args_mock + + parse_config.return_value = ["pg_config", "report_path"] + + fetch_markets.return_value = [["config1", 1], ["config2", 2]] + + main.main(["Foo", "Bar"]) + + parse_args.assert_called_with(["Foo", "Bar"]) + parse_config.assert_called_with("config") + fetch_markets.assert_called_with("pg_config", "user") + + start.assert_called_once_with() + self.assertEqual(2, process.call_count) + process.assert_has_calls([ + mock.call.__bool__(), + mock.call("config1", 1, "report_path", args_mock), + mock.call.__bool__(), + mock.call("config2", 2, "report_path", args_mock), + ]) @mock.patch.object(main.sys, "exit") @mock.patch("main.configparser") @@ -3529,7 +3797,7 @@ class ProcessorTest(WebMockTestCase): method, arguments = processor.method_arguments("wait_for_recent") self.assertEqual(market.Portfolio.wait_for_recent, method) - self.assertEqual(["delta"], arguments) + self.assertEqual(["delta", "poll"], arguments) method, arguments = processor.method_arguments("prepare_trades") self.assertEqual(m.prepare_trades, method)