gitlab.com/SkynetLabs/skyd@v1.6.9/cmd/skyc/accountingcmd_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"gitlab.com/NebulousLabs/fastrand"
    12  	"gitlab.com/SkynetLabs/skyd/skymodules"
    13  	"go.sia.tech/siad/types"
    14  )
    15  
    16  // randomAcountingInfo is a helper that generates random accounting information
    17  func randomAcountingInfo(num int) []skymodules.AccountingInfo {
    18  	var ais []skymodules.AccountingInfo
    19  	for i := 0; i < num; i++ {
    20  		ais = append(ais, skymodules.AccountingInfo{
    21  			Wallet: skymodules.WalletAccounting{
    22  				ConfirmedSiacoinBalance: types.NewCurrency64(fastrand.Uint64n(1000)),
    23  				ConfirmedSiafundBalance: types.NewCurrency64(fastrand.Uint64n(1000)),
    24  			},
    25  			Renter: skymodules.RenterAccounting{
    26  				UnspentUnallocated: types.NewCurrency64(fastrand.Uint64n(1000)),
    27  				WithheldFunds:      types.NewCurrency64(fastrand.Uint64n(1000)),
    28  			},
    29  			Timestamp: time.Now().Unix(),
    30  		})
    31  	}
    32  	return ais
    33  }
    34  
    35  // TestWriteAccountingCSV tests the writeAccountingCSV function
    36  func TestWriteAccountingCSV(t *testing.T) {
    37  	t.Parallel()
    38  
    39  	// Create buffer
    40  	var buf bytes.Buffer
    41  
    42  	// Create accounting information
    43  	ais := randomAcountingInfo(5)
    44  
    45  	// Write the information to the csv file.
    46  	err := writeAccountingCSV(ais, &buf)
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  
    51  	// Read the written data from the file.
    52  	bytes := buf.Bytes()
    53  
    54  	// Generated expected
    55  	headerStr := strings.Join(accountingHeaders, ",")
    56  	expected := fmt.Sprintf("%v\n", headerStr)
    57  	for _, ai := range ais {
    58  		timeStr := strconv.FormatInt(ai.Timestamp, 10)
    59  		scStr := ai.Wallet.ConfirmedSiacoinBalance.String()
    60  		sfStr := ai.Wallet.ConfirmedSiafundBalance.String()
    61  		usStr := ai.Renter.UnspentUnallocated.String()
    62  		whStr := ai.Renter.WithheldFunds.String()
    63  		data := []string{timeStr, scStr, sfStr, usStr, whStr}
    64  		dataStr := strings.Join(data, ",")
    65  		expected = fmt.Sprintf("%v%v\n", expected, dataStr)
    66  	}
    67  
    68  	// Check bytes vs expected
    69  	if expected != string(bytes) {
    70  		t.Log("actual", string(bytes))
    71  		t.Log("expected", expected)
    72  		t.Fatal("unexpected")
    73  	}
    74  }
    75  
    76  // TestAverageSCPriceAndTxnValue verifies the functionality of
    77  // averageSCPriceAndTxnValue
    78  func TestAverageSCPriceAndTxnValue(t *testing.T) {
    79  	now := time.Now()
    80  	before := now.AddDate(-1, 0, 0)
    81  	after := now.AddDate(1, 0, 0)
    82  	var tests = []struct {
    83  		name               string
    84  		scPrices           []SCPrice
    85  		funds              types.Currency
    86  		timestamp          time.Time
    87  		expectedSCPAverage string
    88  		expectAvgPrice     float64
    89  	}{
    90  		{"nil scpPrices", nil, types.SiacoinPrecision, now, "", 0},
    91  		{"zero funds", nil, types.ZeroCurrency, now, "0", 0},
    92  		{"after time window", []SCPrice{
    93  			{Average: 1.00,
    94  				EndTime:   before,
    95  				StartTime: before,
    96  			},
    97  		}, types.SiacoinPrecision, now, "", 0},
    98  		{"before time window", []SCPrice{
    99  			{Average: 1.00,
   100  				EndTime:   after,
   101  				StartTime: after,
   102  			},
   103  		}, types.SiacoinPrecision, now, "", 0},
   104  		{"happy case", []SCPrice{
   105  			{Average: 1.00,
   106  				EndTime:   after,
   107  				StartTime: before,
   108  			},
   109  		}, types.SiacoinPrecision, now, "1", 1},
   110  		{"2 decimal places", []SCPrice{
   111  			{Average: 1.01,
   112  				EndTime:   after,
   113  				StartTime: before,
   114  			},
   115  		}, types.SiacoinPrecision, now, "1.01", 1.01},
   116  		{"less than 1", []SCPrice{
   117  			{Average: 0.01,
   118  				EndTime:   after,
   119  				StartTime: before,
   120  			},
   121  		}, types.SiacoinPrecision, now, "0.01", 0.01},
   122  		{"less than 0.01", []SCPrice{
   123  			{Average: 0.0001,
   124  				EndTime:   after,
   125  				StartTime: before,
   126  			},
   127  		}, types.SiacoinPrecision, now, "0.0001", 0.0001},
   128  	}
   129  	for _, test := range tests {
   130  		scpAverage, avgPrice := averageSCPriceAndTxnValue(test.scPrices, test.funds, test.timestamp)
   131  		if scpAverage != test.expectedSCPAverage {
   132  			t.Fatal(test.name, "wrong scpAverage", scpAverage, test.expectedSCPAverage)
   133  		}
   134  		if avgPrice != test.expectAvgPrice {
   135  			t.Fatal(test.name, "wrong avgPrice", avgPrice, test.expectAvgPrice)
   136  		}
   137  	}
   138  }
   139  
   140  // TestBlockAndTime tests the components of the blockAndTime function
   141  func TestBlockAndTime(t *testing.T) {
   142  	t.Run("Estimate", testBlockAndTimeEstiamte)
   143  	t.Run("Exact", testBlockAndTimeExact)
   144  }
   145  
   146  // testBlockAndTimeEstiamte verifies the functionality of blockAndTimeEstimate
   147  func testBlockAndTimeEstiamte(t *testing.T) {
   148  	// Define today as end of February
   149  	today := time.Date(2022, time.March, 0, 0, 0, 0, 0, time.UTC)
   150  
   151  	// twoMonths is the rough number of seconds in 2 months. Using this as
   152  	// the refHeight since the blockFrequency for testing is 1 sec.
   153  	twoMonths := types.BlockHeight(1440 * 60 * 60)
   154  
   155  	// Test start of month
   156  	startBlock, startTime := blockAndTimeEstimate(today, time.January, twoMonths, true)
   157  
   158  	// Get the expected Data
   159  	date := time.Date(today.Year(), time.January, 1, 0, 0, 0, 0, today.Location())
   160  
   161  	// Check Block
   162  	secondsSinceDate := uint64(today.Sub(date).Seconds())
   163  	blocksSinceStart := types.BlockHeight(secondsSinceDate) / types.BlockFrequency
   164  	blockHeight := twoMonths - blocksSinceStart
   165  	if blockHeight != startBlock {
   166  		t.Fatalf("Unexpected block; expected %v, got %v", blockHeight, startBlock)
   167  	}
   168  
   169  	// Check Time
   170  	expectedTime := date.Unix()
   171  	if expectedTime != startTime {
   172  		t.Fatalf("Unexpected time; expected %v, got %v", expectedTime, startTime)
   173  	}
   174  
   175  	// Test end of month
   176  	endBlock, endTime := blockAndTimeEstimate(today, time.January, twoMonths, false)
   177  
   178  	// Get the expected Data
   179  	day := skymodules.DaysInMonth(time.January, today.Year())
   180  	date = time.Date(today.Year(), time.January, day, 23, 59, 59, 0, today.Location())
   181  
   182  	// Check Block
   183  	secondsSinceDate = uint64(today.Sub(date).Seconds())
   184  	blocksSinceStart = types.BlockHeight(secondsSinceDate) / types.BlockFrequency
   185  	blockHeight = twoMonths - blocksSinceStart
   186  	if blockHeight != endBlock {
   187  		t.Fatalf("Unexpected block; expected %v, got %v", blockHeight, endBlock)
   188  	}
   189  
   190  	// Check Time
   191  	expectedTime = date.Unix()
   192  	if expectedTime != endTime {
   193  		t.Fatalf("Unexpected time; expected %v, got %v", expectedTime, endTime)
   194  	}
   195  }
   196  
   197  // testBlockAndTimeExact verifies the functionality of blockAndTimeExact
   198  func testBlockAndTimeExact(t *testing.T) {
   199  	if testing.Short() {
   200  		t.SkipNow()
   201  	}
   202  	t.Parallel()
   203  
   204  	// Create a test node/client for this test group
   205  	groupDir := skycTestDir(t.Name())
   206  	n, err := newTestNode(groupDir)
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	defer func() {
   211  		if err := n.Close(); err != nil {
   212  			t.Fatal(err)
   213  		}
   214  	}()
   215  
   216  	// Get a block and timeStamp
   217  	equalHeight, err := n.BlockHeight()
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	cbg, err := n.ConsensusBlocksHeightGet(equalHeight)
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	equalTime := cbg.Timestamp
   226  
   227  	// Sleep to ensure significant different between last block time stamp for testing and NDFs
   228  	time.Sleep(2 * time.Second)
   229  
   230  	// Grab a timestamp
   231  	beforeTime := time.Now().Unix()
   232  
   233  	// Sleep to ensure large gap between blocks for timestamp testing
   234  	time.Sleep(2 * time.Second)
   235  
   236  	// Mine some blocks so there are blocks after the timestamp
   237  	for i := 0; i < 5; i++ {
   238  		err = n.MineBlock()
   239  		if err != nil {
   240  			t.Fatal(err)
   241  		}
   242  		// Sleep in between blocks to ensure different timestamps
   243  		time.Sleep(time.Second)
   244  	}
   245  
   246  	var tests = []struct {
   247  		startingBlockHeight types.BlockHeight
   248  		expectedBlockHeight types.BlockHeight
   249  		dateTime            int64
   250  		start               bool
   251  	}{
   252  		// Test Cases
   253  
   254  		// Looking for start block, current block timestamp is before dateTime
   255  		{equalHeight, equalHeight + 1, beforeTime, true},
   256  		// Looking for start block, current block timestamp is after dateTime
   257  		{equalHeight + 1, equalHeight + 1, beforeTime, true},
   258  		// Looking for start block, current block timestamp is equal to dateTime
   259  		{equalHeight, equalHeight, int64(equalTime), true},
   260  		// Looking for end block, current block timestamp is before dateTime
   261  		{equalHeight, equalHeight, beforeTime, false},
   262  		// Looking for end block, current block timestamp is after dateTime
   263  		{equalHeight + 1, equalHeight, beforeTime, false},
   264  		// Looking for end block, current block timestamp is equal to dateTime
   265  		{equalHeight, equalHeight, int64(equalTime), false},
   266  	}
   267  	for i, test := range tests {
   268  		blockHeight, dateTimeReturn := blockAndTimeExact(n.Client, test.startingBlockHeight, test.dateTime, test.start)
   269  		if blockHeight != test.expectedBlockHeight {
   270  			t.Errorf("%v: Expected BH: %v, Actual BH: %v", i, test.expectedBlockHeight, blockHeight)
   271  		}
   272  		if dateTimeReturn != test.dateTime {
   273  			t.Fatal("dateTime shouldn't change", dateTimeReturn, test.dateTime)
   274  		}
   275  	}
   276  }