Fix every finding surfaced by the canonical .golangci.yml with golangci-lint v2.12.2 (refs #61), behavior-preserving throughout: - err113: dynamic errors replaced with package-level sentinels and %w wrapping; direct comparisons converted to errors.Is - goprintffuncname: printf-style helpers renamed with an f suffix (ui.Writer message methods, cli.ReportErrorf, database.Fatalf, vaultik stdoutf) and all call sites updated - revive: stuttering type names renamed (blob.Handler, blob.WithReader, blob.ChunkPosition, storage.URL, storage.Info), doc comments added, unused parameters blanked, package comments added - contextcheck/noctx: ctx threaded through blob.Packer (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites; context-aware exec and sql variants used - funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated functions split into focused helpers across production and test code - paralleltest/tparallel/thelper/usetesting/testpackage: tests parallelized where safe (global log.Initialize kept in the serial phase), helpers marked, t.TempDir adopted, external test packages where only exported API is used - gosec: integer conversions clamped or justified, header timeouts added, remaining findings suppressed with per-site justifications - mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other mechanical findings fixed directly Remove the deprecated log.LogOptions alias (callers migrated to log.Options). make check is green.
300 lines
6.0 KiB
Go
300 lines
6.0 KiB
Go
package cli //nolint:testpackage // needs access to unexported parseDuration
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type parseDurationCase struct {
|
|
name string
|
|
input string
|
|
expected time.Duration
|
|
wantErr bool
|
|
}
|
|
|
|
// runParseDurationCases executes a table of parseDuration cases as
|
|
// parallel subtests.
|
|
func runParseDurationCases(t *testing.T, tests []parseDurationCase) {
|
|
t.Helper()
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := parseDuration(tt.input)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err, "expected error for input %q", tt.input)
|
|
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err, "unexpected error for input %q", tt.input)
|
|
assert.Equal(t, tt.expected, got, "duration mismatch for input %q", tt.input)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseDurationStandard(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runParseDurationCases(t, []parseDurationCase{
|
|
{
|
|
name: "standard seconds",
|
|
input: "30s",
|
|
expected: 30 * time.Second,
|
|
},
|
|
{
|
|
name: "standard minutes",
|
|
input: "45m",
|
|
expected: 45 * time.Minute,
|
|
},
|
|
{
|
|
name: "standard hours",
|
|
input: "2h",
|
|
expected: 2 * time.Hour,
|
|
},
|
|
{
|
|
name: "standard combined",
|
|
input: "3h30m",
|
|
expected: 3*time.Hour + 30*time.Minute,
|
|
},
|
|
{
|
|
name: "standard complex",
|
|
input: "1h45m30s",
|
|
expected: 1*time.Hour + 45*time.Minute + 30*time.Second,
|
|
},
|
|
{
|
|
name: "standard with milliseconds",
|
|
input: "1s500ms",
|
|
expected: 1*time.Second + 500*time.Millisecond,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestParseDurationExtendedUnits(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runParseDurationCases(t, []parseDurationCase{
|
|
// Extended units - days
|
|
{
|
|
name: "single day",
|
|
input: "1d",
|
|
expected: 24 * time.Hour,
|
|
},
|
|
{
|
|
name: "multiple days",
|
|
input: "7d",
|
|
expected: 7 * 24 * time.Hour,
|
|
},
|
|
{
|
|
name: "fractional days",
|
|
input: "1.5d",
|
|
expected: 36 * time.Hour,
|
|
},
|
|
{
|
|
name: "days spelled out",
|
|
input: "3days",
|
|
expected: 3 * 24 * time.Hour,
|
|
},
|
|
// Extended units - weeks
|
|
{
|
|
name: "single week",
|
|
input: "1w",
|
|
expected: 7 * 24 * time.Hour,
|
|
},
|
|
{
|
|
name: "multiple weeks",
|
|
input: "4w",
|
|
expected: 4 * 7 * 24 * time.Hour,
|
|
},
|
|
{
|
|
name: "weeks spelled out",
|
|
input: "2weeks",
|
|
expected: 2 * 7 * 24 * time.Hour,
|
|
},
|
|
// Extended units - months
|
|
{
|
|
name: "single month",
|
|
input: "1mo",
|
|
expected: 30 * 24 * time.Hour,
|
|
},
|
|
{
|
|
name: "multiple months",
|
|
input: "6mo",
|
|
expected: 6 * 30 * 24 * time.Hour,
|
|
},
|
|
{
|
|
name: "months spelled out",
|
|
input: "3months",
|
|
expected: 3 * 30 * 24 * time.Hour,
|
|
},
|
|
// Extended units - years
|
|
{
|
|
name: "single year",
|
|
input: "1y",
|
|
expected: 365 * 24 * time.Hour,
|
|
},
|
|
{
|
|
name: "multiple years",
|
|
input: "2y",
|
|
expected: 2 * 365 * 24 * time.Hour,
|
|
},
|
|
{
|
|
name: "years spelled out",
|
|
input: "1year",
|
|
expected: 365 * 24 * time.Hour,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestParseDurationCombinedAndErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runParseDurationCases(t, []parseDurationCase{
|
|
// Combined extended units
|
|
{
|
|
name: "weeks and days",
|
|
input: "2w3d",
|
|
expected: 2*7*24*time.Hour + 3*24*time.Hour,
|
|
},
|
|
{
|
|
name: "years and months",
|
|
input: "1y6mo",
|
|
expected: 365*24*time.Hour + 6*30*24*time.Hour,
|
|
},
|
|
{
|
|
name: "days and hours",
|
|
input: "1d12h",
|
|
expected: 24*time.Hour + 12*time.Hour,
|
|
},
|
|
{
|
|
name: "complex combination",
|
|
input: "1y2mo3w4d5h6m7s",
|
|
expected: 365*24*time.Hour + 2*30*24*time.Hour +
|
|
3*7*24*time.Hour + 4*24*time.Hour +
|
|
5*time.Hour + 6*time.Minute + 7*time.Second,
|
|
},
|
|
{
|
|
name: "with spaces",
|
|
input: "1d 12h 30m",
|
|
expected: 24*time.Hour + 12*time.Hour + 30*time.Minute,
|
|
},
|
|
// Edge cases
|
|
{
|
|
name: "zero duration",
|
|
input: "0s",
|
|
expected: 0,
|
|
},
|
|
{
|
|
name: "large duration",
|
|
input: "10y",
|
|
expected: 10 * 365 * 24 * time.Hour,
|
|
},
|
|
// Error cases
|
|
{
|
|
name: "empty string",
|
|
input: "",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid format",
|
|
input: "abc",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "unknown unit",
|
|
input: "5x",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid number",
|
|
input: "xyzd",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "negative not supported",
|
|
input: "-5d",
|
|
wantErr: true,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestParseDurationSpecialCases(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Test that standard Go durations work exactly as expected
|
|
standardDurations := []string{
|
|
"300ms",
|
|
"1.5h",
|
|
"2h45m",
|
|
"72h",
|
|
"1us",
|
|
"1µs",
|
|
"1ns",
|
|
}
|
|
|
|
for _, d := range standardDurations {
|
|
expected, err := time.ParseDuration(d)
|
|
require.NoError(t, err)
|
|
|
|
got, err := parseDuration(d)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expected, got, "standard duration %q should parse identically", d)
|
|
}
|
|
}
|
|
|
|
func TestParseDurationRealWorldExamples(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Test real-world snapshot purge scenarios
|
|
tests := []struct {
|
|
description string
|
|
input string
|
|
olderThan time.Duration
|
|
}{
|
|
{
|
|
description: "keep snapshots from last 30 days",
|
|
input: "30d",
|
|
olderThan: 30 * 24 * time.Hour,
|
|
},
|
|
{
|
|
description: "keep snapshots from last 6 months",
|
|
input: "6mo",
|
|
olderThan: 6 * 30 * 24 * time.Hour,
|
|
},
|
|
{
|
|
description: "keep snapshots from last year",
|
|
input: "1y",
|
|
olderThan: 365 * 24 * time.Hour,
|
|
},
|
|
{
|
|
description: "keep snapshots from last week and a half",
|
|
input: "1w3d",
|
|
olderThan: 10 * 24 * time.Hour,
|
|
},
|
|
{
|
|
description: "keep snapshots from last 90 days",
|
|
input: "90d",
|
|
olderThan: 90 * 24 * time.Hour,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.description, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := parseDuration(tt.input)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.olderThan, got)
|
|
|
|
// Verify the duration makes sense for snapshot purging
|
|
assert.Greater(t, got, time.Hour,
|
|
"snapshot purge duration should be at least an hour")
|
|
})
|
|
}
|
|
}
|