decred.org/dcrdex@v1.0.5/server/auth/cancel_test.go (about)

     1  package auth
     2  
     3  import (
     4  	crand "crypto/rand"
     5  	"math/rand"
     6  	"sort"
     7  	"testing"
     8  
     9  	"decred.org/dcrdex/dex/order"
    10  	"decred.org/dcrdex/server/db"
    11  )
    12  
    13  func randomOrderID() (oid order.OrderID) {
    14  	crand.Read(oid[:])
    15  	return
    16  }
    17  
    18  func Test_latestOrders(t *testing.T) {
    19  	cap := int16(cancelThreshWindow)
    20  	ordList := newLatestOrders(cap)
    21  
    22  	maybeCancel := func() *order.OrderID {
    23  		if rand.Intn(6) > 4 {
    24  			oid := randomOrderID()
    25  			return &oid
    26  		}
    27  		return nil
    28  	}
    29  
    30  	checkSort := func() {
    31  		if !sort.IsSorted(ordsByTimeThenID(ordList.orders)) {
    32  			t.Fatal("list wasn't sorted")
    33  		}
    34  		if len(ordList.orders) > int(ordList.cap) {
    35  			t.Fatalf("list is above capacity somehow")
    36  		}
    37  	}
    38  
    39  	// empty list
    40  	total, cancels := ordList.counts()
    41  	if total != 0 {
    42  		t.Errorf("expected 0 orders, got %d", total)
    43  	}
    44  	if cancels != 0 {
    45  		t.Errorf("expected 0 cancels, got %d", total)
    46  	}
    47  
    48  	// add one cancel
    49  	ts := int64(1234)
    50  	coid := randomOrderID()
    51  	ordList.add(&oidStamped{order.OrderID{0x1}, ts, &coid, 1})
    52  	checkSort()
    53  	total, cancels = ordList.counts()
    54  	if total != 1 {
    55  		t.Errorf("expected 1 orders, got %d", total)
    56  	}
    57  	if cancels != 1 {
    58  		t.Errorf("expected 1 cancels, got %d", cancels)
    59  	}
    60  
    61  	// add one non-cancel
    62  	ts++
    63  	ordList.add(&oidStamped{order.OrderID{0x2}, ts, nil, db.EpochGapNA})
    64  	checkSort()
    65  	total, cancels = ordList.counts()
    66  	if total != 2 {
    67  		t.Errorf("expected 2 orders, got %d", total)
    68  	}
    69  	if cancels != 1 {
    70  		t.Errorf("expected 1 cancels, got %d", total)
    71  	}
    72  
    73  	// add one that is the smallest
    74  	ordList.add(&oidStamped{order.OrderID{0x3}, ts - 10, nil, db.EpochGapNA})
    75  	checkSort()
    76  	total, cancels = ordList.counts()
    77  	if total != 3 {
    78  		t.Errorf("expected 3 orders, got %d", total)
    79  	}
    80  	if cancels != 1 {
    81  		t.Errorf("expected 1 cancels, got %d", total)
    82  	}
    83  
    84  	for i := total; i < int(cap); i++ {
    85  		ts++
    86  		ordList.add(&oidStamped{
    87  			OrderID:  randomOrderID(),
    88  			time:     ts,
    89  			target:   maybeCancel(),
    90  			epochGap: db.EpochGapNA,
    91  		})
    92  		checkSort()
    93  	}
    94  
    95  	total, _ = ordList.counts()
    96  	if total != int(cap) {
    97  		t.Errorf("expected %d orders, got %d", int(cap), total)
    98  	}
    99  	//t.Logf("got %d cancels", cancels)
   100  
   101  	// Now that the list is at capacity, add another to test pop of the oldest order.
   102  	expectedOldest := ordList.orders[1] // the second oldest order
   103  	ts += 2                             // still in order, leave space for an out of order add
   104  	ordList.add(&oidStamped{
   105  		OrderID: order.OrderID{0x4},
   106  		time:    ts,
   107  		target:  maybeCancel(),
   108  	})
   109  	checkSort()
   110  
   111  	// should still be at capacity
   112  	total, _ = ordList.counts()
   113  	if total != int(cap) {
   114  		t.Errorf("expected %d orders, got %d", int(cap), total)
   115  	}
   116  
   117  	// verify the oldest order is the previously second oldest
   118  	if expectedOldest != ordList.orders[0] {
   119  		t.Errorf("expected oldest order to be %x, got %x", expectedOldest, ordList.orders[0])
   120  	}
   121  
   122  	// Now add one with the same time as the last, but larger order ID, thus not
   123  	// requiring a swap.
   124  	oid4 := order.OrderID{0x5}
   125  	ordList.add(&oidStamped{
   126  		OrderID: oid4,
   127  		time:    ts,
   128  		target:  maybeCancel(),
   129  	})
   130  	checkSort()
   131  
   132  	// verify the latest order is the one just stored
   133  	latestOid := ordList.orders[len(ordList.orders)-1].OrderID
   134  	if oid4 != latestOid {
   135  		t.Errorf("expected latest order ID to be %x, got %x", oid4, latestOid)
   136  	}
   137  
   138  	// Add another with the same time as last, but this time with a smaller ID,
   139  	// thus requiring a swap.
   140  	oid0 := order.OrderID{0x0}
   141  	ordList.add(&oidStamped{
   142  		OrderID: oid0,
   143  		time:    ts,
   144  		target:  maybeCancel(),
   145  	})
   146  	checkSort()
   147  
   148  	// verify the latest order has not changed
   149  	latestOid = ordList.orders[len(ordList.orders)-1].OrderID
   150  	if oid4 != latestOid {
   151  		t.Errorf("expected latest order ID to be %x, got %x", oid4, latestOid)
   152  	}
   153  
   154  	// Add one with an older time, thus necessitating a few swaps.
   155  	ts--
   156  	ordList.add(&oidStamped{
   157  		OrderID: randomOrderID(),
   158  		time:    ts,
   159  		target:  maybeCancel(),
   160  	})
   161  	checkSort()
   162  
   163  	// verify the latest order has not changed
   164  	latestOid = ordList.orders[len(ordList.orders)-1].OrderID
   165  	if oid4 != latestOid {
   166  		t.Errorf("expected latest order ID to be %x, got %x", oid4, latestOid)
   167  	}
   168  
   169  	// Now exercise it and ensure it is always sorted.
   170  	for i := 0; i < 100000; i++ {
   171  		ordList.add(&oidStamped{
   172  			OrderID: randomOrderID(),
   173  			time:    rand.Int63n(44444),
   174  			target:  maybeCancel(),
   175  		})
   176  		checkSort()
   177  	}
   178  }
   179  
   180  func Test_ordsByTimeThenID_Sort(t *testing.T) {
   181  	tests := []struct {
   182  		name     string
   183  		ords     []*oidStamped
   184  		wantOrds []*oidStamped
   185  	}{
   186  		{
   187  			name: "unique, no swap",
   188  			ords: []*oidStamped{
   189  				{order.OrderID{0x1}, 1234, nil, db.EpochGapNA},
   190  				{order.OrderID{0x2}, 1235, nil, db.EpochGapNA},
   191  			},
   192  			wantOrds: []*oidStamped{
   193  				{order.OrderID{0x1}, 1234, nil, db.EpochGapNA},
   194  				{order.OrderID{0x2}, 1235, nil, db.EpochGapNA},
   195  			},
   196  		},
   197  		{
   198  			name: "unique, one swap",
   199  			ords: []*oidStamped{
   200  				{order.OrderID{0x2}, 1235, nil, db.EpochGapNA},
   201  				{order.OrderID{0x1}, 1234, nil, db.EpochGapNA},
   202  			},
   203  			wantOrds: []*oidStamped{
   204  				{order.OrderID{0x1}, 1234, nil, db.EpochGapNA},
   205  				{order.OrderID{0x2}, 1235, nil, db.EpochGapNA},
   206  			},
   207  		},
   208  		{
   209  			name: "time tie, swap by order ID",
   210  			ords: []*oidStamped{
   211  				{order.OrderID{0x2}, 1234, nil, db.EpochGapNA},
   212  				{order.OrderID{0x1}, 1234, nil, db.EpochGapNA},
   213  			},
   214  			wantOrds: []*oidStamped{
   215  				{order.OrderID{0x1}, 1234, nil, db.EpochGapNA},
   216  				{order.OrderID{0x2}, 1234, nil, db.EpochGapNA},
   217  			},
   218  		},
   219  	}
   220  	for _, tt := range tests {
   221  		t.Run(tt.name, func(t *testing.T) {
   222  			sort.Sort(ordsByTimeThenID(tt.ords))
   223  			for i, o := range tt.ords {
   224  				if o.OrderID != tt.wantOrds[i].OrderID {
   225  					t.Errorf("element %d has order ID %x, wanted %x", i,
   226  						o.OrderID, tt.wantOrds[i].OrderID)
   227  				}
   228  			}
   229  		})
   230  	}
   231  
   232  	// Identical orders in the slice now just log and error, but the case can be
   233  	// tested with the panic instead:
   234  	//
   235  	// t.Run("dup order should panic", func(t *testing.T) {
   236  	//  defer func() {
   237  	//      if recover() == nil {
   238  	//          t.Error("sort should have paniced with identical orders.")
   239  	//      }
   240  	//  }()
   241  	//  dups := []*oidStamped{
   242  	//      {order.OrderID{0x1}, 1234, nil},
   243  	//      {order.OrderID{0x1}, 1234, nil},
   244  	//  }
   245  	//  sort.Sort(ordsByTimeThenID(dups))
   246  	// })
   247  }