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

     1  package main
     2  
     3  import (
     4  	"math"
     5  	"math/big"
     6  	"testing"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  	"gitlab.com/NebulousLabs/fastrand"
    10  	"go.sia.tech/siad/types"
    11  )
    12  
    13  // TestParseFileSize probes the parseFilesize function
    14  func TestParseFilesize(t *testing.T) {
    15  	tests := []struct {
    16  		in, out string
    17  		err     error
    18  	}{
    19  		{"1b", "1", nil},
    20  		{"1 b", "1", nil},
    21  		{"1KB", "1000", nil},
    22  		{"1   kb", "1000", nil},
    23  		{"1 kB", "1000", nil},
    24  		{" 1Kb ", "1000", nil},
    25  		{"1MB", "1000000", nil},
    26  		{"1 MB", "1000000", nil},
    27  		{"   1GB ", "1000000000", nil},
    28  		{"1 GB   ", "1000000000", nil},
    29  		{"1TB", "1000000000000", nil},
    30  		{"1 TB", "1000000000000", nil},
    31  		{"1KiB", "1024", nil},
    32  		{"1 KiB", "1024", nil},
    33  		{"1MiB", "1048576", nil},
    34  		{"1 MiB", "1048576", nil},
    35  		{"1GiB", "1073741824", nil},
    36  		{"1 GiB", "1073741824", nil},
    37  		{"1TiB", "1099511627776", nil},
    38  		{"1 TiB", "1099511627776", nil},
    39  		{"", "", ErrParseSizeUnits},
    40  		{"123", "", ErrParseSizeUnits},
    41  		{"123b", "123", nil},
    42  		{"123 TB", "123000000000000", nil},
    43  		{"123GiB", "132070244352", nil},
    44  		{"123BiB", "", ErrParseSizeAmount},
    45  		{"GB", "", ErrParseSizeAmount},
    46  		{"123G", "", ErrParseSizeUnits},
    47  		{"123B99", "", ErrParseSizeUnits},
    48  		{"12A3456", "", ErrParseSizeUnits},
    49  		{"1.23KB", "1230", nil},
    50  		{"1.234 KB", "1234", nil},
    51  		{"1.2345KB", "1234", nil},
    52  	}
    53  	for _, test := range tests {
    54  		res, err := parseFilesize(test.in)
    55  		if res != test.out || err != test.err {
    56  			t.Errorf("parseFilesize(%v): expected %v %v, got %v %v", test.in, test.out, test.err, res, err)
    57  		}
    58  	}
    59  }
    60  
    61  // TestParsePeriod probes the parsePeriod function
    62  func TestParsePeriod(t *testing.T) {
    63  	tests := []struct {
    64  		in, out string
    65  		err     error
    66  	}{
    67  		{"x", "", ErrParsePeriodUnits},
    68  		{"1", "", ErrParsePeriodUnits},
    69  		{"b", "", ErrParsePeriodAmount},
    70  		{"1b", "1", nil},
    71  		{"1 b", "1", nil},
    72  		{"1block", "1", nil},
    73  		{"1 block ", "1", nil},
    74  		{"1blocks", "1", nil},
    75  		{"1 blocks", "1", nil},
    76  		{" 2b ", "2", nil},
    77  		{"2 b", "2", nil},
    78  		{"2block", "2", nil},
    79  		{"2 block", "2", nil},
    80  		{"2blocks", "2", nil},
    81  		{"2 blocks", "2", nil},
    82  		{"2h", "12", nil},
    83  		{"2 h", "12", nil},
    84  		{"2hour", "12", nil},
    85  		{"2 hour", "12", nil},
    86  		{" 2hours ", "12", nil},
    87  		{"2 hours", "12", nil},
    88  		{"0.5d", "72", nil},
    89  		{" 0.5 d", "72", nil},
    90  		{"0.5day", "72", nil},
    91  		{"0.5 day", "72", nil},
    92  		{"0.5days", "72", nil},
    93  		{"0.5 days", "72", nil},
    94  		{"10w", "10080", nil},
    95  		{"10 w", "10080", nil},
    96  		{"10week", "10080", nil},
    97  		{"10 week", "10080", nil},
    98  		{"10weeks", "10080", nil},
    99  		{"10 weeks", "10080", nil},
   100  		{"1 fortnight", "", ErrParsePeriodUnits},
   101  		{"three h", "", ErrParsePeriodAmount},
   102  	}
   103  	for _, test := range tests {
   104  		res, err := parsePeriod(test.in)
   105  		if res != test.out || err != test.err {
   106  			t.Errorf("parsePeriod(%v): expected %v %v, got %v %v", test.in, test.out, test.err, res, err)
   107  		}
   108  	}
   109  }
   110  
   111  // TestCurrencyUnits probes the currencyUnits function
   112  func TestCurrencyUnits(t *testing.T) {
   113  	tests := []struct {
   114  		in, out string
   115  	}{
   116  		{"1", "1 H"},
   117  		{"1000", "1000 H"},
   118  		{"100000000000", "100000000000 H"},
   119  		{"1000000000000", "1 pS"},
   120  		{"1234560000000", "1.235 pS"},
   121  		{"12345600000000", "12.35 pS"},
   122  		{"123456000000000", "123.5 pS"},
   123  		{"1000000000000000", "1 nS"},
   124  		{"1000000000000000000", "1 uS"},
   125  		{"1000000000000000000000", "1 mS"},
   126  		{"1000000000000000000000000", "1 SC"},
   127  		{"1000000000000000000000000000", "1 KS"},
   128  		{"1000000000000000000000000000000", "1 MS"},
   129  		{"1000000000000000000000000000000000", "1 GS"},
   130  		{"1000000000000000000000000000000000000", "1 TS"},
   131  		{"1234560000000000000000000000000000000", "1.235 TS"},
   132  		{"1234560000000000000000000000000000000000", "1235 TS"},
   133  	}
   134  	for _, test := range tests {
   135  		i, _ := new(big.Int).SetString(test.in, 10)
   136  		out := currencyUnits(types.NewCurrency(i))
   137  		if out != test.out {
   138  			t.Errorf("currencyUnits(%v): expected %v, got %v", test.in, test.out, out)
   139  		}
   140  	}
   141  }
   142  
   143  // TestCurrencyUnitsWithExchangeRate probes the currencyUnitsWithExchangeRate
   144  // function. Here we only test for the general format. The precise formatting of
   145  // the foreign currency amount is further tested in types/exchangerate_test.go .
   146  func TestCurrencyUnitsWithExchangeRate(t *testing.T) {
   147  	tests := []struct {
   148  		cStr    string
   149  		rateStr string
   150  		result  string
   151  	}{
   152  		{"1", "", "1 H"},
   153  		{"1000000000000000000000", "n/a", "1 mS"},
   154  		{"1000000000000000000000000", "---", "1 SC"},
   155  		{"1000000000000000000000000000", "", "1 KS"},
   156  		{"1000000000000000000000000", "1 USD", "1 SC (~ 1.00 USD)"},
   157  		{"100000000000000000000000", "1 EUR", "100 mS (~ 0.10 EUR)"},
   158  		{"10000000000000000000000", "10 EUR", "10 mS (~ 0.10 EUR)"},
   159  		{"1000000000000000000000000000", "0.0039 USD", "1 KS (~ 3.90 USD)"},
   160  	}
   161  	for _, test := range tests {
   162  		i, _ := new(big.Int).SetString(test.cStr, 10)
   163  
   164  		rate, _ := types.ParseExchangeRate(test.rateStr)
   165  		// ignore potential parse errors; those are being tested in types/exchangerate_test.go
   166  
   167  		result := currencyUnitsWithExchangeRate(types.NewCurrency(i), rate)
   168  		if test.result != result {
   169  			t.Errorf("currencyUnitsWithExchangeRate(%v, %v): expected %#v, got %#v",
   170  				test.cStr, test.rateStr, test.result, result)
   171  		}
   172  	}
   173  }
   174  
   175  // TestRateLimitUnits probes the ratelimitUnits function
   176  func TestRatelimitUnits(t *testing.T) {
   177  	tests := []struct {
   178  		in  int64
   179  		out string
   180  	}{
   181  		{0, "0 B/s"},
   182  		{123, "123 B/s"},
   183  		{1234, "1.234 KB/s"},
   184  		{1234000, "1.234 MB/s"},
   185  		{1234000000, "1.234 GB/s"},
   186  		{1234000000000, "1.234 TB/s"},
   187  	}
   188  	for _, test := range tests {
   189  		out := ratelimitUnits(test.in)
   190  		if out != test.out {
   191  			t.Errorf("ratelimitUnits(%v): expected %v, got %v", test.in, test.out, out)
   192  		}
   193  	}
   194  }
   195  
   196  // TestParseRateLimit probes the parseRatelimit function
   197  func TestParseRatelimit(t *testing.T) {
   198  	tests := []struct {
   199  		in  string
   200  		out int64
   201  		err error
   202  	}{
   203  		{"x", 0, ErrParseRateLimitUnits},
   204  		{"1", 0, ErrParseRateLimitUnits},
   205  		{"B/s", 0, ErrParseRateLimitNoAmount},
   206  		{"Bps", 0, ErrParseRateLimitNoAmount},
   207  		{"1Bps", 0, ErrParseRateLimitAmount},
   208  		{" 1B/s ", 1, nil},
   209  		{"1 B/s", 1, nil},
   210  		{"8Bps", 1, nil},
   211  		{"8 Bps", 1, nil},
   212  		{" 1KB/s ", 1000, nil},
   213  		{"1 KB/s", 1000, nil},
   214  		{"8Kbps", 1000, nil},
   215  		{" 8 Kbps", 1000, nil},
   216  		{"1MB/s", 1000000, nil},
   217  		{"1 MB/s", 1000000, nil},
   218  		{"8Mbps", 1000000, nil},
   219  		{"8 Mbps", 1000000, nil},
   220  		{"1GB/s", 1000000000, nil},
   221  		{"1 GB/s", 1000000000, nil},
   222  		{"8Gbps", 1000000000, nil},
   223  		{"8 Gbps", 1000000000, nil},
   224  		{"1TB/s", 1000000000000, nil},
   225  		{"1 TB/s", 1000000000000, nil},
   226  		{"8Tbps", 1000000000000, nil},
   227  		{"8 Tbps", 1000000000000, nil},
   228  	}
   229  
   230  	for _, test := range tests {
   231  		res, err := parseRatelimit(test.in)
   232  		if res != test.out || (err != test.err && !errors.Contains(err, test.err)) {
   233  			t.Errorf("parsePeriod(%v): expected %v %v, got %v %v", test.in, test.out, test.err, res, err)
   234  		}
   235  	}
   236  }
   237  
   238  // TestParsePercentages probes the parsePercentages function
   239  func TestParsePercentages(t *testing.T) {
   240  	tests := []struct {
   241  		in  []float64
   242  		out []float64
   243  	}{
   244  		{[]float64{50.0, 50.0}, []float64{50, 50}},
   245  		{[]float64{49.5, 50.5}, []float64{50, 50}},
   246  		{[]float64{33.1, 33.4, 33.5}, []float64{33, 33, 34}},
   247  		{[]float64{63.1, 33.4, 3.5}, []float64{63, 33, 4}},
   248  		{[]float64{0, 0, 100}, []float64{0, 0, 100}},
   249  		{[]float64{100}, []float64{100}},
   250  	}
   251  
   252  	// Test set cases to ensure known edge cases are always handled
   253  	for _, test := range tests {
   254  		res := parsePercentages(test.in)
   255  		for i, v := range res {
   256  			if v != test.out[i] {
   257  				t.Log("Result", res)
   258  				t.Log("Expected", test.out)
   259  				t.Fatal("Result not as expected")
   260  			}
   261  		}
   262  	}
   263  
   264  	// For test-long test additional random cases
   265  	if testing.Short() {
   266  		t.SkipNow()
   267  	}
   268  
   269  	// Test Random Edge Cases
   270  	for i := 0; i < 10; i++ {
   271  		values := parsePercentages(randomPercentages())
   272  		// Since we can't know what the exact output should be, verify that the
   273  		// values add up to 100 and that none of the values have a non zero
   274  		// remainder
   275  		var total float64
   276  		for _, v := range values {
   277  			_, r := math.Modf(v)
   278  			if r != 0 {
   279  				t.Log(values)
   280  				t.Log(v)
   281  				t.Fatal("Found non zero remainder")
   282  			}
   283  			total += v
   284  		}
   285  		if total != float64(100) {
   286  			t.Log(values)
   287  			t.Log(total)
   288  			t.Fatal("Values should add up to 100 but added up to", total)
   289  		}
   290  	}
   291  }
   292  
   293  // randomPercentages creates a slice of pseudo random size, up to 500 elements,
   294  // with random elements that add to 100.
   295  //
   296  // NOTE: this function does not explicitly check that all the elements strictly
   297  // add up to 100 due to potential significant digit rounding errors. It was
   298  // common to see the elements add up to 100.00000000000001.
   299  func randomPercentages() []float64 {
   300  	var p []float64
   301  
   302  	remainder := float64(100)
   303  	for i := 0; i < 500; i++ {
   304  		n := float64(fastrand.Intn(1000))
   305  		d := float64(fastrand.Intn(100000)) + n
   306  		val := n / d * 100
   307  		if math.IsNaN(val) || remainder < val {
   308  			continue
   309  		}
   310  		remainder -= val
   311  		p = append(p, val)
   312  		if remainder == 0 {
   313  			break
   314  		}
   315  	}
   316  
   317  	// Check if we have a remainder to add
   318  	if remainder > 0 {
   319  		p = append(p, remainder)
   320  	}
   321  
   322  	return p
   323  }
   324  
   325  // TestSizeString probes the sizeString function
   326  func TestSizeString(t *testing.T) {
   327  	tests := []struct {
   328  		in  uint64
   329  		out string
   330  	}{
   331  		{0, "0 B"},
   332  		{1, "1 B"},
   333  		{123, "123 B"},
   334  		{999, "999 B"},
   335  		{1000, "1 KB"},
   336  		{1001, "1.001 KB"},
   337  		{1234, "1.234 KB"},
   338  		{12340, "12.34 KB"},
   339  		{123400, "123.4 KB"},
   340  		{1234000, "1.234 MB"},
   341  		{500000, "500 KB"},
   342  		{900000, "900 KB"},
   343  		{998999, "999 KB"},
   344  		{999001, "999 KB"},
   345  		{999998, "1 MB"}, // Should round up to 1MB
   346  		{999999, "1 MB"}, // Should round up to 1MB
   347  		{1000001, "1 MB"},
   348  		{1999999, "2 MB"}, // Should round up to 2MB
   349  		{2000001, "2 MB"},
   350  		{1234000, "1.234 MB"},
   351  		{1235000000, "1.235 GB"},       // Verifies it doesn't round the last digit
   352  		{1234500000000, "1.234 TB"},    // Verifies it truncates and doesn't round
   353  		{1234490000000000, "1.234 PB"}, // Verifies it truncates and doesn't round
   354  		{1234000000000000000, "1.234 EB"},
   355  		{math.MaxUint64, "18.45 EB"},
   356  	}
   357  	for _, test := range tests {
   358  		out := sizeString(test.in)
   359  		if out != test.out {
   360  			t.Errorf("sizeString(%v): expected %v, got %v", test.in, test.out, out)
   361  		}
   362  	}
   363  
   364  	// Add some random tests for any edge case panics
   365  	for i := 0; i < 1e3; i++ {
   366  		sizeString(fastrand.Uint64n(math.MaxUint64))
   367  	}
   368  }