Files
ccxt_with_mt5/ccxt/test/exchange/base/test_shared_methods.py
lz_db 0fab423a18 add
2025-11-16 12:31:03 +08:00

552 lines
29 KiB
Python

import os
import sys
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
sys.path.append(root)
# ----------------------------------------------------------------------------
# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
# ----------------------------------------------------------------------------
# -*- coding: utf-8 -*-
from ccxt.base.decimal_to_precision import DECIMAL_PLACES # noqa E402
from ccxt.base.decimal_to_precision import TICK_SIZE # noqa E402
import numbers # noqa E402
import json # noqa E402
from ccxt.base.precise import Precise # noqa E402
from ccxt.base.errors import OnMaintenance # noqa E402
from ccxt.base.errors import OperationFailed # noqa E402
def log_template(exchange, method, entry):
# there are cases when exchange is undefined (eg. base tests)
id = exchange.id if (exchange is not None) else 'undefined'
method_string = method if (method is not None) else 'undefined'
entry_string = exchange.json(entry) if (exchange is not None) else ''
return ' <<< ' + id + ' ' + method_string + ' ::: ' + entry_string + ' >>> '
def is_temporary_failure(e):
return (isinstance(e, OperationFailed)) and (not (isinstance(e, OnMaintenance)))
def string_value(value):
string_val = None
if isinstance(value, str):
string_val = value
elif value is None:
string_val = 'undefined'
else:
string_val = str(value)
return string_val
def assert_type(exchange, skipped_properties, entry, key, format):
if key in skipped_properties:
return None
# because "typeof" string is not transpilable without === 'name', we list them manually at this moment
entry_key_val = exchange.safe_value(entry, key)
format_key_val = exchange.safe_value(format, key)
same_string = (isinstance(entry_key_val, str)) and (isinstance(format_key_val, str))
same_numeric = (isinstance(entry_key_val, numbers.Real)) and (isinstance(format_key_val, numbers.Real))
same_boolean = ((entry_key_val) or (entry_key_val is False)) and ((format_key_val) or (format_key_val is False))
same_array = isinstance(entry_key_val, list) and isinstance(format_key_val, list)
same_object = (isinstance(entry_key_val, dict)) and (isinstance(format_key_val, dict))
result = (entry_key_val is None) or same_string or same_numeric or same_boolean or same_array or same_object
return result
def assert_structure(exchange, skipped_properties, method, entry, format, empty_allowed_for=None, deep=False):
log_text = log_template(exchange, method, entry)
assert entry is not None, 'item is null/undefined' + log_text
# get all expected & predefined keys for this specific item and ensure thos ekeys exist in parsed structure
allow_empty_skips = exchange.safe_list(skipped_properties, 'allowNull', [])
if empty_allowed_for is not None:
empty_allowed_for = concat(empty_allowed_for, allow_empty_skips)
if isinstance(format, list):
assert isinstance(entry, list), 'entry is not an array' + log_text
real_length = len(entry)
expected_length = len(format)
assert real_length == expected_length, 'entry length is not equal to expected length of ' + str(expected_length) + log_text
for i in range(0, len(format)):
empty_allowed_for_this_key = (empty_allowed_for is None) or exchange.in_array(i, empty_allowed_for)
value = entry[i]
if i in skipped_properties:
continue
# check when:
# - it's not inside "allowe empty values" list
# - it's not undefined
if empty_allowed_for_this_key and (value is None):
continue
assert value is not None, str(i) + ' index is expected to have a value' + log_text
# because of other langs, this is needed for arrays
type_assertion = assert_type(exchange, skipped_properties, entry, i, format)
assert type_assertion, str(i) + ' index does not have an expected type ' + log_text
else:
assert isinstance(entry, dict), 'entry is not an object' + log_text
keys = list(format.keys())
for i in range(0, len(keys)):
key = keys[i]
if key in skipped_properties:
continue
assert key in entry, '"' + string_value(key) + '" key is missing from structure' + log_text
if key in skipped_properties:
continue
empty_allowed_for_this_key = (empty_allowed_for is None) or exchange.in_array(key, empty_allowed_for)
value = entry[key]
# check when:
# - it's not inside "allowe empty values" list
# - it's not undefined
if empty_allowed_for_this_key and (value is None):
continue
# if it was in needed keys, then it should have value.
assert value is not None, '"' + string_value(key) + '" key is expected to have a value' + log_text
# add exclusion for info key, as it can be any type
if key != 'info':
type_assertion = assert_type(exchange, skipped_properties, entry, key, format)
assert type_assertion, '"' + string_value(key) + '" key is neither undefined, neither of expected type' + log_text
if deep:
if isinstance(value, dict):
assert_structure(exchange, skipped_properties, method, value, format[key], empty_allowed_for, deep)
def assert_timestamp(exchange, skipped_properties, method, entry, now_to_check=None, key_name_or_index='timestamp', allow_null=True):
log_text = log_template(exchange, method, entry)
skip_value = exchange.safe_value(skipped_properties, key_name_or_index)
if skip_value is not None:
return # skipped
is_date_time_object = isinstance(key_name_or_index, str)
if is_date_time_object:
assert (key_name_or_index in entry), 'timestamp key "' + key_name_or_index + '" is missing from structure' + log_text
else:
# if index was provided (mostly from fetchOHLCV) then we check if it exists, as mandatory
assert not (entry[key_name_or_index] is None), 'timestamp index ' + string_value(key_name_or_index) + ' is undefined' + log_text
ts = entry[key_name_or_index]
assert ts is not None or allow_null, 'timestamp is null' + log_text
if ts is not None:
assert isinstance(ts, numbers.Real), 'timestamp is not numeric' + log_text
assert isinstance(ts, int), 'timestamp should be an integer' + log_text
min_ts = 1230940800000 # 03 Jan 2009 - first block
max_ts = 2147483648000 # 19 Jan 2038 - max int
assert ts > min_ts, 'timestamp is impossible to be before ' + str(min_ts) + ' (03.01.2009)' + log_text # 03 Jan 2009 - first block
assert ts < max_ts, 'timestamp more than ' + str(max_ts) + ' (19.01.2038)' + log_text # 19 Jan 2038 - int32 overflows # 7258118400000 -> Jan 1 2200
if now_to_check is not None:
max_ms_offset = 60000 # 1 min
assert ts < now_to_check + max_ms_offset, 'returned item timestamp (' + exchange.iso8601(ts) + ') is ahead of the current time (' + exchange.iso8601(now_to_check) + ')' + log_text
def assert_timestamp_and_datetime(exchange, skipped_properties, method, entry, now_to_check=None, key_name_or_index='timestamp', allow_null=True):
log_text = log_template(exchange, method, entry)
skip_value = exchange.safe_value(skipped_properties, key_name_or_index)
if skip_value is not None:
return
assert_timestamp(exchange, skipped_properties, method, entry, now_to_check, key_name_or_index)
is_date_time_object = isinstance(key_name_or_index, str)
# only in case if the entry is a dictionary, thus it must have 'timestamp' & 'datetime' string keys
if is_date_time_object:
# we also test 'datetime' here because it's certain sibling of 'timestamp'
assert ('datetime' in entry), '"datetime" key is missing from structure' + log_text
dt = entry['datetime']
assert dt is not None or allow_null, 'timestamp is null' + log_text
if dt is not None:
assert isinstance(dt, str), '"datetime" key does not have a string value' + log_text
# there are exceptional cases, like getting microsecond-targeted string '2022-08-08T22:03:19.014680Z', so parsed unified timestamp, which carries only 13 digits (millisecond precision) can not be stringified back to microsecond accuracy, causing the bellow assertion to fail
# assert (dt === exchange.iso8601 (entry['timestamp']))
# so, we have to compare with millisecond accururacy
dt_parsed = exchange.parse8601(dt)
ts_ms = entry['timestamp']
diff = abs(dt_parsed - ts_ms)
if diff >= 500:
dt_parsed_string = exchange.iso8601(dt_parsed)
dt_entry_string = exchange.iso8601(ts_ms)
assert False, 'datetime is not iso8601 of timestamp:' + dt_parsed_string + '(string) != ' + dt_entry_string + '(from ts)' + log_text
def assert_currency_code(exchange, skipped_properties, method, entry, actual_code, expected_code=None, allow_null=True):
if ('currency' in skipped_properties) or ('currencyIdAndCode' in skipped_properties):
return
log_text = log_template(exchange, method, entry)
assert actual_code is not None or allow_null, 'currency code is null' + log_text
if actual_code is not None:
assert isinstance(actual_code, str), 'currency code should be either undefined or a string' + log_text
assert (actual_code in exchange.currencies), 'currency code ("' + actual_code + '") should be present in exchange.currencies' + log_text
if expected_code is not None:
assert actual_code == expected_code, 'currency code in response ("' + string_value(actual_code) + '") should be equal to expected code ("' + string_value(expected_code) + '")' + log_text
def assert_valid_currency_id_and_code(exchange, skipped_properties, method, entry, currency_id, currency_code, allow_null=True):
# this is exclusive exceptional key name to be used in `skip-tests.json`, to skip check for currency id and code
if ('currency' in skipped_properties) or ('currencyIdAndCode' in skipped_properties):
return
log_text = log_template(exchange, method, entry)
undefined_values = currency_id is None and currency_code is None
defined_values = currency_id is not None and currency_code is not None
assert undefined_values or defined_values, 'currencyId and currencyCode should be either both defined or both undefined' + log_text
assert defined_values or allow_null, 'currency code and id is not defined' + log_text
if defined_values:
# check by code
currency_by_code = exchange.currency(currency_code)
assert currency_by_code['id'] == currency_id, 'currencyId "' + string_value(currency_id) + '" does not match currency id from instance: "' + string_value(currency_by_code['id']) + '"' + log_text
# check by id
currency_by_id = exchange.safe_currency(currency_id)
assert currency_by_id['code'] == currency_code, 'currencyCode ' + string_value(currency_code) + ' does not match currency of id: ' + string_value(currency_id) + log_text
def assert_symbol(exchange, skipped_properties, method, entry, key, expected_symbol=None, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
actual_symbol = exchange.safe_string(entry, key)
if actual_symbol is not None:
assert isinstance(actual_symbol, str), 'symbol should be either undefined or a string' + log_text
if expected_symbol is not None:
assert actual_symbol == expected_symbol, 'symbol in response ("' + string_value(actual_symbol) + '") should be equal to expected symbol ("' + string_value(expected_symbol) + '")' + log_text
defined_values = actual_symbol is not None and expected_symbol is not None
assert defined_values or allow_null, 'symbols are not defined' + log_text
def assert_symbol_in_markets(exchange, skipped_properties, method, symbol):
log_text = log_template(exchange, method, {})
assert (symbol in exchange.markets), 'symbol should be present in exchange.symbols' + log_text
def assert_greater(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
value = exchange.safe_string(entry, key)
assert value is not None or allow_null, 'value is null' + log_text
if value is not None:
assert Precise.string_gt(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be > ' + string_value(compare_to) + log_text
def assert_greater_or_equal(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
value = exchange.safe_string(entry, key)
assert value is not None or allow_null, 'value is null' + log_text
if value is not None and compare_to is not None:
assert Precise.string_ge(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be >= ' + string_value(compare_to) + log_text
def assert_less(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
value = exchange.safe_string(entry, key)
assert value is not None or allow_null, 'value is null' + log_text
if value is not None and compare_to is not None:
assert Precise.string_lt(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be < ' + string_value(compare_to) + log_text
def assert_less_or_equal(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
value = exchange.safe_string(entry, key)
assert value is not None or allow_null, 'value is null' + log_text
if value is not None and compare_to is not None:
assert Precise.string_le(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be <= ' + string_value(compare_to) + log_text
def assert_equal(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
value = exchange.safe_string(entry, key)
assert value is not None or allow_null, 'value is null' + log_text
if value is not None and compare_to is not None:
assert Precise.string_eq(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected to be equal to ' + string_value(compare_to) + log_text
def assert_non_equal(exchange, skipped_properties, method, entry, key, compare_to, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
value = exchange.safe_string(entry, key)
assert value is not None or allow_null, 'value is null' + log_text
if value is not None:
assert not Precise.string_eq(value, compare_to), string_value(key) + ' key (with a value of ' + string_value(value) + ') was expected not to be equal to ' + string_value(compare_to) + log_text
def assert_in_array(exchange, skipped_properties, method, entry, key, expected_array, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
value = exchange.safe_value(entry, key)
assert value is not None or allow_null, 'value is null' + log_text
# todo: remove undefined check
if value is not None:
stingified_array_value = exchange.json(expected_array) # don't use expectedArray.join (','), as it bugs in other languages, if values are bool, undefined or etc..
assert exchange.in_array(value, expected_array), '"' + string_value(key) + '" key (value "' + string_value(value) + '") is not from the expected list : [' + stingified_array_value + ']' + log_text
def assert_fee_structure(exchange, skipped_properties, method, entry, key, allow_null=True):
log_text = log_template(exchange, method, entry)
key_string = string_value(key)
if isinstance(key, int):
assert isinstance(entry, list), 'fee container is expected to be an array' + log_text
assert key < len(entry), 'fee key ' + key_string + ' was expected to be present in entry' + log_text
else:
assert isinstance(entry, dict), 'fee container is expected to be an object' + log_text
assert key in entry, 'fee key "' + key + '" was expected to be present in entry' + log_text
fee_object = exchange.safe_value(entry, key)
assert fee_object is not None or allow_null, 'fee object is null' + log_text
# todo: remove undefined check to make stricter
if fee_object is not None:
assert 'cost' in fee_object, key_string + ' fee object should contain "cost" key' + log_text
if fee_object['cost'] is None:
return # todo: remove undefined check to make stricter
assert isinstance(fee_object['cost'], numbers.Real), key_string + ' "cost" must be numeric type' + log_text
# assertGreaterOrEqual (exchange, skippedProperties, method, feeObject, 'cost', '0'); # fee might be negative in the case of a rebate or reward
assert 'currency' in fee_object, '"' + key_string + '" fee object should contain "currency" key' + log_text
assert_currency_code(exchange, skipped_properties, method, entry, fee_object['currency'])
def assert_timestamp_order(exchange, method, code_or_symbol, items, ascending=True):
for i in range(0, len(items)):
if i > 0:
current_ts = items[i - 1]['timestamp']
next_ts = items[i]['timestamp']
if current_ts is not None and next_ts is not None:
ascending_or_descending = 'ascending' if ascending else 'descending'
comparison = (current_ts <= next_ts) if ascending else (current_ts >= next_ts)
assert comparison, exchange.id + ' ' + method + ' ' + string_value(code_or_symbol) + ' must return a ' + ascending_or_descending + ' sorted array of items by timestamp, but ' + str(current_ts) + ' is opposite with its next ' + str(next_ts) + ' ' + exchange.json(items)
def assert_integer(exchange, skipped_properties, method, entry, key, allow_null=True):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
if entry is not None:
value = exchange.safe_value(entry, key)
assert value is not None or allow_null, 'value is null' + log_text
if value is not None:
is_integer = isinstance(value, int)
assert is_integer, '"' + string_value(key) + '" key (value "' + string_value(value) + '") is not an integer' + log_text
def check_precision_accuracy(exchange, skipped_properties, method, entry, key):
if key in skipped_properties:
return
if exchange.is_tick_precision():
# TICK_SIZE should be above zero
assert_greater(exchange, skipped_properties, method, entry, key, '0')
# the below array of integers are inexistent tick-sizes (theoretically technically possible, but not in real-world cases), so their existence in our case indicates to incorrectly implemented tick-sizes, which might mistakenly be implemented with DECIMAL_PLACES, so we throw error
decimal_numbers = ['2', '3', '4', '5', '6', '7', '8', '9', '11', '12', '13', '14', '15', '16']
for i in range(0, len(decimal_numbers)):
num = decimal_numbers[i]
num_str = num
assert_non_equal(exchange, skipped_properties, method, entry, key, num_str)
else:
# todo: significant-digits return doubles from `this.parseNumber`, so for now can't assert against integer atm
# assertInteger (exchange, skippedProperties, method, entry, key); # should be integer
assert_less_or_equal(exchange, skipped_properties, method, entry, key, '18') # should be under 18 decimals
assert_greater_or_equal(exchange, skipped_properties, method, entry, key, '-8') # in real-world cases, there would not be less than that
def fetch_best_bid_ask(exchange, method, symbol):
log_text = log_template(exchange, method, {})
# find out best bid/ask price
best_bid = None
best_ask = None
used_method = None
if exchange.has['fetchOrderBook']:
used_method = 'fetchOrderBook'
orderbook = exchange.fetch_order_book(symbol)
bids = exchange.safe_list(orderbook, 'bids')
asks = exchange.safe_list(orderbook, 'asks')
best_bid_array = exchange.safe_list(bids, 0)
best_ask_array = exchange.safe_list(asks, 0)
best_bid = exchange.safe_number(best_bid_array, 0)
best_ask = exchange.safe_number(best_ask_array, 0)
elif exchange.has['fetchBidsAsks']:
used_method = 'fetchBidsAsks'
tickers = exchange.fetch_bids_asks([symbol])
ticker = exchange.safe_dict(tickers, symbol)
best_bid = exchange.safe_number(ticker, 'bid')
best_ask = exchange.safe_number(ticker, 'ask')
elif exchange.has['fetchTicker']:
used_method = 'fetchTicker'
ticker = exchange.fetch_ticker(symbol)
best_bid = exchange.safe_number(ticker, 'bid')
best_ask = exchange.safe_number(ticker, 'ask')
elif exchange.has['fetchTickers']:
used_method = 'fetchTickers'
tickers = exchange.fetch_tickers([symbol])
ticker = exchange.safe_dict(tickers, symbol)
best_bid = exchange.safe_number(ticker, 'bid')
best_ask = exchange.safe_number(ticker, 'ask')
#
assert best_bid is not None and best_ask is not None, log_text + ' ' + exchange.id + ' could not get best bid/ask for ' + symbol + ' using ' + used_method + ' while testing ' + method
return [best_bid, best_ask]
def fetch_order(exchange, symbol, order_id, skipped_properties):
fetched_order = None
original_id = order_id
# set 'since' to 5 minute ago for optimal results
since_time = exchange.milliseconds() - 1000 * 60 * 5
# iterate
methods_singular = ['fetchOrder', 'fetchOpenOrder', 'fetchClosedOrder', 'fetchCanceledOrder']
for i in range(0, len(methods_singular)):
singular_fetch_name = methods_singular[i]
if exchange.has[singular_fetch_name]:
current_order = exchange[singular_fetch_name](original_id, symbol)
# if there is an id inside the order, it means the order was fetched successfully
if current_order['id'] == original_id:
fetched_order = current_order
break
#
# search through plural methods
if fetched_order is None:
methods_plural = ['fetchOrders', 'fetchOpenOrders', 'fetchClosedOrders', 'fetchCanceledOrders']
for i in range(0, len(methods_plural)):
plural_fetch_name = methods_plural[i]
if exchange.has[plural_fetch_name]:
orders = exchange[plural_fetch_name](symbol, since_time)
found = False
for j in range(0, len(orders)):
current_order = orders[j]
if current_order['id'] == original_id:
fetched_order = current_order
found = True
break
if found:
break
return fetched_order
def assert_order_state(exchange, skipped_properties, method, order, asserted_status, strict_check):
# note, `strictCheck` is `true` only from "fetchOrder" cases
log_text = log_template(exchange, method, order)
msg = 'order should be ' + asserted_status + ', but it was not asserted' + log_text
filled = exchange.safe_string(order, 'filled')
amount = exchange.safe_string(order, 'amount')
# shorthand variables
status_undefined = (order['status'] is None)
status_open = (order['status'] == 'open')
status_closed = (order['status'] == 'closed')
status_clanceled = (order['status'] == 'canceled')
filled_defined = (filled is not None)
amount_defined = (amount is not None)
condition = None
#
# ### OPEN STATUS
#
# if strict check, then 'status' must be 'open' and filled amount should be less then whole order amount
strict_open = status_open and (filled_defined and amount_defined and filled < amount)
# if non-strict check, then accept & ignore undefined values
nonstrict_open = (status_open or status_undefined) and ((not filled_defined or not amount_defined) or Precise.string_lt(filled, amount))
# check
if asserted_status == 'open':
condition = strict_open if strict_check else nonstrict_open
assert condition, msg
return
#
# ### CLOSED STATUS
#
# if strict check, then 'status' must be 'closed' and filled amount should be equal to the whole order amount
closed_strict = status_closed and (filled_defined and amount_defined and Precise.string_eq(filled, amount))
# if non-strict check, then accept & ignore undefined values
closed_non_strict = (status_closed or status_undefined) and ((not filled_defined or not amount_defined) or Precise.string_eq(filled, amount))
# check
if asserted_status == 'closed':
condition = closed_strict if strict_check else closed_non_strict
assert condition, msg
return
#
# ### CANCELED STATUS
#
# if strict check, then 'status' must be 'canceled' and filled amount should be less then whole order amount
canceled_strict = status_clanceled and (filled_defined and amount_defined and Precise.string_lt(filled, amount))
# if non-strict check, then accept & ignore undefined values
canceled_non_strict = (status_clanceled or status_undefined) and ((not filled_defined or not amount_defined) or Precise.string_lt(filled, amount))
# check
if asserted_status == 'canceled':
condition = canceled_strict if strict_check else canceled_non_strict
assert condition, msg
return
#
# ### CLOSED_or_CANCELED STATUS
#
if asserted_status == 'closed_or_canceled':
condition = (closed_strict or canceled_strict) if strict_check else (closed_non_strict or canceled_non_strict)
assert condition, msg
return
def get_active_markets(exchange, include_unknown=True):
filtered_active = exchange.filter_by(exchange.markets, 'active', True)
if include_unknown:
filtered_undefined = exchange.filter_by(exchange.markets, 'active', None)
return exchange.array_concat(filtered_active, filtered_undefined)
return filtered_active
def remove_proxy_options(exchange, skipped_properties):
proxy_url = exchange.check_proxy_url_settings()
[http_proxy, https_proxy, socks_proxy] = exchange.check_proxy_settings()
# because of bug in transpiled, about `.proxyUrl` being transpiled into `.proxy_url`, we have to use this workaround
exchange.set_property(exchange, 'proxyUrl', None)
exchange.set_property(exchange, 'proxy_url', None)
exchange.set_property(exchange, 'httpProxy', None)
exchange.set_property(exchange, 'http_proxy', None)
exchange.set_property(exchange, 'httpsProxy', None)
exchange.set_property(exchange, 'https_proxy', None)
exchange.set_property(exchange, 'socksProxy', None)
exchange.set_property(exchange, 'socks_proxy', None)
return [proxy_url, http_proxy, https_proxy, socks_proxy]
def set_proxy_options(exchange, skipped_properties, proxy_url, http_proxy, https_proxy, socks_proxy):
exchange.proxy_url = proxy_url
exchange.http_proxy = http_proxy
exchange.https_proxy = https_proxy
exchange.socks_proxy = socks_proxy
def concat(a=None, b=None):
# we use this method temporarily, because of ast-transpiler issue across langs
if a is None:
return b
elif b is None:
return a
else:
result = []
for i in range(0, len(a)):
result.append(a[i])
for j in range(0, len(b)):
result.append(b[j])
return result
def assert_non_emtpy_array(exchange, skipped_properties, method, entry, hint=None):
log_text = log_template(exchange, method, entry)
if hint is not None:
log_text = log_text + ' ' + hint
assert isinstance(entry, list), 'response is expected to be an array' + log_text
if not ('emptyResponse' in skipped_properties):
return
assert len(entry) > 0, 'response is expected to be a non-empty array' + log_text + ' (add "emptyResponse" in skip-tests.json to skip this check)'
def assert_round_minute_timestamp(exchange, skipped_properties, method, entry, key):
if key in skipped_properties:
return
log_text = log_template(exchange, method, entry)
ts = exchange.safe_string(entry, key)
assert Precise.string_mod(ts, '60000') == '0', 'timestamp should be a multiple of 60 seconds (1 minute)' + log_text
def deep_equal(a, b):
return json.dumps(a) == json.dumps(b)
def assert_deep_equal(exchange, skipped_properties, method, a, b):
log_text = log_template(exchange, method, {})
assert deep_equal(a, b), 'two dicts do not match: ' + json.dumps(a) + ' != ' + json.dumps(b) + log_text