From 62a52f405ba5613e0426cf155f178dcdae858248 Mon Sep 17 00:00:00 2001 From: Suliman Abdulrazzaq Date: Wed, 23 Sep 2026 11:48:56 +0300 Subject: [PATCH] Print StringToInt and StringToInt64 defaults in sorted order #365 sorted the keys in stringToStringValue.String() so that the "(default [...])" text in FlagUsages no longer changes from run to run (#362). The stringToInt and stringToInt64 values still range over the map directly, so a flag with more than one default key prints them in Go's randomized map order and --help output, or docs generated from it, differs between runs. Sort the keys in both String() methods the same way. --- string_to_int.go | 12 +++++++++--- string_to_int64.go | 12 +++++++++--- string_to_int64_test.go | 18 ++++++++++++++++++ string_to_int_test.go | 18 ++++++++++++++++++ 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/string_to_int.go b/string_to_int.go index 5ceda396..155c5575 100644 --- a/string_to_int.go +++ b/string_to_int.go @@ -3,6 +3,7 @@ package pflag import ( "bytes" "fmt" + "sort" "strconv" "strings" ) @@ -51,16 +52,21 @@ func (s *stringToIntValue) Type() string { } func (s *stringToIntValue) String() string { + keys := make([]string, 0, len(*s.value)) + for k := range *s.value { + keys = append(keys, k) + } + sort.Strings(keys) + var buf bytes.Buffer - i := 0 - for k, v := range *s.value { + for i, k := range keys { if i > 0 { buf.WriteRune(',') } + v := (*s.value)[k] buf.WriteString(k) buf.WriteRune('=') buf.WriteString(strconv.Itoa(v)) - i++ } return "[" + buf.String() + "]" } diff --git a/string_to_int64.go b/string_to_int64.go index a807a04a..bbec1755 100644 --- a/string_to_int64.go +++ b/string_to_int64.go @@ -3,6 +3,7 @@ package pflag import ( "bytes" "fmt" + "sort" "strconv" "strings" ) @@ -51,16 +52,21 @@ func (s *stringToInt64Value) Type() string { } func (s *stringToInt64Value) String() string { + keys := make([]string, 0, len(*s.value)) + for k := range *s.value { + keys = append(keys, k) + } + sort.Strings(keys) + var buf bytes.Buffer - i := 0 - for k, v := range *s.value { + for i, k := range keys { if i > 0 { buf.WriteRune(',') } + v := (*s.value)[k] buf.WriteString(k) buf.WriteRune('=') buf.WriteString(strconv.FormatInt(v, 10)) - i++ } return "[" + buf.String() + "]" } diff --git a/string_to_int64_test.go b/string_to_int64_test.go index 2b3f2989..1b0960bb 100644 --- a/string_to_int64_test.go +++ b/string_to_int64_test.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" "strconv" + "strings" "testing" ) @@ -154,3 +155,20 @@ func TestS2I64CalledTwice(t *testing.T) { } } } + +func TestS2I64UsageSorted(t *testing.T) { + var s2i map[string]int64 + f := NewFlagSet("test", ContinueOnError) + f.StringToInt64Var(&s2i, "s2i", map[string]int64{"c": 3, "a": 1, "d": 4, "b": 2}, "usage") + + want := "[a=1,b=2,c=3,d=4]" + // Map iteration order is randomized, so check repeatedly. + for i := 0; i < 100; i++ { + if got := f.Lookup("s2i").Value.String(); got != want { + t.Fatalf("expected String() to be %q but got: %q", want, got) + } + if usage := f.FlagUsages(); !strings.Contains(usage, "(default "+want+")") { + t.Fatalf("expected usage to contain default %s but got: %q", want, usage) + } + } +} diff --git a/string_to_int_test.go b/string_to_int_test.go index b60bbafb..3b9909b3 100644 --- a/string_to_int_test.go +++ b/string_to_int_test.go @@ -8,6 +8,7 @@ import ( "bytes" "fmt" "strconv" + "strings" "testing" ) @@ -154,3 +155,20 @@ func TestS2ICalledTwice(t *testing.T) { } } } + +func TestS2IUsageSorted(t *testing.T) { + var s2i map[string]int + f := NewFlagSet("test", ContinueOnError) + f.StringToIntVar(&s2i, "s2i", map[string]int{"c": 3, "a": 1, "d": 4, "b": 2}, "usage") + + want := "[a=1,b=2,c=3,d=4]" + // Map iteration order is randomized, so check repeatedly. + for i := 0; i < 100; i++ { + if got := f.Lookup("s2i").Value.String(); got != want { + t.Fatalf("expected String() to be %q but got: %q", want, got) + } + if usage := f.FlagUsages(); !strings.Contains(usage, "(default "+want+")") { + t.Fatalf("expected usage to contain default %s but got: %q", want, usage) + } + } +}