Tests: Various race fixes and move TestFixtureToDataHandler (#1534)

* Tests: Move and simplify TestFixtureToDataHandler

* Currency: Fix PairsManager.Load breaking matcher

* Tests: Add multi-instance cache to UpdatePairsOnce

* Kraken: Fix TestUpdateTickers race error

Calling StorePairs on global instance can lead to race

* Bitfinex: Fix TestUpdateTickers racing intermittently

* Currency: Fix concurrent access to PM formats

* Currency: Fix SupportsAsset implementation

This should delegate entirely to PairManager's IsAssetSupported

* Okx: Fix PM intrusion, rm GetPairFromInstrumentID

* Exchange: Fix SetGlobalPairsManager to set asset enabled

* Bitflyer: Fix race on set TestGetCurrURL

TestGetCurrencyTradeURL would fail sometimes due to sequencing of
enabling futures but not having pairs for it.

* Tests: Simplify usage pattern for FixtureToDH
This commit is contained in:
Gareth Kirwan
2024-05-16 06:09:26 +02:00
committed by GitHub
parent 34ef09dad6
commit 7d1eecfa7e
19 changed files with 584 additions and 693 deletions

View File

@@ -3065,15 +3065,6 @@ func (ok *Okx) GetUnderlying(pair currency.Pair, a asset.Item) (string, error) {
return pair.Base.String() + format.Delimiter + pair.Quote.String(), nil
}
// GetPairFromInstrumentID returns a currency pair give an instrument ID and asset Item, which represents the instrument type.
func (ok *Okx) GetPairFromInstrumentID(instrumentID string) (currency.Pair, error) {
codes := strings.Split(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
if len(codes) >= 2 {
instrumentID = codes[0] + ok.CurrencyPairs.RequestFormat.Delimiter + strings.Join(codes[1:], ok.CurrencyPairs.RequestFormat.Delimiter)
}
return currency.NewPairFromString(instrumentID)
}
// GetOrderBookDepth returns the recent order asks and bids before specified timestamp.
func (ok *Okx) GetOrderBookDepth(ctx context.Context, instrumentID string, depth int64) (*OrderBookResponse, error) {
params := url.Values{}
@@ -4320,11 +4311,15 @@ func (ok *Okx) GetAssetsFromInstrumentTypeOrID(instType, instrumentID string) ([
if instrumentID == "" {
return nil, fmt.Errorf("%w instrumentID", errEmptyArgument)
}
splitSymbol := strings.Split(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
pf, err := ok.CurrencyPairs.GetFormat(asset.Spot, true)
if err != nil {
return nil, err
}
splitSymbol := strings.Split(instrumentID, pf.Delimiter)
if len(splitSymbol) <= 1 {
return nil, fmt.Errorf("%w %v", currency.ErrCurrencyNotSupported, instrumentID)
}
pair, err := currency.NewPairDelimiter(instrumentID, ok.CurrencyPairs.RequestFormat.Delimiter)
pair, err := currency.NewPairDelimiter(instrumentID, pf.Delimiter)
if err != nil {
return nil, err
}

View File

@@ -2206,24 +2206,6 @@ func TestWithdraw(t *testing.T) {
}
}
func TestGetPairFromInstrumentID(t *testing.T) {
t.Parallel()
instruments := []string{
"BTC-USDT",
"BTC-USDT-SWAP",
"BTC-USDT-ER33234",
}
if _, err := ok.GetPairFromInstrumentID(instruments[0]); err != nil {
t.Error("Okx GetPairFromInstrumentID() error", err)
}
if _, ere := ok.GetPairFromInstrumentID(instruments[1]); ere != nil {
t.Error("Okx GetPairFromInstrumentID() error", ere)
}
if _, erf := ok.GetPairFromInstrumentID(instruments[2]); erf != nil {
t.Error("Okx GetPairFromInstrumentID() error", erf)
}
}
func TestGetActiveOrders(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, ok)
@@ -2534,68 +2516,63 @@ func TestBalanceAndPosition(t *testing.T) {
func TestOrderPushData(t *testing.T) {
t.Parallel()
n := new(Okx)
sharedtestvalues.TestFixtureToDataHandler(t, ok, n, "testdata/wsOrders.json", n.WsHandleData)
seen := 0
for reading := true; reading; {
select {
default:
reading = false
case resp := <-n.GetBase().Websocket.DataHandler:
seen++
switch v := resp.(type) {
case *order.Detail:
switch seen {
case 1:
assert.Equal(t, "452197707845865472", v.OrderID, "OrderID")
assert.Equal(t, "HamsterParty14", v.ClientOrderID, "ClientOrderID")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, order.Filled, v.Status, "Status")
assert.Equal(t, order.Limit, v.Type, "Type")
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "Pair")
assert.Equal(t, 31527.1, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, time.UnixMilli(1654084334977), v.Date, "Date")
assert.Equal(t, time.UnixMilli(1654084353263), v.CloseTime, "CloseTime")
assert.Equal(t, 0.001, v.Amount, "Amount")
assert.Equal(t, 0.001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.000, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 31527.1, v.Price, "Price")
assert.Equal(t, 0.02522168, v.Fee, "Fee")
assert.Equal(t, currency.USDT, v.FeeAsset, "FeeAsset")
case 2:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, order.Market, v.Type, "Type")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, order.Active, v.Status, "Status")
assert.Equal(t, 0.0, v.Amount, "Amount should be 0 for a market sell")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
case 3:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
assert.Equal(t, 0.00038127046945832905, v.Amount, "Amount")
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
case 4:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.00038128, v.Amount, "Amount should be derived because order filled")
assert.Equal(t, order.Filled, v.Status, "Status")
}
case error:
t.Error(v)
default:
t.Errorf("Got unexpected data: %T %v", v, v)
ok := new(Okx) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes
require.NoError(t, testexch.Setup(ok), "Test instance Setup must not error")
testexch.FixtureToDataHandler(t, "testdata/wsOrders.json", ok.WsHandleData)
close(ok.Websocket.DataHandler)
assert.Len(t, ok.Websocket.DataHandler, 4, "Should see 4 orders")
for resp := range ok.Websocket.DataHandler {
switch v := resp.(type) {
case *order.Detail:
switch len(ok.Websocket.DataHandler) {
case 3:
assert.Equal(t, "452197707845865472", v.OrderID, "OrderID")
assert.Equal(t, "HamsterParty14", v.ClientOrderID, "ClientOrderID")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, order.Filled, v.Status, "Status")
assert.Equal(t, order.Limit, v.Type, "Type")
assert.Equal(t, currency.NewPairWithDelimiter("BTC", "USDT", "-"), v.Pair, "Pair")
assert.Equal(t, 31527.1, v.AverageExecutedPrice, "AverageExecutedPrice")
assert.Equal(t, time.UnixMilli(1654084334977), v.Date, "Date")
assert.Equal(t, time.UnixMilli(1654084353263), v.CloseTime, "CloseTime")
assert.Equal(t, 0.001, v.Amount, "Amount")
assert.Equal(t, 0.001, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.000, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 31527.1, v.Price, "Price")
assert.Equal(t, 0.02522168, v.Fee, "Fee")
assert.Equal(t, currency.USDT, v.FeeAsset, "FeeAsset")
case 2:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, asset.Spot, v.AssetType, "AssetType")
assert.Equal(t, order.Market, v.Type, "Type")
assert.Equal(t, order.Sell, v.Side, "Side")
assert.Equal(t, order.Active, v.Status, "Status")
assert.Equal(t, 0.0, v.Amount, "Amount should be 0 for a market sell")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
case 1:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
assert.Equal(t, 0.00038127046945832905, v.Amount, "Amount")
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, order.PartiallyFilled, v.Status, "Status")
case 0:
assert.Equal(t, "620258920632008725", v.OrderID, "OrderID")
assert.Equal(t, 10.0, v.QuoteAmount, "QuoteAmount")
assert.Equal(t, 0.010000249968, v.Fee, "Fee")
assert.Equal(t, 0.0, v.RemainingAmount, "RemainingAmount")
assert.Equal(t, 0.00038128, v.ExecutedAmount, "ExecutedAmount")
assert.Equal(t, 0.00038128, v.Amount, "Amount should be derived because order filled")
assert.Equal(t, order.Filled, v.Status, "Status")
}
case error:
t.Error(v)
default:
t.Errorf("Got unexpected data: %T %v", v, v)
}
}
assert.Equal(t, 4, seen, "Saw 4 records")
}
const algoOrdersPushDataJSON = `{"arg": {"channel": "orders-algo","uid": "77982378738415879","instType": "FUTURES","instId": "BTC-USD-200329"},"data": [{"instType": "FUTURES","instId": "BTC-USD-200329","ordId": "312269865356374016","ccy": "BTC","algoId": "1234","px": "999","sz": "3","tdMode": "cross","tgtCcy": "","notionalUsd": "","ordType": "trigger","side": "buy","posSide": "long","state": "live","lever": "20","tpTriggerPx": "","tpTriggerPxType": "","tpOrdPx": "","slTriggerPx": "","slTriggerPxType": "","triggerPx": "99","triggerPxType": "last","ordPx": "12","actualSz": "","actualPx": "","tag": "adadadadad","actualSide": "","triggerTime": "1597026383085","cTime": "1597026383000"}]}`

View File

@@ -688,7 +688,8 @@ func (ok *Okx) wsProcessIndexCandles(respRaw []byte) error {
if len(response.Data) == 0 {
return errNoCandlestickDataFound
}
pair, err := ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
if err != nil {
return err
}
@@ -750,7 +751,7 @@ func (ok *Okx) wsProcessOrderbook5(data []byte) error {
return err
}
pair, err := ok.GetPairFromInstrumentID(resp.Argument.InstrumentID)
pair, err := currency.NewPairFromString(resp.Argument.InstrumentID)
if err != nil {
return err
}
@@ -809,13 +810,11 @@ func (ok *Okx) wsProcessOrderBooks(data []byte) error {
response.Action != wsOrderbookSnapshot {
return errors.New("invalid order book action")
}
var pair currency.Pair
var assets []asset.Item
assets, err = ok.GetAssetsFromInstrumentTypeOrID(response.Argument.InstrumentType, response.Argument.InstrumentID)
assets, err := ok.GetAssetsFromInstrumentTypeOrID(response.Argument.InstrumentType, response.Argument.InstrumentID)
if err != nil {
return err
}
pair, err = ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
if err != nil {
return err
}
@@ -1062,8 +1061,7 @@ func (ok *Okx) wsProcessTrades(data []byte) error {
}
trades := make([]trade.Data, 0, len(response.Data)*len(assets))
for i := range response.Data {
var pair currency.Pair
pair, err = ok.GetPairFromInstrumentID(response.Data[i].InstrumentID)
pair, err := currency.NewPairFromString(response.Data[i].InstrumentID)
if err != nil {
return err
}
@@ -1086,7 +1084,6 @@ func (ok *Okx) wsProcessTrades(data []byte) error {
// wsProcessOrders handles websocket order push data responses.
func (ok *Okx) wsProcessOrders(respRaw []byte) error {
var response WsOrderResponse
var pair currency.Pair
err := json.Unmarshal(respRaw, &response)
if err != nil {
return err
@@ -1109,7 +1106,7 @@ func (ok *Okx) wsProcessOrders(respRaw []byte) error {
Err: err,
}
}
pair, err = ok.GetPairFromInstrumentID(response.Data[x].InstrumentID)
pair, err := currency.NewPairFromString(response.Data[x].InstrumentID)
if err != nil {
return err
}
@@ -1189,7 +1186,7 @@ func (ok *Okx) wsProcessCandles(respRaw []byte) error {
if len(response.Data) == 0 {
return errNoCandlestickDataFound
}
pair, err := ok.GetPairFromInstrumentID(response.Argument.InstrumentID)
pair, err := currency.NewPairFromString(response.Argument.InstrumentID)
if err != nil {
return err
}
@@ -1260,8 +1257,7 @@ func (ok *Okx) wsProcessTickers(data []byte) error {
if err != nil {
return err
}
var c currency.Pair
c, err = ok.GetPairFromInstrumentID(response.Data[i].InstrumentID)
c, err := currency.NewPairFromString(response.Data[i].InstrumentID)
if err != nil {
return err
}

View File

@@ -259,9 +259,13 @@ func (ok *Okx) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.P
if err != nil {
return nil, err
}
pf, err := ok.CurrencyPairs.GetFormat(a, false)
if err != nil {
return nil, err
}
pairs := make([]currency.Pair, len(insts))
for x := range insts {
pairs[x], err = currency.NewPairDelimiter(insts[x].InstrumentID, ok.CurrencyPairs.ConfigFormat.Delimiter)
pairs[x], err = currency.NewPairDelimiter(insts[x].InstrumentID, pf.Delimiter)
if err != nil {
return nil, err
}
@@ -378,7 +382,7 @@ func (ok *Okx) UpdateTickers(ctx context.Context, assetType asset.Item) error {
}
for y := range ticks {
pair, err := ok.GetPairFromInstrumentID(ticks[y].InstrumentID)
pair, err := currency.NewPairFromString(ticks[y].InstrumentID)
if err != nil {
return err
}
@@ -1192,8 +1196,7 @@ allOrders:
break allOrders
}
orderSide := orderList[i].Side
var pair currency.Pair
pair, err = ok.GetPairFromInstrumentID(orderList[i].InstrumentID)
pair, err := currency.NewPairFromString(orderList[i].InstrumentID)
if err != nil {
return nil, err
}
@@ -1286,8 +1289,7 @@ allOrders:
// reached end of orders to crawl
break allOrders
}
var pair currency.Pair
pair, err = ok.GetPairFromInstrumentID(orderList[i].InstrumentID)
pair, err := currency.NewPairFromString(orderList[i].InstrumentID)
if err != nil {
return nil, err
}