ordermanager: fix test error introduced in #917 (#942)

* ordermanager: fix residual test issue from #917 and reduce some racey action

* glorious: nits; also removed functions that weren't being used and were unexported

* rm: pew

* linter: fix issues

* glourious: nits

* credentials: fix test issue with racey racey horse basey
This commit is contained in:
Ryan O'Hara-Reid
2022-05-11 14:18:21 +10:00
committed by GitHub
parent 61212fb8ea
commit 5cb26e7ecf
5 changed files with 189 additions and 280 deletions

View File

@@ -47,10 +47,7 @@ func SetupOrderManager(exchangeManager iExchangeManager, communicationsManager i
// IsRunning safely checks whether the subsystem is running
func (m *OrderManager) IsRunning() bool {
if m == nil {
return false
}
return atomic.LoadInt32(&m.started) == 1
return m != nil && atomic.LoadInt32(&m.started) == 1
}
// Start runs the subsystem
@@ -63,6 +60,7 @@ func (m *OrderManager) Start() error {
}
log.Debugln(log.OrderMgr, "Order manager starting...")
m.shutdown = make(chan struct{})
m.orderStore.wg.Add(1)
go m.run()
return nil
}
@@ -75,48 +73,44 @@ func (m *OrderManager) Stop() error {
if atomic.LoadInt32(&m.started) == 0 {
return fmt.Errorf("order manager %w", ErrSubSystemNotStarted)
}
defer func() {
atomic.CompareAndSwapInt32(&m.started, 1, 0)
}()
log.Debugln(log.OrderMgr, "Order manager shutting down...")
close(m.shutdown)
atomic.CompareAndSwapInt32(&m.started, 1, 0)
return nil
}
// gracefulShutdown cancels all orders (if enabled) before shutting down
func (m *OrderManager) gracefulShutdown() {
if m.cfg.CancelOrdersOnShutdown {
log.Debugln(log.OrderMgr, "Order manager: Cancelling any open orders...")
exchanges, err := m.orderStore.exchangeManager.GetExchanges()
if err != nil {
log.Errorf(log.OrderMgr, "Order manager cannot get exchanges: %v", err)
return
}
m.CancelAllOrders(context.TODO(), exchanges)
if !m.cfg.CancelOrdersOnShutdown {
return
}
log.Debugln(log.OrderMgr, "Order manager: Cancelling any open orders...")
exchanges, err := m.orderStore.exchangeManager.GetExchanges()
if err != nil {
log.Errorf(log.OrderMgr, "Order manager cannot get exchanges: %v", err)
return
}
m.CancelAllOrders(context.TODO(), exchanges)
}
// run will periodically process orders
func (m *OrderManager) run() {
log.Debugln(log.OrderMgr, "Order manager started.")
m.processOrders()
tick := time.NewTicker(orderManagerDelay)
m.orderStore.wg.Add(1)
defer func() {
log.Debugln(log.OrderMgr, "Order manager shutdown.")
tick.Stop()
m.orderStore.wg.Done()
}()
timer := time.NewTimer(orderManagerDelay)
for {
select {
case <-m.shutdown:
m.gracefulShutdown()
if !timer.Stop() {
<-timer.C
}
m.orderStore.wg.Done()
log.Debugln(log.OrderMgr, "Order manager shutdown.")
return
case <-tick.C:
case <-timer.C:
// Process orders go routine allows shutdown procedures to continue
go m.processOrders()
timer.Reset(orderManagerDelay)
}
}
}
@@ -138,7 +132,9 @@ func (m *OrderManager) CancelAllOrders(ctx context.Context, exchangeNames []exch
continue
}
for j := range exchangeOrders {
log.Debugf(log.OrderMgr, "Order manager: Cancelling order(s) for exchange %s.", exchangeNames[i].GetName())
log.Debugf(log.OrderMgr,
"Order manager: Cancelling order(s) for exchange %s.",
exchangeNames[i].GetName())
err := m.Cancel(ctx, &order.Cancel{
Exchange: exchangeOrders[j].Exchange,
ID: exchangeOrders[j].ID,
@@ -207,22 +203,22 @@ func (m *OrderManager) Cancel(ctx context.Context, cancel *order.Cancel) error {
err = fmt.Errorf("%v - Failed to cancel order: %w", cancel.Exchange, err)
return err
}
var od *order.Detail
od, err = m.orderStore.getByExchangeAndID(cancel.Exchange, cancel.ID)
od, err := m.orderStore.getByExchangeAndID(cancel.Exchange, cancel.ID)
if err != nil {
err = fmt.Errorf("%v - Failed to retrieve order %v to update cancelled status: %w", cancel.Exchange, cancel.ID, err)
return err
}
od.Status = order.Cancelled
err = m.orderStore.updateExisting(od)
if err != nil {
err = fmt.Errorf("%v - Failed to update existing order when cancelled: %w", cancel.Exchange, err)
return err
}
msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.",
od.Exchange, od.ID)
log.Debugln(log.OrderMgr, msg)
m.orderStore.commsManager.PushEvent(base.Event{
Type: "order",
Message: msg,
})
m.orderStore.commsManager.PushEvent(base.Event{Type: "order", Message: msg})
return nil
}
@@ -534,9 +530,6 @@ func (m *OrderManager) GetOrdersFiltered(f *order.Filter) ([]order.Detail, error
if m == nil {
return nil, fmt.Errorf("order manager %w", ErrNilSubsystem)
}
if f == nil {
return nil, fmt.Errorf("order manager, GetOrdersFiltered: Filter is nil")
}
if atomic.LoadInt32(&m.started) == 0 {
return nil, fmt.Errorf("order manager %w", ErrSubSystemNotStarted)
}
@@ -640,9 +633,8 @@ func (m *OrderManager) processOrders() {
if !atomic.CompareAndSwapInt32(&m.processingOrders, 0, 1) {
return
}
defer func() {
atomic.StoreInt32(&m.processingOrders, 0)
}()
defer atomic.StoreInt32(&m.processingOrders, 0)
exchanges, err := m.orderStore.exchangeManager.GetExchanges()
if err != nil {
log.Errorf(log.OrderMgr, "Order manager cannot get exchanges: %v", err)
@@ -657,14 +649,14 @@ func (m *OrderManager) processOrders() {
"Order manager: Processing orders for exchange %v.",
exchanges[i].GetName())
supportedAssets := exchanges[i].GetAssetTypes(true)
for y := range supportedAssets {
pairs, err := exchanges[i].GetEnabledPairs(supportedAssets[y])
enabledAssets := exchanges[i].GetAssetTypes(true)
for y := range enabledAssets {
pairs, err := exchanges[i].GetEnabledPairs(enabledAssets[y])
if err != nil {
log.Errorf(log.OrderMgr,
"Order manager: Unable to get enabled pairs for %s and asset type %s: %s",
exchanges[i].GetName(),
supportedAssets[y],
enabledAssets[y],
err)
continue
}
@@ -674,33 +666,26 @@ func (m *OrderManager) processOrders() {
log.Debugf(log.OrderMgr,
"Order manager: No pairs enabled for %s and asset type %s, skipping...",
exchanges[i].GetName(),
supportedAssets[y])
enabledAssets[y])
}
continue
}
filter := &order.Filter{
Exchange: exchanges[i].GetName(),
}
filter := &order.Filter{Exchange: exchanges[i].GetName()}
orders := m.orderStore.getActiveOrders(filter)
order.FilterOrdersByPairs(&orders, pairs)
requiresProcessing := make(map[string]bool, len(orders))
for x := range orders {
requiresProcessing[orders[x].InternalOrderID] = true
}
req := order.GetOrdersRequest{
result, err := exchanges[i].GetActiveOrders(context.TODO(), &order.GetOrdersRequest{
Side: order.AnySide,
Type: order.AnyType,
Pairs: pairs,
AssetType: supportedAssets[y],
}
result, err := exchanges[i].GetActiveOrders(context.TODO(), &req)
AssetType: enabledAssets[y],
})
if err != nil {
log.Errorf(log.OrderMgr,
"Order manager: Unable to get active orders for %s and asset type %s: %s",
exchanges[i].GetName(),
supportedAssets[y],
enabledAssets[y],
err)
continue
}
@@ -713,36 +698,38 @@ func (m *OrderManager) processOrders() {
if err != nil {
log.Error(log.OrderMgr, err)
} else {
requiresProcessing[upsertResponse.OrderDetails.InternalOrderID] = false
for i := range orders {
if orders[i].InternalOrderID != upsertResponse.OrderDetails.InternalOrderID {
continue
}
orders[i] = orders[len(orders)-1]
orders = orders[:len(orders)-1]
}
}
}
if !exchanges[i].GetBase().GetSupportedFeatures().RESTCapabilities.GetOrder {
continue
}
wg.Add(1)
go m.processMatchingOrders(exchanges[i], orders, requiresProcessing, &wg)
go m.processMatchingOrders(exchanges[i], orders, &wg)
}
}
wg.Wait()
}
func (m *OrderManager) processMatchingOrders(exch exchange.IBotExchange, orders []order.Detail, requiresProcessing map[string]bool, wg *sync.WaitGroup) {
defer func() {
if wg != nil {
wg.Done()
}
}()
func (m *OrderManager) processMatchingOrders(exch exchange.IBotExchange, orders []order.Detail, wg *sync.WaitGroup) {
for x := range orders {
if time.Since(orders[x].LastUpdated) < time.Minute {
continue
}
if requiresProcessing[orders[x].InternalOrderID] {
err := m.FetchAndUpdateExchangeOrder(exch, &orders[x], orders[x].AssetType)
if err != nil {
log.Error(log.OrderMgr, err)
}
err := m.FetchAndUpdateExchangeOrder(exch, &orders[x], orders[x].AssetType)
if err != nil {
log.Error(log.OrderMgr, err)
}
}
if wg != nil {
wg.Done()
}
}
// FetchAndUpdateExchangeOrder calls the exchange to upsert an order to the order store
@@ -762,11 +749,7 @@ func (m *OrderManager) FetchAndUpdateExchangeOrder(exch exchange.IBotExchange, o
// Exists checks whether an order exists in the order store
func (m *OrderManager) Exists(o *order.Detail) bool {
if m == nil || atomic.LoadInt32(&m.started) == 0 {
return false
}
return m.orderStore.exists(o)
return m != nil && atomic.LoadInt32(&m.started) != 0 && m.orderStore.exists(o)
}
// Add adds an order to the orderstore
@@ -857,11 +840,13 @@ func (m *OrderManager) UpsertOrder(od *order.Detail) (resp *OrderUpsertResponse,
return upsertResponse, nil
}
// get returns all orders for all exchanges
// should not be exported as it can have large impact if used improperly
// get returns a copy of all orders for all exchanges.
func (s *store) get() map[string][]*order.Detail {
orders := make(map[string][]*order.Detail)
s.m.Lock()
orders := s.Orders
for k, val := range s.Orders {
orders[k] = order.CopyPointerOrderSlice(val)
}
s.m.Unlock()
return orders
}
@@ -877,7 +862,7 @@ func (s *store) getByExchangeAndID(exchange, id string) (*order.Detail, error) {
for x := range r {
if r[x].ID == id {
return r[x], nil
return r[x].CopyToPointer(), nil
}
}
return nil, ErrOrderNotFound
@@ -896,20 +881,19 @@ func (s *store) updateExisting(od *order.Detail) error {
return ErrExchangeNotFound
}
for x := range r {
if r[x].ID == od.ID {
r[x].UpdateOrderFromDetail(od)
if r[x].AssetType.IsFutures() {
err := s.futuresPositionController.TrackNewOrder(r[x])
if err != nil {
if !errors.Is(err, order.ErrPositionClosed) {
return err
}
}
}
if r[x].ID != od.ID {
continue
}
r[x].UpdateOrderFromDetail(od)
if !r[x].AssetType.IsFutures() {
return nil
}
err := s.futuresPositionController.TrackNewOrder(r[x])
if err != nil && !errors.Is(err, order.ErrPositionClosed) {
return err
}
return nil
}
return ErrOrderNotFound
}
@@ -923,30 +907,30 @@ func (s *store) modifyExisting(id string, mod *order.Modify) error {
return ErrExchangeNotFound
}
for x := range r {
if r[x].ID == id {
r[x].UpdateOrderFromModify(mod)
if r[x].AssetType.IsFutures() {
err := s.futuresPositionController.TrackNewOrder(r[x])
if err != nil {
if !errors.Is(err, order.ErrPositionClosed) {
return err
}
}
}
if r[x].ID != id {
continue
}
r[x].UpdateOrderFromModify(mod)
if !r[x].AssetType.IsFutures() {
return nil
}
err := s.futuresPositionController.TrackNewOrder(r[x])
if err != nil && !errors.Is(err, order.ErrPositionClosed) {
return err
}
return nil
}
return ErrOrderNotFound
}
// upsert (1) checks if such an exchange exists in the exchangeManager, (2) checks if
// order exists and updates/creates it.
func (s *store) upsert(od *order.Detail) (resp *OrderUpsertResponse, err error) {
func (s *store) upsert(od *order.Detail) (*OrderUpsertResponse, error) {
if od == nil {
return nil, errNilOrder
}
lName := strings.ToLower(od.Exchange)
_, err = s.exchangeManager.GetExchangeByName(lName)
_, err := s.exchangeManager.GetExchangeByName(lName)
if err != nil {
return nil, err
}
@@ -954,66 +938,27 @@ func (s *store) upsert(od *order.Detail) (resp *OrderUpsertResponse, err error)
defer s.m.Unlock()
if od.AssetType.IsFutures() {
err = s.futuresPositionController.TrackNewOrder(od)
if err != nil {
if !errors.Is(err, order.ErrPositionClosed) {
return nil, err
}
if err != nil && !errors.Is(err, order.ErrPositionClosed) {
return nil, err
}
}
r, ok := s.Orders[lName]
if !ok {
od.GenerateInternalOrderID()
s.Orders[lName] = []*order.Detail{od}
resp = &OrderUpsertResponse{
OrderDetails: od.Copy(),
IsNewOrder: true,
}
return resp, nil
return &OrderUpsertResponse{OrderDetails: od.Copy(), IsNewOrder: true}, nil
}
for x := range r {
if r[x].ID == od.ID {
r[x].UpdateOrderFromDetail(od)
resp = &OrderUpsertResponse{
OrderDetails: r[x].Copy(),
IsNewOrder: false,
}
return resp, nil
if r[x].ID != od.ID {
continue
}
r[x].UpdateOrderFromDetail(od)
return &OrderUpsertResponse{OrderDetails: r[x].Copy(), IsNewOrder: false}, nil
}
// Untracked websocket orders will not have internalIDs yet
od.GenerateInternalOrderID()
s.Orders[lName] = append(s.Orders[lName], od)
resp = &OrderUpsertResponse{
OrderDetails: od.Copy(),
IsNewOrder: true,
}
return resp, nil
}
// getByExchange returns orders by exchange
func (s *store) getByExchange(exchange string) ([]*order.Detail, error) {
s.m.RLock()
defer s.m.RUnlock()
r, ok := s.Orders[strings.ToLower(exchange)]
if !ok {
return nil, ErrExchangeNotFound
}
return r, nil
}
// getByInternalOrderID will search all orders for our internal orderID
// and return the order
func (s *store) getByInternalOrderID(internalOrderID string) (*order.Detail, error) {
s.m.RLock()
defer s.m.RUnlock()
for _, v := range s.Orders {
for x := range v {
if v[x].InternalOrderID == internalOrderID {
return v[x], nil
}
}
}
return nil, ErrOrderNotFound
return &OrderUpsertResponse{OrderDetails: od.Copy(), IsNewOrder: true}, nil
}
// exists verifies if the orderstore contains the provided order
@@ -1056,13 +1001,10 @@ func (s *store) add(det *order.Detail) error {
orders = append(orders, det)
s.Orders[strings.ToLower(det.Exchange)] = orders
if det.AssetType.IsFutures() {
err = s.futuresPositionController.TrackNewOrder(det)
if err != nil {
return err
}
if !det.AssetType.IsFutures() {
return nil
}
return nil
return s.futuresPositionController.TrackNewOrder(det)
}
// getFilteredOrders returns a filtered copy of the orders
@@ -1107,7 +1049,7 @@ func (s *store) getActiveOrders(f *order.Filter) []order.Detail {
case f == nil:
for _, e := range s.Orders {
for i := range e {
if !e[i].IsActive() {
if e[i].Status != order.UnknownStatus && !e[i].IsActive() {
continue
}
orders = append(orders, e[i].Copy())
@@ -1117,7 +1059,7 @@ func (s *store) getActiveOrders(f *order.Filter) []order.Detail {
// optimization if Exchange is filtered
if e, ok := s.Orders[strings.ToLower(f.Exchange)]; ok {
for i := range e {
if !e[i].IsActive() || !e[i].MatchFilter(f) {
if e[i].Status != order.UnknownStatus && (!e[i].IsActive() || !e[i].MatchFilter(f)) {
continue
}
orders = append(orders, e[i].Copy())
@@ -1126,7 +1068,7 @@ func (s *store) getActiveOrders(f *order.Filter) []order.Detail {
default:
for _, e := range s.Orders {
for i := range e {
if !e[i].IsActive() || !e[i].MatchFilter(f) {
if e[i].Status != order.UnknownStatus && (!e[i].IsActive() || !e[i].MatchFilter(f)) {
continue
}
orders = append(orders, e[i].Copy())

View File

@@ -253,95 +253,6 @@ func TestOrdersAdd(t *testing.T) {
}
}
func TestGetByInternalOrderID(t *testing.T) {
m := OrdersSetup(t)
err := m.orderStore.add(&order.Detail{
Exchange: testExchange,
ID: "TestGetByInternalOrderID",
InternalOrderID: "internalTest",
})
if err != nil {
t.Error(err)
}
o, err := m.orderStore.getByInternalOrderID("internalTest")
if err != nil {
t.Fatal(err)
}
if o == nil { //nolint:staticcheck,nolintlint // SA5011 Ignore the nil warnings
t.Fatal("Expected a matching order")
}
if o.ID != "TestGetByInternalOrderID" { //nolint:staticcheck,nolintlint // SA5011 Ignore the nil warnings
t.Error("Expected to retrieve order")
}
_, err = m.orderStore.getByInternalOrderID("NoOrder")
if err != ErrOrderNotFound {
t.Error(err)
}
}
func TestGetByExchange(t *testing.T) {
m := OrdersSetup(t)
err := m.orderStore.add(&order.Detail{
Exchange: testExchange,
ID: "TestGetByExchange",
InternalOrderID: "internalTestGetByExchange",
})
if err != nil {
t.Error(err)
}
err = m.orderStore.add(&order.Detail{
Exchange: testExchange,
ID: "TestGetByExchange2",
InternalOrderID: "internalTestGetByExchange2",
})
if err != nil {
t.Error(err)
}
err = m.orderStore.add(&order.Detail{
Exchange: testExchange,
ID: "TestGetByExchange3",
InternalOrderID: "internalTest3",
})
if err != nil {
t.Error(err)
}
var o []*order.Detail
o, err = m.orderStore.getByExchange(testExchange)
if err != nil {
t.Error(err)
}
if o == nil {
t.Error("Expected non nil response")
}
var o1Found, o2Found bool
for i := range o {
if o[i].ID == "TestGetByExchange" && o[i].Exchange == testExchange {
o1Found = true
}
if o[i].ID == "TestGetByExchange2" && o[i].Exchange == testExchange {
o2Found = true
}
}
if !o1Found || !o2Found {
t.Error("Expected orders 'TestGetByExchange' and 'TestGetByExchange2' to be returned")
}
_, err = m.orderStore.getByInternalOrderID("NoOrder")
if err != ErrOrderNotFound {
t.Error(err)
}
err = m.orderStore.add(&order.Detail{
Exchange: "thisWillFail",
})
if err == nil {
t.Error("Expected exchange not found error")
}
}
func TestGetByExchangeAndID(t *testing.T) {
m := OrdersSetup(t)
err := m.orderStore.add(&order.Detail{
@@ -557,7 +468,11 @@ func TestCancelAllOrders(t *testing.T) {
}
m.CancelAllOrders(context.Background(), []exchange.IBotExchange{})
if o.Status == order.Cancelled {
checkDeets, err := m.orderStore.getByExchangeAndID(testExchange, "TestCancelAllOrders")
if err != nil {
t.Fatal(err)
}
if checkDeets.Status == order.Cancelled {
t.Error("Order should not be cancelled")
}
@@ -567,14 +482,13 @@ func TestCancelAllOrders(t *testing.T) {
}
m.CancelAllOrders(context.Background(), []exchange.IBotExchange{exch})
if o.Status != order.Cancelled {
t.Error("Order should be cancelled")
checkDeets, err = m.orderStore.getByExchangeAndID(testExchange, "TestCancelAllOrders")
if err != nil {
t.Fatal(err)
}
o.Status = order.New
m.CancelAllOrders(context.Background(), nil)
if o.Status != order.New {
t.Error("Order should not be cancelled")
if checkDeets.Status != order.Cancelled {
t.Error("Order should be cancelled", checkDeets.Status)
}
}
@@ -1024,39 +938,20 @@ func Test_processMatchingOrders(t *testing.T) {
t.Fatal(err)
}
orders := []order.Detail{
{
Exchange: testExchange,
ID: "Test1",
LastUpdated: time.Now(),
},
{
Exchange: testExchange,
ID: "Test2",
LastUpdated: time.Now(),
},
{
Exchange: testExchange,
ID: "Test3",
LastUpdated: time.Now().Add(-time.Hour),
},
{
Exchange: testExchange,
ID: "Test4",
LastUpdated: time.Now().Add(-time.Hour),
},
}
requiresProcessing := make(map[string]bool, len(orders))
for i := range orders {
orders[i].GenerateInternalOrderID()
if i%2 == 0 {
requiresProcessing[orders[i].InternalOrderID] = false
} else {
requiresProcessing[orders[i].InternalOrderID] = true
}
}
var wg sync.WaitGroup
wg.Add(1)
m.processMatchingOrders(exch, orders, requiresProcessing, &wg)
go m.processMatchingOrders(exch, orders, &wg)
wg.Wait()
res, err := m.GetOrdersFiltered(&order.Filter{Exchange: testExchange})
if err != nil {

View File

@@ -212,7 +212,6 @@ func TestAreCredentialsValid(t *testing.T) {
func TestValidateAPICredentials(t *testing.T) {
t.Parallel()
var b Base
type tester struct {
Key string
Secret string
@@ -247,7 +246,8 @@ func TestValidateAPICredentials(t *testing.T) {
{RequiresBase64DecodeSecret: true, Secret: "aGVsbG8gd29ybGQ="},
}
setupBase := func(b *Base, tData *tester) {
setupBase := func(tData *tester) *Base {
b := &Base{}
b.API.SetKey(tData.Key)
b.API.SetSecret(tData.Secret)
b.API.SetClientID(tData.ClientID)
@@ -257,13 +257,14 @@ func TestValidateAPICredentials(t *testing.T) {
b.API.CredentialsValidator.RequiresPEM = tData.RequiresPEM
b.API.CredentialsValidator.RequiresClientID = tData.RequiresClientID
b.API.CredentialsValidator.RequiresBase64DecodeSecret = tData.RequiresBase64DecodeSecret
return b
}
for x := range testCases {
testData := &testCases[x]
t.Run("", func(t *testing.T) {
t.Parallel()
setupBase(&b, testData)
b := setupBase(testData)
if err := b.ValidateAPICredentials(b.API.credentials); !errors.Is(err, testData.Expected) {
t.Errorf("Test %d: expected: %v: got %v", x+1, testData.Expected, err)
}

View File

@@ -1554,6 +1554,7 @@ func TestGenerateInternalOrderID(t *testing.T) {
}
func TestDetail_Copy(t *testing.T) {
t.Parallel()
d := []Detail{
{
Exchange: "Binance",
@@ -1577,3 +1578,56 @@ func TestDetail_Copy(t *testing.T) {
}
}
}
func TestDetail_CopyToPointer(t *testing.T) {
t.Parallel()
d := []Detail{
{
Exchange: "Binance",
},
{
Exchange: "Binance",
Trades: []TradeHistory{
{Price: 1},
},
},
}
for i := range d {
r := d[i].CopyToPointer()
if !reflect.DeepEqual(d[i], *r) {
t.Errorf("[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, d[i], r)
}
if len(d[i].Trades) > 0 {
if &d[i].Trades[0] == &r.Trades[0] {
t.Errorf("[%d]Trades point to the same data elements", i)
}
}
}
}
func TestDetail_CopyPointerOrderSlice(t *testing.T) {
t.Parallel()
d := []*Detail{
{
Exchange: "Binance",
},
{
Exchange: "Binance",
Trades: []TradeHistory{
{Price: 1},
},
},
}
sliceCopy := CopyPointerOrderSlice(d)
for i := range sliceCopy {
if !reflect.DeepEqual(*sliceCopy[i], *d[i]) {
t.Errorf("[%d] Copy does not contain same elements, expected: %v\ngot:%v", i, sliceCopy[i], d[i])
}
if len(sliceCopy[i].Trades) > 0 {
if &sliceCopy[i].Trades[0] == &d[i].Trades[0] {
t.Errorf("[%d]Trades point to the same data elements", i)
}
}
}
}

View File

@@ -466,7 +466,14 @@ func (d *Detail) GenerateInternalOrderID() {
}
}
// Copy will return a copy of Detail
// CopyToPointer will return the address of a new copy of the order Detail
// WARNING: DO NOT DEREFERENCE USE METHOD Copy().
func (d *Detail) CopyToPointer() *Detail {
c := d.Copy()
return &c
}
// Copy makes a full copy of underlying details NOTE: This is Addressable.
func (d *Detail) Copy() Detail {
c := *d
if len(d.Trades) > 0 {
@@ -476,6 +483,16 @@ func (d *Detail) Copy() Detail {
return c
}
// CopyPointerOrderSlice returns a copy of all order detail and returns a slice
// of pointers.
func CopyPointerOrderSlice(old []*Detail) []*Detail {
copySlice := make([]*Detail, len(old))
for x := range old {
copySlice[x] = old[x].CopyToPointer()
}
return copySlice
}
// String implements the stringer interface
func (t Type) String() string {
switch t {