github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/parseduration_test.go (about) 1 package fs 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 // Check it satisfies the interfaces 15 var ( 16 _ flagger = (*Duration)(nil) 17 _ flaggerNP = Duration(0) 18 ) 19 20 func TestParseDuration(t *testing.T) { 21 now := time.Date(2020, 9, 5, 8, 15, 5, 250, time.UTC) 22 getNow := func() time.Time { 23 return now 24 } 25 26 for _, test := range []struct { 27 in string 28 want time.Duration 29 err bool 30 }{ 31 {"0", 0, false}, 32 {"", 0, true}, 33 {"1ms", time.Millisecond, false}, 34 {"1s", time.Second, false}, 35 {"1m", time.Minute, false}, 36 {"1.5m", (3 * time.Minute) / 2, false}, 37 {"1h", time.Hour, false}, 38 {"1d", time.Hour * 24, false}, 39 {"1w", time.Hour * 24 * 7, false}, 40 {"1M", time.Hour * 24 * 30, false}, 41 {"1y", time.Hour * 24 * 365, false}, 42 {"1.5y", time.Hour * 24 * 365 * 3 / 2, false}, 43 {"-1s", -time.Second, false}, 44 {"1.s", time.Second, false}, 45 {"1x", 0, true}, 46 {"off", time.Duration(DurationOff), false}, 47 {"1h2m3s", time.Hour + 2*time.Minute + 3*time.Second, false}, 48 {"2001-02-03", now.Sub(time.Date(2001, 2, 3, 0, 0, 0, 0, time.Local)), false}, 49 {"2001-02-03 10:11:12", now.Sub(time.Date(2001, 2, 3, 10, 11, 12, 0, time.Local)), false}, 50 {"2001-08-03 10:11:12", now.Sub(time.Date(2001, 8, 3, 10, 11, 12, 0, time.Local)), false}, 51 {"2001-02-03T10:11:12", now.Sub(time.Date(2001, 2, 3, 10, 11, 12, 0, time.Local)), false}, 52 {"2001-02-03T10:11:12.123Z", now.Sub(time.Date(2001, 2, 3, 10, 11, 12, 123, time.UTC)), false}, 53 {"2001-02-03T10:11:12.123+00:00", now.Sub(time.Date(2001, 2, 3, 10, 11, 12, 123, time.UTC)), false}, 54 } { 55 duration, err := parseDurationFromNow(test.in, getNow) 56 if test.err { 57 require.Error(t, err) 58 } else { 59 require.NoError(t, err) 60 } 61 if strings.HasPrefix(test.in, "2001-") { 62 ok := duration > test.want-time.Second && duration < test.want+time.Second 63 assert.True(t, ok, test.in) 64 } else { 65 assert.Equal(t, test.want, duration) 66 } 67 } 68 } 69 70 func TestDurationString(t *testing.T) { 71 now := time.Date(2020, 9, 5, 8, 15, 5, 250, time.UTC) 72 getNow := func() time.Time { 73 return now 74 } 75 76 for _, test := range []struct { 77 in time.Duration 78 want string 79 }{ 80 {time.Duration(0), "0s"}, 81 {time.Second, "1s"}, 82 {time.Minute, "1m0s"}, 83 {time.Millisecond, "1ms"}, 84 {time.Second, "1s"}, 85 {(3 * time.Minute) / 2, "1m30s"}, 86 {time.Hour, "1h0m0s"}, 87 {time.Hour * 24, "1d"}, 88 {time.Hour * 24 * 7, "1w"}, 89 {time.Hour * 24 * 30, "1M"}, 90 {time.Hour * 24 * 365, "1y"}, 91 {time.Hour * 24 * 365 * 3 / 2, "1.5y"}, 92 {-time.Second, "-1s"}, 93 {time.Second, "1s"}, 94 {time.Duration(DurationOff), "off"}, 95 {time.Hour + 2*time.Minute + 3*time.Second, "1h2m3s"}, 96 {time.Hour * 24, "1d"}, 97 {time.Hour * 24 * 7, "1w"}, 98 {time.Hour * 24 * 30, "1M"}, 99 {time.Hour * 24 * 365, "1y"}, 100 {time.Hour * 24 * 365 * 3 / 2, "1.5y"}, 101 {-time.Hour * 24 * 365 * 3 / 2, "-1.5y"}, 102 } { 103 got := Duration(test.in).String() 104 assert.Equal(t, test.want, got) 105 // Test the reverse 106 reverse, err := parseDurationFromNow(test.want, getNow) 107 assert.NoError(t, err) 108 assert.Equal(t, test.in, reverse) 109 } 110 } 111 112 func TestDurationReadableString(t *testing.T) { 113 for _, test := range []struct { 114 negative bool 115 in time.Duration 116 wantLong string 117 wantShort string 118 }{ 119 // Edge Cases 120 {false, time.Duration(DurationOff), "off", "off"}, 121 // Base Cases 122 {false, time.Duration(0), "0s", "0s"}, 123 {true, time.Millisecond, "1ms", "1ms"}, 124 {true, time.Second, "1s", "1s"}, 125 {true, time.Minute, "1m", "1m"}, 126 {true, (3 * time.Minute) / 2, "1m30s", "1m30s"}, 127 {true, time.Hour, "1h", "1h"}, 128 {true, time.Hour * 24, "1d", "1d"}, 129 {true, time.Hour * 24 * 7, "1w", "1w"}, 130 {true, time.Hour * 24 * 365, "1y", "1y"}, 131 // Composite Cases 132 {true, time.Hour + 2*time.Minute + 3*time.Second, "1h2m3s", "1h2m3s"}, 133 {true, time.Hour * 24 * (365 + 14), "1y2w", "1y2w"}, 134 {true, time.Hour*24*4 + time.Hour*3 + time.Minute*2 + time.Second, "4d3h2m1s", "4d3h2m"}, 135 {true, time.Hour * 24 * (365*3 + 7*2 + 1), "3y2w1d", "3y2w1d"}, 136 {true, time.Hour*24*(365*3+7*2+1) + time.Hour*2 + time.Second, "3y2w1d2h1s", "3y2w1d"}, 137 {true, time.Hour*24*(365*3+7*2+1) + time.Second, "3y2w1d1s", "3y2w1d"}, 138 {true, time.Hour*24*(365+7*2+3) + time.Hour*4 + time.Minute*5 + time.Second*6 + time.Millisecond*7, "1y2w3d4h5m6s7ms", "1y2w3d"}, 139 {true, time.Duration(DurationOff) / time.Millisecond * time.Millisecond, "292y24w3d23h47m16s853ms", "292y24w3d"}, // Should have been 854ms but some precision are lost with floating point calculations 140 } { 141 got := Duration(test.in).ReadableString() 142 assert.Equal(t, test.wantLong, got) 143 got = Duration(test.in).ShortReadableString() 144 assert.Equal(t, test.wantShort, got) 145 146 // Test Negative Case 147 if test.negative { 148 got = Duration(-test.in).ReadableString() 149 assert.Equal(t, "-"+test.wantLong, got) 150 got = Duration(-test.in).ShortReadableString() 151 assert.Equal(t, "-"+test.wantShort, got) 152 } 153 } 154 } 155 156 func TestDurationScan(t *testing.T) { 157 now := time.Date(2020, 9, 5, 8, 15, 5, 250, time.UTC) 158 oldTimeNowFunc := timeNowFunc 159 timeNowFunc = func() time.Time { return now } 160 defer func() { timeNowFunc = oldTimeNowFunc }() 161 162 for _, test := range []struct { 163 in string 164 want Duration 165 }{ 166 {"17m", Duration(17 * time.Minute)}, 167 {"-12h", Duration(-12 * time.Hour)}, 168 {"0", Duration(0)}, 169 {"off", DurationOff}, 170 {"2022-03-26T17:48:19Z", Duration(now.Sub(time.Date(2022, 03, 26, 17, 48, 19, 0, time.UTC)))}, 171 {"2022-03-26 17:48:19", Duration(now.Sub(time.Date(2022, 03, 26, 17, 48, 19, 0, time.Local)))}, 172 } { 173 var got Duration 174 n, err := fmt.Sscan(test.in, &got) 175 require.NoError(t, err) 176 assert.Equal(t, 1, n) 177 assert.Equal(t, test.want, got) 178 } 179 } 180 181 func TestParseUnmarshalJSON(t *testing.T) { 182 for _, test := range []struct { 183 in string 184 want time.Duration 185 err bool 186 }{ 187 {`""`, 0, true}, 188 {`"0"`, 0, false}, 189 {`"1ms"`, time.Millisecond, false}, 190 {`"1s"`, time.Second, false}, 191 {`"1m"`, time.Minute, false}, 192 {`"1h"`, time.Hour, false}, 193 {`"1d"`, time.Hour * 24, false}, 194 {`"1w"`, time.Hour * 24 * 7, false}, 195 {`"1M"`, time.Hour * 24 * 30, false}, 196 {`"1y"`, time.Hour * 24 * 365, false}, 197 {`"off"`, time.Duration(DurationOff), false}, 198 {`"error"`, 0, true}, 199 {"0", 0, false}, 200 {"1000000", time.Millisecond, false}, 201 {"1000000000", time.Second, false}, 202 {"60000000000", time.Minute, false}, 203 {"3600000000000", time.Hour, false}, 204 {"9223372036854775807", time.Duration(DurationOff), false}, 205 {"error", 0, true}, 206 } { 207 var duration Duration 208 err := json.Unmarshal([]byte(test.in), &duration) 209 if test.err { 210 require.Error(t, err, test.in) 211 } else { 212 require.NoError(t, err, test.in) 213 } 214 assert.Equal(t, Duration(test.want), duration, test.in) 215 } 216 } 217 218 func TestUnmarshalJSON(t *testing.T) { 219 tests := []struct { 220 name string 221 input string 222 want Duration 223 wantErr bool 224 }{ 225 {"off string", `"off"`, DurationOff, false}, 226 {"max int64", `9223372036854775807`, DurationOff, false}, 227 {"duration string", `"1h"`, Duration(time.Hour), false}, 228 {"invalid string", `"invalid"`, 0, true}, 229 {"negative int", `-1`, Duration(-1), false}, 230 } 231 232 for _, tt := range tests { 233 t.Run(tt.name, func(t *testing.T) { 234 var d Duration 235 err := json.Unmarshal([]byte(tt.input), &d) 236 if (err != nil) != tt.wantErr { 237 t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) 238 return 239 } 240 if d != tt.want { 241 t.Errorf("UnmarshalJSON() got = %v, want %v", d, tt.want) 242 } 243 }) 244 } 245 }