diff --git a/flag_extended_test.go b/flag_extended_test.go new file mode 100644 index 00000000..b09d60a4 --- /dev/null +++ b/flag_extended_test.go @@ -0,0 +1,357 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pflag + +import ( + "bytes" + "errors" + "io" + "os" + "testing" +) + +func TestFlagSet_HasFlags_And_HasAvailableFlags(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + if f.HasFlags() { + t.Errorf("expected HasFlags to be false on empty FlagSet") + } + if f.HasAvailableFlags() { + t.Errorf("expected HasAvailableFlags to be false on empty FlagSet") + } + + f.String("hidden", "default", "hidden flag") + if err := f.MarkHidden("hidden"); err != nil { + t.Fatalf("unexpected error marking hidden: %v", err) + } + + if !f.HasFlags() { + t.Errorf("expected HasFlags to be true after adding hidden flag") + } + if f.HasAvailableFlags() { + t.Errorf("expected HasAvailableFlags to be false when only hidden flag exists") + } + + f.String("visible", "default", "visible flag") + if !f.HasAvailableFlags() { + t.Errorf("expected HasAvailableFlags to be true after adding visible flag") + } +} + +func TestFlagSet_MarkDeprecated_Errors(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + if err := f.MarkDeprecated("nonexistent", "deprecated message"); err == nil { + t.Errorf("expected error marking non-existent flag as deprecated") + } + if err := f.MarkShorthandDeprecated("nonexistent", "deprecated shorthand"); err == nil { + t.Errorf("expected error marking non-existent shorthand as deprecated") + } + if err := f.MarkHidden("nonexistent"); err == nil { + t.Errorf("expected error marking non-existent flag as hidden") + } +} + +func TestFlagSet_AddFlag_Errors(t *testing.T) { + // Test duplicate flag panic + assertPanic := func(name string, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("%s: expected panic, but did not panic", name) + } + }() + f() + } + + assertPanic("duplicate flag", func() { + f := NewFlagSet("test", ContinueOnError) + f.SetOutput(io.Discard) + f.String("testflag", "val", "usage") + fl := &Flag{ + Name: "testflag", + Usage: "duplicate", + Value: newStringValue("val2", new(string)), + } + f.AddFlag(fl) + }) + + assertPanic("multichar shorthand", func() { + f := NewFlagSet("test", ContinueOnError) + f.SetOutput(io.Discard) + fl := &Flag{ + Name: "testflag2", + Shorthand: "ab", + Usage: "usage", + Value: newStringValue("val", new(string)), + } + f.AddFlag(fl) + }) + + assertPanic("duplicate shorthand", func() { + f := NewFlagSet("test", ContinueOnError) + f.SetOutput(io.Discard) + f.StringVarP(new(string), "flagA", "a", "val", "usage") + f.StringVarP(new(string), "flagB", "a", "val", "usage") + }) +} + +func TestFlagSet_AddFlagSet_Duplicate(t *testing.T) { + f1 := NewFlagSet("f1", ContinueOnError) + f1.String("shared", "val1", "desc1") + f1.AddFlagSet(nil) // should safely return + + f2 := NewFlagSet("f2", ContinueOnError) + f2.String("shared", "val2", "desc2") + f2.String("unique", "val3", "desc3") + + f1.AddFlagSet(f2) // shared is ignored, unique is added + if f1.Lookup("unique") == nil { + t.Errorf("expected unique flag to be added from f2") + } +} + +func TestFlagSet_Lookup_And_Set_Errors(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + f.String("foo", "bar", "usage") + + if flag := f.Lookup("unknown"); flag != nil { + t.Errorf("expected nil for unknown flag lookup") + } + if flag := f.ShorthandLookup("u"); flag != nil { + t.Errorf("expected nil for unknown shorthand lookup") + } + + if err := f.Set("unknown", "value"); err == nil { + t.Errorf("expected error setting unknown flag") + } + + // getFlagType on non-existent flag + _, err := f.getFlagType("unknown", "string", func(s string) (interface{}, error) { return s, nil }) + if err == nil { + t.Errorf("expected error for getFlagType on non-existent flag") + } + + // getFlagType with type mismatch + f.Int("intflag", 10, "int flag") + _, err = f.getFlagType("intflag", "string", func(s string) (interface{}, error) { return s, nil }) + if err == nil { + t.Errorf("expected error for getFlagType with type mismatch") + } + + // getFlagType with conversion error + _, err = f.getFlagType("foo", "int", func(s string) (interface{}, error) { + return nil, errors.New("parse error") + }) + if err == nil { + t.Errorf("expected error for getFlagType when conversion fails") + } +} + +func TestFlagSet_UnquoteUsage_Variations(t *testing.T) { + dummyVal := newStringValue("val", new(string)) + tests := []struct { + usage string + wantName string + wantUsage string + }{ + {"`filepath` path to file", "filepath", "filepath path to file"}, + {"unquoted usage", "string", "unquoted usage"}, + {"`invalid backtick without closing", "string", "`invalid backtick without closing"}, + {"`name`", "name", "name"}, + } + + for _, tc := range tests { + name, usage := UnquoteUsage(&Flag{Usage: tc.usage, Value: dummyVal}) + if name != tc.wantName || usage != tc.wantUsage { + t.Errorf("UnquoteUsage(%q) = (%q, %q); want (%q, %q)", tc.usage, name, usage, tc.wantName, tc.wantUsage) + } + } +} + +func TestFlagSet_Wrap_And_Usage_EdgeCases(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + buf := new(bytes.Buffer) + f.SetOutput(buf) + + f.StringP("longflag", "l", "default", "This is a very long usage description that should wrap around multiple lines properly when printed to the output stream.") + f.StringP("second", "s", "val", "`type` short usage") + f.PrintDefaults() + + if buf.Len() == 0 { + t.Errorf("expected non-empty output from PrintDefaults") + } + + // FlagUsages + usages := f.FlagUsages() + if len(usages) == 0 { + t.Errorf("expected non-empty FlagUsages") + } +} + +func TestFlagSet_ParseAll_And_Interspersed(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + f.SetInterspersed(false) + + flagA := f.BoolP("alpha", "a", false, "alpha flag") + err := f.Parse([]string{"arg1", "-a", "arg2"}) + if err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + + if *flagA { + t.Errorf("expected flagA to be false when interspersed is false and argument comes first") + } + if len(f.Args()) != 3 { + t.Errorf("expected 3 non-flag args with interspersed=false, got %v", f.Args()) + } + + // ParseAll with custom fn + f2 := NewFlagSet("test2", ContinueOnError) + f2.String("valid", "default", "usage") + var parsedFlags []string + err = f2.ParseAll([]string{"--valid=customval"}, func(flag *Flag, value string) error { + parsedFlags = append(parsedFlags, flag.Name) + return nil + }) + if err != nil { + t.Fatalf("unexpected ParseAll error: %v", err) + } + if len(parsedFlags) != 1 || parsedFlags[0] != "valid" { + t.Errorf("expected parsedFlags = [valid], got %v", parsedFlags) + } + + // ParseAll returning error + f3 := NewFlagSet("test3", ContinueOnError) + f3.SetOutput(io.Discard) + f3.String("errflag", "default", "usage") + err = f3.ParseAll([]string{"--errflag=val"}, func(flag *Flag, value string) error { + return errors.New("custom error") + }) + if err == nil { + t.Errorf("expected error from ParseAll when fn returns error") + } +} + +func TestPackageLevel_CommandLine_Helpers(t *testing.T) { + // Reset CommandLine for testing package-level functions + oldCommandLine := CommandLine + defer func() { CommandLine = oldCommandLine }() + + CommandLine = NewFlagSet("test_cmd", ContinueOnError) + CommandLine.SetOutput(io.Discard) + + if CommandLine.HasFlags() { + t.Errorf("expected CommandLine.HasFlags() to be false on empty CommandLine") + } + if CommandLine.HasAvailableFlags() { + t.Errorf("expected CommandLine.HasAvailableFlags() to be false on empty CommandLine") + } + + var dummy string + StringVar(&dummy, "dummy", "default", "dummy usage") + StringVarP(&dummy, "dummy2", "d", "default", "dummy usage") + + if !CommandLine.HasFlags() { + t.Errorf("expected CommandLine.HasFlags() to be true") + } + if !CommandLine.HasAvailableFlags() { + t.Errorf("expected CommandLine.HasAvailableFlags() to be true") + } + + if f := Lookup("dummy"); f == nil { + t.Errorf("Lookup('dummy') returned nil") + } + if f := ShorthandLookup("d"); f == nil { + t.Errorf("ShorthandLookup('d') returned nil") + } + + SetInterspersed(true) + + if Parsed() { + t.Errorf("expected Parsed() to be false before parse") + } + + // Var and VarPF on CommandLine + var customVal string + Var(newStringValue("init", &customVal), "custom", "custom usage") + VarP(newStringValue("init2", &customVal), "custom2", "c", "custom usage") + + err := CommandLine.Parse([]string{"--dummy=foo", "pos1", "pos2"}) + if err != nil { + t.Fatalf("CommandLine.Parse failed: %v", err) + } + + if !Parsed() { + t.Errorf("expected Parsed() to be true after parse") + } + + if NFlag() != 1 { + t.Errorf("NFlag() = %d, want 1", NFlag()) + } + if NArg() != 2 { + t.Errorf("NArg() = %d, want 2", NArg()) + } + if len(Args()) != 2 { + t.Errorf("Args() len = %d, want 2", len(Args())) + } + if Arg(0) != "pos1" || Arg(1) != "pos2" { + t.Errorf("Arg(0)=%q, Arg(1)=%q, want pos1, pos2", Arg(0), Arg(1)) + } + if Arg(99) != "" { + t.Errorf("Arg(99) out of bounds want empty, got %q", Arg(99)) + } + + // PrintDefaults & defaultUsage + buf := new(bytes.Buffer) + CommandLine.SetOutput(buf) + PrintDefaults() + if buf.Len() == 0 { + t.Errorf("PrintDefaults output empty") + } + + // ParseAll on CommandLine + CommandLine = NewFlagSet("test_cmd2", ContinueOnError) + CommandLine.SetOutput(io.Discard) + var flagVal string + StringVar(&flagVal, "target", "default", "usage") + var called bool + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + os.Args = []string{"cmd", "--target=custom"} + ParseAll(func(flag *Flag, value string) error { + if flag.Name == "target" && value == "custom" { + called = true + } + return nil + }) + if !called { + t.Errorf("ParseAll callback not called") + } +} + +func TestFlagSet_ArgsLenAtDash(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + f.String("flag", "val", "usage") + + err := f.Parse([]string{"--flag=foo", "arg1", "--", "arg2", "arg3"}) + if err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + + if f.ArgsLenAtDash() != 1 { + t.Errorf("ArgsLenAtDash() = %d, want 1", f.ArgsLenAtDash()) + } + if len(f.Args()) != 3 { + t.Errorf("Args() = %v, want 3 args", f.Args()) + } + + // Output & SetOutput + var b bytes.Buffer + f.SetOutput(&b) + if f.Output() != &b { + t.Errorf("Output() did not return buffer") + } + if f.Name() != "test" { + t.Errorf("Name() = %q, want test", f.Name()) + } +} diff --git a/slice_and_map_extended_test.go b/slice_and_map_extended_test.go new file mode 100644 index 00000000..7f189da1 --- /dev/null +++ b/slice_and_map_extended_test.go @@ -0,0 +1,703 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pflag + +import ( + "io" + "net" + "reflect" + "testing" + "time" +) + +func TestAllSliceTypes_Comprehensive(t *testing.T) { + f := NewFlagSet("test_slices", ContinueOnError) + f.SetOutput(io.Discard) + + var ( + bools []bool + i32s []int32 + i64s []int64 + u32s []uint + f32s []float32 + f64s []float64 + durs []time.Duration + ips []net.IP + ipnets []net.IPNet + ) + + // BoolSlice + f.BoolSliceVar(&bools, "bools", []bool{false}, "usage") + f.BoolSliceVarP(&bools, "bools-p", "a", []bool{false}, "usage") + pBools := f.BoolSlice("bools-ptr", []bool{true}, "usage") + pBoolsp := f.BoolSliceP("bools-ptr-p", "b", []bool{true}, "usage") + + // Int32Slice & Int64Slice + f.Int32SliceVar(&i32s, "i32s", nil, "usage") + f.Int32SliceVarP(&i32s, "i32s-p", "c", nil, "usage") + pI32 := f.Int32Slice("i32-ptr", []int32{1}, "usage") + pI32p := f.Int32SliceP("i32-ptr-p", "d", []int32{1}, "usage") + + f.Int64SliceVar(&i64s, "i64s", nil, "usage") + f.Int64SliceVarP(&i64s, "i64s-p", "e", nil, "usage") + pI64 := f.Int64Slice("i64-ptr", []int64{2}, "usage") + pI64p := f.Int64SliceP("i64-ptr-p", "g", []int64{2}, "usage") + + // UintSlice + f.UintSliceVar(&u32s, "uints", nil, "usage") + f.UintSliceVarP(&u32s, "uints-p", "h", nil, "usage") + pU := f.UintSlice("uint-ptr", []uint{3}, "usage") + pUp := f.UintSliceP("uint-ptr-p", "i", []uint{3}, "usage") + + // Float32Slice & Float64Slice + f.Float32SliceVar(&f32s, "f32s", nil, "usage") + f.Float32SliceVarP(&f32s, "f32s-p", "j", nil, "usage") + pF32 := f.Float32Slice("f32-ptr", []float32{1.5}, "usage") + pF32p := f.Float32SliceP("f32-ptr-p", "k", []float32{1.5}, "usage") + + f.Float64SliceVar(&f64s, "f64s", nil, "usage") + f.Float64SliceVarP(&f64s, "f64s-p", "l", nil, "usage") + pF64 := f.Float64Slice("f64-ptr", []float64{2.5}, "usage") + pF64p := f.Float64SliceP("f64-ptr-p", "m", []float64{2.5}, "usage") + + // DurationSlice + f.DurationSliceVar(&durs, "durs", nil, "usage") + f.DurationSliceVarP(&durs, "durs-p", "n", nil, "usage") + pDurs := f.DurationSlice("durs-ptr", []time.Duration{time.Second}, "usage") + pDursp := f.DurationSliceP("durs-ptr-p", "o", []time.Duration{time.Second}, "usage") + + // IPSlice & IPNetSlice + f.IPSliceVar(&ips, "ips", nil, "usage") + f.IPSliceVarP(&ips, "ips-p", "q", nil, "usage") + pIPs := f.IPSlice("ips-ptr", []net.IP{net.ParseIP("127.0.0.1")}, "usage") + pIPsp := f.IPSliceP("ips-ptr-p", "r", []net.IP{net.ParseIP("127.0.0.1")}, "usage") + + _, defaultNet, _ := net.ParseCIDR("192.168.1.0/24") + f.IPNetSliceVar(&ipnets, "nets", nil, "usage") + f.IPNetSliceVarP(&ipnets, "nets-p", "s", nil, "usage") + pNets := f.IPNetSlice("nets-ptr", []net.IPNet{*defaultNet}, "usage") + pNetsp := f.IPNetSliceP("nets-ptr-p", "u", []net.IPNet{*defaultNet}, "usage") + + args := []string{ + "--bools=true,false,true", + "-b", "false,false", + "--i32s=10,20,30", + "-d", "40,50", + "--i64s=100,200", + "-g", "300", + "--uints=1,2,3", + "-i", "4,5", + "--f32s=1.2,3.4", + "-k", "5.6", + "--f64s=10.1,20.2", + "-m", "30.3", + "--durs=1s,2m,3h", + "-o", "4h", + "--ips=1.1.1.1,8.8.8.8", + "-r", "10.0.0.1", + "--nets=10.0.0.0/8,172.16.0.0/12", + "-u", "192.168.0.0/16", + } + + if err := f.Parse(args); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + + // Verify values and getters + bVal, err := f.GetBoolSlice("bools") + if err != nil || len(bVal) != 3 || !bVal[0] || bVal[1] || !bVal[2] { + t.Errorf("GetBoolSlice failed: %v, %v", bVal, err) + } + if len(*pBoolsp) != 2 || (*pBoolsp)[0] || (*pBoolsp)[1] { + t.Errorf("pBoolsp mismatch: %v", *pBoolsp) + } + + i32Val, err := f.GetInt32Slice("i32s") + if err != nil || len(i32Val) != 3 || i32Val[0] != 10 { + t.Errorf("GetInt32Slice failed: %v, %v", i32Val, err) + } + + i64Val, err := f.GetInt64Slice("i64s") + if err != nil || len(i64Val) != 2 || i64Val[1] != 200 { + t.Errorf("GetInt64Slice failed: %v, %v", i64Val, err) + } + + uVal, err := f.GetUintSlice("uints") + if err != nil || len(uVal) != 3 || uVal[2] != 3 { + t.Errorf("GetUintSlice failed: %v, %v", uVal, err) + } + + f32Val, err := f.GetFloat32Slice("f32s") + if err != nil || len(f32Val) != 2 { + t.Errorf("GetFloat32Slice failed: %v, %v", f32Val, err) + } + + f64Val, err := f.GetFloat64Slice("f64s") + if err != nil || len(f64Val) != 2 { + t.Errorf("GetFloat64Slice failed: %v, %v", f64Val, err) + } + + durVal, err := f.GetDurationSlice("durs") + if err != nil || len(durVal) != 3 || durVal[2] != 3*time.Hour { + t.Errorf("GetDurationSlice failed: %v, %v", durVal, err) + } + + ipVal, err := f.GetIPSlice("ips") + if err != nil || len(ipVal) != 2 || ipVal[0].String() != "1.1.1.1" { + t.Errorf("GetIPSlice failed: %v, %v", ipVal, err) + } + + netVal, err := f.GetIPNetSlice("nets") + if err != nil || len(netVal) != 2 || netVal[0].String() != "10.0.0.0/8" { + t.Errorf("GetIPNetSlice failed: %v, %v", netVal, err) + } + + // Verify pointers + if len(*pBools) != 1 || len(*pI32) != 1 || len(*pI32p) != 2 || len(*pI64) != 1 || len(*pI64p) != 1 || len(*pU) != 1 || len(*pUp) != 2 || len(*pF32) != 1 || len(*pF32p) != 1 || len(*pF64) != 1 || len(*pF64p) != 1 || len(*pDurs) != 1 || len(*pDursp) != 1 || len(*pIPs) != 1 || len(*pIPsp) != 1 || len(*pNets) != 1 || len(*pNetsp) != 1 { + t.Errorf("pointer slice lengths mismatch") + } + + // Test Append, Replace, and GetSlice on SliceValue + testSliceMethods(t) + + // Error tests on invalid slice items + sliceErrTests := []struct { + name string + val string + }{ + {"bools", "not_a_bool"}, + {"i32s", "not_an_int"}, + {"i64s", "not_an_int"}, + {"uints", "-1"}, + {"f32s", "not_a_float"}, + {"f64s", "not_a_float"}, + {"durs", "not_a_duration"}, + {"ips", "not.an.ip.address.really"}, + {"nets", "not_a_cidr"}, + } + + for _, st := range sliceErrTests { + if err := f.Set(st.name, st.val); err == nil { + t.Errorf("expected error setting %s to %s", st.name, st.val) + } + } +} + +func testSliceMethods(t *testing.T) { + // boolSliceValue + var bs []bool + bv := newBoolSliceValue([]bool{true}, &bs) + if err := bv.Append("false"); err != nil { + t.Fatalf("bv.Append failed: %v", err) + } + if len(bs) != 2 || bs[1] != false { + t.Errorf("bv.Append unexpected bs: %v", bs) + } + if err := bv.Replace([]string{"true", "true"}); err != nil { + t.Fatalf("bv.Replace failed: %v", err) + } + if slice := bv.GetSlice(); len(slice) != 2 || slice[0] != "true" { + t.Errorf("bv.GetSlice unexpected: %v", slice) + } + if err := bv.Append("invalid"); err == nil { + t.Errorf("expected error from bv.Append('invalid')") + } + if err := bv.Replace([]string{"invalid"}); err == nil { + t.Errorf("expected error from bv.Replace(['invalid'])") + } + + // durationSliceValue + var ds []time.Duration + dv := newDurationSliceValue([]time.Duration{time.Second}, &ds) + _ = dv.Append("1m") + if len(ds) != 2 || ds[1] != time.Minute { + t.Errorf("dv.Append unexpected: %v", ds) + } + _ = dv.Replace([]string{"2s", "3s"}) + if slice := dv.GetSlice(); len(slice) != 2 || slice[0] != "2s" { + t.Errorf("dv.GetSlice unexpected: %v", slice) + } + if err := dv.Append("invalid"); err == nil { + t.Errorf("expected error from dv.Append('invalid')") + } + if err := dv.Replace([]string{"invalid"}); err == nil { + t.Errorf("expected error from dv.Replace(['invalid'])") + } + + // float32SliceValue & float64SliceValue + var f32s []float32 + f32v := newFloat32SliceValue(nil, &f32s) + _ = f32v.Append("1.23") + _ = f32v.Replace([]string{"4.56"}) + _ = f32v.GetSlice() + if err := f32v.Append("invalid"); err == nil { + t.Errorf("expected err f32v.Append") + } + if err := f32v.Replace([]string{"invalid"}); err == nil { + t.Errorf("expected err f32v.Replace") + } + + var f64s []float64 + f64v := newFloat64SliceValue(nil, &f64s) + _ = f64v.Append("7.89") + _ = f64v.Replace([]string{"0.12"}) + _ = f64v.GetSlice() + if err := f64v.Append("invalid"); err == nil { + t.Errorf("expected err f64v.Append") + } + if err := f64v.Replace([]string{"invalid"}); err == nil { + t.Errorf("expected err f64v.Replace") + } + + // int32SliceValue & int64SliceValue & uintSliceValue + var i32s []int32 + i32v := newInt32SliceValue(nil, &i32s) + _ = i32v.Append("10") + _ = i32v.Replace([]string{"20"}) + _ = i32v.GetSlice() + if err := i32v.Append("invalid"); err == nil { + t.Errorf("expected err i32v.Append") + } + if err := i32v.Replace([]string{"invalid"}); err == nil { + t.Errorf("expected err i32v.Replace") + } + + var i64s []int64 + i64v := newInt64SliceValue(nil, &i64s) + _ = i64v.Append("100") + _ = i64v.Replace([]string{"200"}) + _ = i64v.GetSlice() + if err := i64v.Append("invalid"); err == nil { + t.Errorf("expected err i64v.Append") + } + if err := i64v.Replace([]string{"invalid"}); err == nil { + t.Errorf("expected err i64v.Replace") + } + + var us []uint + uv := newUintSliceValue(nil, &us) + _ = uv.Append("300") + _ = uv.Replace([]string{"400"}) + _ = uv.GetSlice() + if err := uv.Append("invalid"); err == nil { + t.Errorf("expected err uv.Append") + } + if err := uv.Replace([]string{"invalid"}); err == nil { + t.Errorf("expected err uv.Replace") + } + + // ipSliceValue + var ips []net.IP + ipv := newIPSliceValue(nil, &ips) + _ = ipv.Append("1.1.1.1") + _ = ipv.Replace([]string{"2.2.2.2"}) + _ = ipv.GetSlice() + if err := ipv.Append("invalid"); err == nil { + t.Errorf("expected err ipv.Append") + } + if err := ipv.Replace([]string{"invalid"}); err == nil { + t.Errorf("expected err ipv.Replace") + } + + // ipNetSliceValue + var ipnets []net.IPNet + netv := newIPNetSliceValue(nil, &ipnets) + _ = netv.Set("10.0.0.0/8, 192.168.0.0/16") + _ = netv.String() + _ = netv.Type() + if err := netv.Set("invalid_cidr"); err == nil { + t.Errorf("expected err netv.Set(invalid)") + } +} + +func TestAllMapTypes_Comprehensive(t *testing.T) { + f := NewFlagSet("test_maps", ContinueOnError) + f.SetOutput(io.Discard) + + var ( + s2i map[string]int + s2i6 map[string]int64 + s2s map[string]string + ) + + f.StringToIntVar(&s2i, "s2i", nil, "usage") + f.StringToIntVarP(&s2i, "s2i-p", "a", nil, "usage") + pS2i := f.StringToInt("s2i-ptr", map[string]int{"k": 1}, "usage") + pS2ip := f.StringToIntP("s2i-ptr-p", "b", map[string]int{"k": 1}, "usage") + + f.StringToInt64Var(&s2i6, "s2i6", nil, "usage") + f.StringToInt64VarP(&s2i6, "s2i6-p", "c", nil, "usage") + pS2i6 := f.StringToInt64("s2i6-ptr", map[string]int64{"k6": 2}, "usage") + pS2i6p := f.StringToInt64P("s2i6-ptr-p", "d", map[string]int64{"k6": 2}, "usage") + + f.StringToStringVar(&s2s, "s2s", nil, "usage") + f.StringToStringVarP(&s2s, "s2s-p", "e", nil, "usage") + pS2s := f.StringToString("s2s-ptr", map[string]string{"k": "v"}, "usage") + pS2sp := f.StringToStringP("s2s-ptr-p", "g", map[string]string{"k": "v"}, "usage") + + args := []string{ + "--s2i=apple=5,banana=10", + "-b", "cherry=15", + "--s2i6=x=1000,y=2000", + "-d", "z=3000", + "--s2s=env=prod,region=us", + "-g", "cluster=east", + } + + if err := f.Parse(args); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + + // Verify values and getters + m1, err := f.GetStringToInt("s2i") + if err != nil || m1["apple"] != 5 || m1["banana"] != 10 { + t.Errorf("GetStringToInt failed: %v, %v", m1, err) + } + if (*pS2ip)["cherry"] != 15 || (*pS2i)["k"] != 1 { + t.Errorf("pS2i mismatch") + } + + m2, err := f.GetStringToInt64("s2i6") + if err != nil || m2["x"] != 1000 || m2["y"] != 2000 { + t.Errorf("GetStringToInt64 failed: %v, %v", m2, err) + } + if (*pS2i6p)["z"] != 3000 || (*pS2i6)["k6"] != 2 { + t.Errorf("pS2i6 mismatch") + } + + m3, err := f.GetStringToString("s2s") + if err != nil || m3["env"] != "prod" || m3["region"] != "us" { + t.Errorf("GetStringToString failed: %v, %v", m3, err) + } + if (*pS2sp)["cluster"] != "east" || (*pS2s)["k"] != "v" { + t.Errorf("pS2s mismatch") + } + + // Error paths + if err := f.Set("s2i", "no_equal_sign"); err == nil { + t.Errorf("expected error setting s2i without equal sign") + } + if err := f.Set("s2i", "key=not_an_int"); err == nil { + t.Errorf("expected error setting s2i with non-int value") + } + if err := f.Set("s2i6", "no_equal_sign"); err == nil { + t.Errorf("expected error setting s2i6 without equal sign") + } + if err := f.Set("s2i6", "key=not_an_int"); err == nil { + t.Errorf("expected error setting s2i6 with non-int value") + } + if err := f.Set("s2s", "no_equal_sign"); err == nil { + t.Errorf("expected error setting s2s without equal sign") + } +} + +func TestIP_And_Func_Comprehensive(t *testing.T) { + f := NewFlagSet("test_ip_func", ContinueOnError) + f.SetOutput(io.Discard) + + // IP + var ip net.IP + defaultIP := net.ParseIP("127.0.0.1") + f.IPVar(&ip, "ip", defaultIP, "usage") + f.IPVarP(&ip, "ip-p", "i", defaultIP, "usage") + pIP := f.IP("ip-ptr", defaultIP, "usage") + pIPp := f.IPP("ip-ptr-p", "j", defaultIP, "usage") + + // IPNet + var ipnet net.IPNet + _, defaultCIDR, _ := net.ParseCIDR("10.0.0.0/8") + f.IPNetVar(&ipnet, "cidr", *defaultCIDR, "usage") + f.IPNetVarP(&ipnet, "cidr-p", "c", *defaultCIDR, "usage") + pCIDR := f.IPNet("cidr-ptr", *defaultCIDR, "usage") + pCIDRp := f.IPNetP("cidr-ptr-p", "d", *defaultCIDR, "usage") + + // BoolFunc & Func + var boolFuncVal string + f.BoolFunc("boolfunc", "usage", func(s string) error { + boolFuncVal = s + return nil + }) + f.BoolFuncP("boolfunc-p", "b", "usage", func(s string) error { + boolFuncVal = "p:" + s + return nil + }) + + var funcVal string + f.Func("customfunc", "usage", func(s string) error { + funcVal = s + return nil + }) + f.FuncP("customfunc-p", "F", "usage", func(s string) error { + funcVal = "p:" + s + return nil + }) + + args := []string{ + "--ip=1.1.1.1", + "-j", "8.8.8.8", + "--cidr=172.16.0.0/12", + "-d", "192.168.1.0/24", + "-b", + "-F", "hello_func", + } + + if err := f.Parse(args); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + + if ip.String() != "1.1.1.1" || pIPp.String() != "8.8.8.8" || pIP.String() != "127.0.0.1" { + t.Errorf("IP mismatch: ip=%s, pIPp=%s", ip, pIPp) + } + getIP, err := f.GetIP("ip") + if err != nil || getIP.String() != "1.1.1.1" { + t.Errorf("GetIP failed: %v, %v", getIP, err) + } + + if ipnet.String() != "172.16.0.0/12" || pCIDRp.String() != "192.168.1.0/24" || pCIDR.String() != "10.0.0.0/8" { + t.Errorf("IPNet mismatch: ipnet=%s, pCIDRp=%s", ipnet.String(), pCIDRp.String()) + } + getCIDR, err := f.GetIPNet("cidr") + if err != nil || getCIDR.String() != "172.16.0.0/12" { + t.Errorf("GetIPNet failed: %v, %v", getCIDR, err) + } + + if boolFuncVal != "p:true" { + t.Errorf("boolFuncVal = %q, want p:true", boolFuncVal) + } + if funcVal != "p:hello_func" { + t.Errorf("funcVal = %q, want p:hello_func", funcVal) + } + + // Error paths + if err := f.Set("ip", "invalid.ip"); err == nil { + t.Errorf("expected error setting invalid IP") + } + if err := f.Set("cidr", "invalid.cidr"); err == nil { + t.Errorf("expected error setting invalid CIDR") + } +} + +func TestPackageLevel_SlicesAndMaps(t *testing.T) { + oldCommandLine := CommandLine + defer func() { CommandLine = oldCommandLine }() + + CommandLine = NewFlagSet("cmd_slices_maps", ContinueOnError) + CommandLine.SetOutput(io.Discard) + + var ( + bs []bool + i32s []int32 + i64s []int64 + u []uint + f32s []float32 + f64s []float64 + ds []time.Duration + ips []net.IP + nets []net.IPNet + s2i map[string]int + s2i6 map[string]int64 + s2s map[string]string + ip net.IP + cidr net.IPNet + ) + + BoolSliceVar(&bs, "p-bs", nil, "usage") + BoolSliceVarP(&bs, "p-bs-p", "a", nil, "usage") + _ = BoolSlice("pkg-bs", nil, "usage") + _ = BoolSliceP("pkg-bs-p", "A", nil, "usage") + + Int32SliceVar(&i32s, "p-i32s", nil, "usage") + Int32SliceVarP(&i32s, "p-i32s-p", "b", nil, "usage") + _ = Int32Slice("pkg-i32s", nil, "usage") + _ = Int32SliceP("pkg-i32s-p", "B", nil, "usage") + + Int64SliceVar(&i64s, "p-i64s", nil, "usage") + Int64SliceVarP(&i64s, "p-i64s-p", "c", nil, "usage") + _ = Int64Slice("pkg-i64s", nil, "usage") + _ = Int64SliceP("pkg-i64s-p", "C", nil, "usage") + + UintSliceVar(&u, "p-u", nil, "usage") + UintSliceVarP(&u, "p-u-p", "d", nil, "usage") + _ = UintSlice("pkg-u", nil, "usage") + _ = UintSliceP("pkg-u-p", "D", nil, "usage") + + Float32SliceVar(&f32s, "p-f32s", nil, "usage") + Float32SliceVarP(&f32s, "p-f32s-p", "e", nil, "usage") + _ = Float32Slice("pkg-f32s", nil, "usage") + _ = Float32SliceP("pkg-f32s-p", "E", nil, "usage") + + Float64SliceVar(&f64s, "p-f64s", nil, "usage") + Float64SliceVarP(&f64s, "p-f64s-p", "g", nil, "usage") + _ = Float64Slice("pkg-f64s", nil, "usage") + _ = Float64SliceP("pkg-f64s-p", "G", nil, "usage") + + DurationSliceVar(&ds, "p-ds", nil, "usage") + DurationSliceVarP(&ds, "p-ds-p", "h", nil, "usage") + _ = DurationSlice("pkg-ds", nil, "usage") + _ = DurationSliceP("pkg-ds-p", "H", nil, "usage") + + IPSliceVar(&ips, "p-ips", nil, "usage") + IPSliceVarP(&ips, "p-ips-p", "i", nil, "usage") + _ = IPSlice("pkg-ips", nil, "usage") + _ = IPSliceP("pkg-ips-p", "I", nil, "usage") + + _, defaultCIDR, _ := net.ParseCIDR("10.0.0.0/8") + IPNetSliceVar(&nets, "p-nets", nil, "usage") + IPNetSliceVarP(&nets, "p-nets-p", "j", nil, "usage") + _ = IPNetSlice("pkg-nets", nil, "usage") + _ = IPNetSliceP("pkg-nets-p", "J", nil, "usage") + + StringToIntVar(&s2i, "p-s2i", nil, "usage") + StringToIntVarP(&s2i, "p-s2i-p", "k", nil, "usage") + _ = StringToInt("pkg-s2i", nil, "usage") + _ = StringToIntP("pkg-s2i-p", "K", nil, "usage") + + StringToInt64Var(&s2i6, "p-s2i6", nil, "usage") + StringToInt64VarP(&s2i6, "p-s2i6-p", "l", nil, "usage") + _ = StringToInt64("pkg-s2i6", nil, "usage") + _ = StringToInt64P("pkg-s2i6-p", "L", nil, "usage") + + StringToStringVar(&s2s, "p-s2s", nil, "usage") + StringToStringVarP(&s2s, "p-s2s-p", "m", nil, "usage") + _ = StringToString("pkg-s2s", nil, "usage") + _ = StringToStringP("pkg-s2s-p", "M", nil, "usage") + + IPVar(&ip, "p-ip", net.ParseIP("127.0.0.1"), "usage") + IPVarP(&ip, "p-ip-p", "n", net.ParseIP("127.0.0.1"), "usage") + _ = IP("pkg-ip", net.ParseIP("127.0.0.1"), "usage") + _ = IPP("pkg-ip-p", "N", net.ParseIP("127.0.0.1"), "usage") + + IPNetVar(&cidr, "p-cidr", *defaultCIDR, "usage") + IPNetVarP(&cidr, "p-cidr-p", "o", *defaultCIDR, "usage") + _ = IPNet("pkg-cidr", *defaultCIDR, "usage") + _ = IPNetP("pkg-cidr-p", "O", *defaultCIDR, "usage") + + BoolFunc("pkg-boolfunc", "usage", func(string) error { return nil }) + BoolFuncP("pkg-boolfunc-p", "p", "usage", func(string) error { return nil }) + Func("pkg-func", "usage", func(string) error { return nil }) + FuncP("pkg-func-p", "q", "usage", func(string) error { return nil }) + + err := CommandLine.Parse([]string{ + "-a", "true", + "-b", "100", + "-c", "200", + "-k", "k1=1", + "-l", "k2=2", + "-m", "k3=v3", + }) + if err != nil { + t.Fatalf("CommandLine.Parse failed: %v", err) + } + + if !reflect.DeepEqual(bs, []bool{true}) || !reflect.DeepEqual(i32s, []int32{100}) || !reflect.DeepEqual(i64s, []int64{200}) { + t.Errorf("unexpected package slice values: bs=%v, i32s=%v, i64s=%v", bs, i32s, i64s) + } + if s2i["k1"] != 1 || s2i6["k2"] != 2 || s2s["k3"] != "v3" { + t.Errorf("unexpected package map values: s2i=%v, s2i6=%v, s2s=%v", s2i, s2i6, s2s) + } +} + +func TestRemainingTypes_Comprehensive(t *testing.T) { + f := NewFlagSet("test_remaining", ContinueOnError) + f.SetOutput(io.Discard) + + var ( + is []int + sa []string + ss []string + tip net.IP + ) + + f.IntSliceVar(&is, "is", nil, "usage") + f.IntSliceVarP(&is, "is-p", "i", nil, "usage") + pIs := f.IntSlice("is-ptr", []int{1}, "usage") + pIsp := f.IntSliceP("is-ptr-p", "I", []int{1}, "usage") + + f.StringArrayVar(&sa, "sa", nil, "usage") + f.StringArrayVarP(&sa, "sa-p", "a", nil, "usage") + pSa := f.StringArray("sa-ptr", []string{"a"}, "usage") + pSap := f.StringArrayP("sa-ptr-p", "A", []string{"a"}, "usage") + + f.StringSliceVar(&ss, "ss", nil, "usage") + f.StringSliceVarP(&ss, "ss-p", "s", nil, "usage") + pSs := f.StringSlice("ss-ptr", []string{"s"}, "usage") + pSsp := f.StringSliceP("ss-ptr-p", "S", []string{"s"}, "usage") + + defIP := net.ParseIP("10.0.0.1") + f.TextVar(&tip, "text-ip", &defIP, "usage") + f.TextVarP(&tip, "text-ip-p", "t", &defIP, "usage") + + args := []string{ + "--is=1,2,3", + "-I", "4,5", + "--sa=hello", "--sa=world", + "-A", "foo", + "--ss=cat,dog", + "-S", "bird", + "-t", "192.168.1.1", + } + + if err := f.Parse(args); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + + if len(is) != 3 || len(*pIsp) != 2 || len(*pIs) != 1 { + t.Errorf("IntSlice mismatch") + } + if len(sa) != 2 || len(*pSap) != 1 || len(*pSa) != 1 { + t.Errorf("StringArray mismatch") + } + if len(ss) != 2 || len(*pSsp) != 1 || len(*pSs) != 1 { + t.Errorf("StringSlice mismatch") + } + if tip.String() != "192.168.1.1" { + t.Errorf("tip = %s, want 192.168.1.1", tip.String()) + } + + var outIP net.IP + if err := f.GetText("text-ip-p", &outIP); err != nil || outIP.String() != "192.168.1.1" { + t.Errorf("GetText failed: %v, %v", outIP, err) + } + + // Package-level helpers + oldCommandLine := CommandLine + defer func() { CommandLine = oldCommandLine }() + + CommandLine = NewFlagSet("cmd_rem", ContinueOnError) + CommandLine.SetOutput(io.Discard) + + var ( + pkgIs []int + pkgSa []string + pkgSs []string + pkgTip net.IP + ) + + IntSliceVar(&pkgIs, "p-is", nil, "usage") + IntSliceVarP(&pkgIs, "p-is-p", "i", nil, "usage") + _ = IntSlice("pkg-is", nil, "usage") + _ = IntSliceP("pkg-is-p", "I", nil, "usage") + + StringArrayVar(&pkgSa, "p-sa", nil, "usage") + StringArrayVarP(&pkgSa, "p-sa-p", "a", nil, "usage") + _ = StringArray("pkg-sa", nil, "usage") + _ = StringArrayP("pkg-sa-p", "A", nil, "usage") + + StringSliceVar(&pkgSs, "p-ss", nil, "usage") + StringSliceVarP(&pkgSs, "p-ss-p", "s", nil, "usage") + _ = StringSlice("pkg-ss", nil, "usage") + _ = StringSliceP("pkg-ss-p", "S", nil, "usage") + + TextVar(&pkgTip, "p-text", &defIP, "usage") + TextVarP(&pkgTip, "p-text-p", "t", &defIP, "usage") + + err := CommandLine.Parse([]string{"-i", "10,20", "-a", "one", "-s", "two", "-t", "8.8.8.8"}) + if err != nil { + t.Fatalf("CommandLine.Parse failed: %v", err) + } + if len(pkgIs) != 2 || len(pkgSa) != 1 || len(pkgSs) != 1 || pkgTip.String() != "8.8.8.8" { + t.Errorf("package level remaining types mismatch") + } +} diff --git a/types_extended_test.go b/types_extended_test.go new file mode 100644 index 00000000..282fa6dc --- /dev/null +++ b/types_extended_test.go @@ -0,0 +1,450 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pflag + +import ( + "encoding/hex" + "io" + "net" + "testing" + "time" +) + +func TestAllNumericTypes_FlagSet(t *testing.T) { + f := NewFlagSet("test_num", ContinueOnError) + f.SetOutput(io.Discard) + + var ( + i8 int8 + i16 int16 + i32 int32 + i64 int64 + iv int + u8 uint8 + u16 uint16 + u32 uint32 + u64 uint64 + uv uint + f32 float32 + f64 float64 + dur time.Duration + ) + + // Var & VarP + f.Int8Var(&i8, "int8", 8, "int8 usage") + f.Int8VarP(&i8, "int8-p", "a", 8, "int8 usage") + f.Int16Var(&i16, "int16", 16, "int16 usage") + f.Int16VarP(&i16, "int16-p", "b", 16, "int16 usage") + f.Int32Var(&i32, "int32", 32, "int32 usage") + f.Int32VarP(&i32, "int32-p", "c", 32, "int32 usage") + f.Int64Var(&i64, "int64", 64, "int64 usage") + f.Int64VarP(&i64, "int64-p", "d", 64, "int64 usage") + f.IntVar(&iv, "int", 1, "int usage") + f.IntVarP(&iv, "int-p", "e", 1, "int usage") + + f.Uint8Var(&u8, "uint8", 8, "uint8 usage") + f.Uint8VarP(&u8, "uint8-p", "g", 8, "uint8 usage") + f.Uint16Var(&u16, "uint16", 16, "uint16 usage") + f.Uint16VarP(&u16, "uint16-p", "h", 16, "uint16 usage") + f.Uint32Var(&u32, "uint32", 32, "uint32 usage") + f.Uint32VarP(&u32, "uint32-p", "i", 32, "uint32 usage") + f.Uint64Var(&u64, "uint64", 64, "uint64 usage") + f.Uint64VarP(&u64, "uint64-p", "j", 64, "uint64 usage") + f.UintVar(&uv, "uint", 1, "uint usage") + f.UintVarP(&uv, "uint-p", "k", 1, "uint usage") + + f.Float32Var(&f32, "float32", 3.2, "float32 usage") + f.Float32VarP(&f32, "float32-p", "l", 3.2, "float32 usage") + f.Float64Var(&f64, "float64", 6.4, "float64 usage") + f.Float64VarP(&f64, "float64-p", "m", 6.4, "float64 usage") + + f.DurationVar(&dur, "duration", time.Second, "duration usage") + f.DurationVarP(&dur, "duration-p", "n", time.Second, "duration usage") + + // Pointers + pi8 := f.Int8("int8-ptr", 1, "usage") + pi8p := f.Int8P("int8-ptr-p", "o", 1, "usage") + pi16 := f.Int16("int16-ptr", 2, "usage") + pi16p := f.Int16P("int16-ptr-p", "q", 2, "usage") + pi32 := f.Int32("int32-ptr", 3, "usage") + pi32p := f.Int32P("int32-ptr-p", "r", 3, "usage") + pi64 := f.Int64("int64-ptr", 4, "usage") + pi64p := f.Int64P("int64-ptr-p", "s", 4, "usage") + piv := f.Int("int-ptr", 5, "usage") + pivp := f.IntP("int-ptr-p", "v", 5, "usage") + + pu8 := f.Uint8("uint8-ptr", 6, "usage") + pu8p := f.Uint8P("uint8-ptr-p", "w", 6, "usage") + pu16 := f.Uint16("uint16-ptr", 7, "usage") + pu16p := f.Uint16P("uint16-ptr-p", "x", 7, "usage") + pu32 := f.Uint32("uint32-ptr", 8, "usage") + pu32p := f.Uint32P("uint32-ptr-p", "y", 8, "usage") + pu64 := f.Uint64("uint64-ptr", 9, "usage") + pu64p := f.Uint64P("uint64-ptr-p", "z", 9, "usage") + puv := f.Uint("uint-ptr", 10, "usage") + puvp := f.UintP("uint-ptr-p", "A", 10, "usage") + + pf32 := f.Float32("float32-ptr", 1.1, "usage") + pf32p := f.Float32P("float32-ptr-p", "B", 1.1, "usage") + pf64 := f.Float64("float64-ptr", 2.2, "usage") + pf64p := f.Float64P("float64-ptr-p", "C", 2.2, "usage") + pdur := f.Duration("duration-ptr", time.Minute, "usage") + pdurp := f.DurationP("duration-ptr-p", "D", time.Minute, "usage") + + args := []string{ + "--int8=12", "-b", "24", + "--int32=36", "-d", "48", + "--int=50", + "--uint8=80", "-h", "90", + "--uint32=100", "-j", "110", + "--uint=120", + "--float32=12.5", "-m", "25.5", + "--duration=2h45m", + "-o", "7", "-q", "8", "-r", "9", "-s", "10", "-v", "11", + "-w", "12", "-x", "13", "-y", "14", "-z", "15", "-A", "16", + "-B", "3.3", "-C", "4.4", "-D", "10s", + } + + if err := f.Parse(args); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + + // Verify getters + if v, err := f.GetInt8("int8"); err != nil || v != 12 { + t.Errorf("GetInt8('int8') = %v, %v; want 12, nil", v, err) + } + if v, err := f.GetInt16("int16-p"); err != nil || v != 24 { + t.Errorf("GetInt16('int16-p') = %v, %v; want 24, nil", v, err) + } + if v, err := f.GetInt32("int32"); err != nil || v != 36 { + t.Errorf("GetInt32('int32') = %v, %v; want 36, nil", v, err) + } + if v, err := f.GetInt64("int64-p"); err != nil || v != 48 { + t.Errorf("GetInt64('int64-p') = %v, %v; want 48, nil", v, err) + } + if v, err := f.GetInt("int"); err != nil || v != 50 { + t.Errorf("GetInt('int') = %v, %v; want 50, nil", v, err) + } + + if v, err := f.GetUint8("uint8"); err != nil || v != 80 { + t.Errorf("GetUint8('uint8') = %v, %v; want 80, nil", v, err) + } + if v, err := f.GetUint16("uint16-p"); err != nil || v != 90 { + t.Errorf("GetUint16('uint16-p') = %v, %v; want 90, nil", v, err) + } + if v, err := f.GetUint32("uint32"); err != nil || v != 100 { + t.Errorf("GetUint32('uint32') = %v, %v; want 100, nil", v, err) + } + if v, err := f.GetUint64("uint64-p"); err != nil || v != 110 { + t.Errorf("GetUint64('uint64-p') = %v, %v; want 110, nil", v, err) + } + if v, err := f.GetUint("uint"); err != nil || v != 120 { + t.Errorf("GetUint('uint') = %v, %v; want 120, nil", v, err) + } + + if v, err := f.GetFloat32("float32"); err != nil || v != 12.5 { + t.Errorf("GetFloat32('float32') = %v, %v; want 12.5, nil", v, err) + } + if v, err := f.GetFloat64("float64-p"); err != nil || v != 25.5 { + t.Errorf("GetFloat64('float64-p') = %v, %v; want 25.5, nil", v, err) + } + if v, err := f.GetDuration("duration"); err != nil || v != 2*time.Hour+45*time.Minute { + t.Errorf("GetDuration('duration') = %v, %v; want 2h45m, nil", v, err) + } + + // Verify pointer dereferences + if *pi8 != 1 || *pi8p != 7 || *pi16 != 2 || *pi16p != 8 || *pi32 != 3 || *pi32p != 9 || *pi64 != 4 || *pi64p != 10 || *piv != 5 || *pivp != 11 { + t.Errorf("unexpected signed int pointer values") + } + if *pu8 != 6 || *pu8p != 12 || *pu16 != 7 || *pu16p != 13 || *pu32 != 8 || *pu32p != 14 || *pu64 != 9 || *pu64p != 15 || *puv != 10 || *puvp != 16 { + t.Errorf("unexpected unsigned int pointer values") + } + if *pf32 != 1.1 || *pf32p != 3.3 || *pf64 != 2.2 || *pf64p != 4.4 || *pdur != time.Minute || *pdurp != 10*time.Second { + t.Errorf("unexpected float/duration pointer values") + } + + // Test Error paths on setters + errTests := []struct { + name string + val string + }{ + {"int8", "invalid"}, + {"int8", "128"}, // overflow + {"int16", "invalid"}, + {"int32", "invalid"}, + {"int64", "invalid"}, + {"int", "invalid"}, + {"uint8", "invalid"}, + {"uint8", "256"}, // overflow + {"uint16", "invalid"}, + {"uint32", "invalid"}, + {"uint64", "invalid"}, + {"uint", "invalid"}, + {"float32", "invalid"}, + {"float64", "invalid"}, + {"duration", "invalid"}, + } + + for _, et := range errTests { + if err := f.Set(et.name, et.val); err == nil { + t.Errorf("expected error setting %s to %s", et.name, et.val) + } + } +} + +func TestPackageLevel_NumericAndBasic(t *testing.T) { + oldCommandLine := CommandLine + defer func() { CommandLine = oldCommandLine }() + + CommandLine = NewFlagSet("cmd_num", ContinueOnError) + CommandLine.SetOutput(io.Discard) + + var ( + i8 int8 + i16 int16 + i32 int32 + i64 int64 + iv int + u8 uint8 + u16 uint16 + u32 uint32 + u64 uint64 + uv uint + f32 float32 + f64 float64 + dur time.Duration + str string + ) + + Int8Var(&i8, "p-int8", 1, "usage") + Int8VarP(&i8, "p-int8-p", "a", 1, "usage") + Int16Var(&i16, "p-int16", 2, "usage") + Int16VarP(&i16, "p-int16-p", "b", 2, "usage") + Int32Var(&i32, "p-int32", 3, "usage") + Int32VarP(&i32, "p-int32-p", "c", 3, "usage") + Int64Var(&i64, "p-int64", 4, "usage") + Int64VarP(&i64, "p-int64-p", "d", 4, "usage") + IntVar(&iv, "p-int", 5, "usage") + IntVarP(&iv, "p-int-p", "e", 5, "usage") + + Uint8Var(&u8, "p-uint8", 6, "usage") + Uint8VarP(&u8, "p-uint8-p", "f", 6, "usage") + Uint16Var(&u16, "p-uint16", 7, "usage") + Uint16VarP(&u16, "p-uint16-p", "g", 7, "usage") + Uint32Var(&u32, "p-uint32", 8, "usage") + Uint32VarP(&u32, "p-uint32-p", "h", 8, "usage") + Uint64Var(&u64, "p-uint64", 9, "usage") + Uint64VarP(&u64, "p-uint64-p", "i", 9, "usage") + UintVar(&uv, "p-uint", 10, "usage") + UintVarP(&uv, "p-uint-p", "j", 10, "usage") + + Float32Var(&f32, "p-float32", 1.5, "usage") + Float32VarP(&f32, "p-float32-p", "k", 1.5, "usage") + Float64Var(&f64, "p-float64", 2.5, "usage") + Float64VarP(&f64, "p-float64-p", "l", 2.5, "usage") + DurationVar(&dur, "p-duration", time.Second, "usage") + DurationVarP(&dur, "p-duration-p", "m", time.Second, "usage") + StringVar(&str, "p-str", "def", "usage") + StringVarP(&str, "p-str-p", "n", "def", "usage") + + // Package level pointers + _ = Int8("pkg-int8", 1, "usage") + _ = Int8P("pkg-int8-p", "o", 1, "usage") + _ = Int16("pkg-int16", 2, "usage") + _ = Int16P("pkg-int16-p", "p", 2, "usage") + _ = Int32("pkg-int32", 3, "usage") + _ = Int32P("pkg-int32-p", "q", 3, "usage") + _ = Int64("pkg-int64", 4, "usage") + _ = Int64P("pkg-int64-p", "r", 4, "usage") + _ = Int("pkg-int", 5, "usage") + _ = IntP("pkg-int-p", "s", 5, "usage") + + _ = Uint8("pkg-uint8", 6, "usage") + _ = Uint8P("pkg-uint8-p", "t", 6, "usage") + _ = Uint16("pkg-uint16", 7, "usage") + _ = Uint16P("pkg-uint16-p", "u", 7, "usage") + _ = Uint32("pkg-uint32", 8, "usage") + _ = Uint32P("pkg-uint32-p", "v", 8, "usage") + _ = Uint64("pkg-uint64", 9, "usage") + _ = Uint64P("pkg-uint64-p", "w", 9, "usage") + _ = Uint("pkg-uint", 10, "usage") + _ = UintP("pkg-uint-p", "x", 10, "usage") + + _ = Float32("pkg-float32", 1.1, "usage") + _ = Float32P("pkg-float32-p", "y", 1.1, "usage") + _ = Float64("pkg-float64", 2.2, "usage") + _ = Float64P("pkg-float64-p", "z", 2.2, "usage") + _ = Duration("pkg-duration", time.Minute, "usage") + _ = DurationP("pkg-duration-p", "A", time.Minute, "usage") + _ = String("pkg-string", "val", "usage") + _ = StringP("pkg-string-p", "B", "val", "usage") + + err := CommandLine.Parse([]string{"--p-int8=100", "-n", "hello"}) + if err != nil { + t.Fatalf("CommandLine.Parse failed: %v", err) + } + if i8 != 100 || str != "hello" { + t.Errorf("unexpected parsed values: i8=%d, str=%s", i8, str) + } +} + +func TestSpecialTypes_FlagSet_And_PackageLevel(t *testing.T) { + f := NewFlagSet("test_special", ContinueOnError) + f.SetOutput(io.Discard) + + // Count + var cnt int + f.CountVar(&cnt, "verbose", "verbose count") + f.CountVarP(&cnt, "verbose-p", "v", "verbose count") + pcnt := f.Count("cnt", "count ptr") + pcntp := f.CountP("cnt-p", "c", "count ptr") + + // BytesHex & BytesBase64 + var bHex, bB64 []byte + f.BytesHexVar(&bHex, "hex", []byte{0x01}, "hex usage") + f.BytesHexVarP(&bHex, "hex-p", "H", []byte{0x01}, "hex usage") + pHex := f.BytesHex("hex-ptr", []byte{0x02}, "usage") + pHexp := f.BytesHexP("hex-ptr-p", "I", []byte{0x02}, "usage") + + f.BytesBase64Var(&bB64, "b64", []byte("a"), "b64 usage") + f.BytesBase64VarP(&bB64, "b64-p", "J", []byte("a"), "b64 usage") + pB64 := f.BytesBase64("b64-ptr", []byte("b"), "usage") + pB64p := f.BytesBase64P("b64-ptr-p", "K", []byte("b"), "usage") + + // IPMask + var mask net.IPMask + defaultMask := net.IPv4Mask(255, 255, 255, 0) + f.IPMaskVar(&mask, "mask", defaultMask, "mask usage") + f.IPMaskVarP(&mask, "mask-p", "M", defaultMask, "mask usage") + pMask := f.IPMask("mask-ptr", defaultMask, "usage") + pMaskp := f.IPMaskP("mask-ptr-p", "N", defaultMask, "usage") + + // Time + var tm time.Time + formats := []string{time.RFC3339, "2006-01-02"} + defaultTime, _ := time.Parse("2006-01-02", "2026-01-01") + f.TimeVar(&tm, "time", defaultTime, formats, "time usage") + f.TimeVarP(&tm, "time-p", "T", defaultTime, formats, "time usage") + pTime := f.Time("time-ptr", defaultTime, formats, "usage") + pTimep := f.TimeP("time-ptr-p", "U", defaultTime, formats, "usage") + + args := []string{ + "-v", "-v", "-v", + "-c", "-c", + "--hex=deadbeef", + "-I", "cafebabe", + "--b64=aGVsbG8=", + "-K", "d29ybGQ=", + "--mask=255.255.0.0", + "-N", "ffff0000", + "--time=2026-10-04T12:00:00Z", + "-U", "2026-12-31", + } + + if err := f.Parse(args); err != nil { + t.Fatalf("unexpected parse error: %v", err) + } + + // Verify Count + if cnt != 3 || *pcnt != 0 || *pcntp != 2 { + t.Errorf("Count values: cnt=%d, *pcnt=%d, *pcntp=%d", cnt, *pcnt, *pcntp) + } + if v, err := f.GetCount("verbose"); err != nil || v != 3 { + t.Errorf("GetCount = %v, %v", v, err) + } + + // Verify BytesHex + expectedHex, _ := hex.DecodeString("deadbeef") + expectedHexPtr, _ := hex.DecodeString("cafebabe") + if string(bHex) != string(expectedHex) || string(*pHexp) != string(expectedHexPtr) || string(*pHex) != string([]byte{0x02}) { + t.Errorf("BytesHex mismatch") + } + if v, err := f.GetBytesHex("hex"); err != nil || string(v) != string(expectedHex) { + t.Errorf("GetBytesHex = %v, %v", v, err) + } + + // Verify BytesBase64 + if string(bB64) != "hello" || string(*pB64p) != "world" || string(*pB64) != "b" { + t.Errorf("BytesBase64 mismatch") + } + if v, err := f.GetBytesBase64("b64"); err != nil || string(v) != "hello" { + t.Errorf("GetBytesBase64 = %v, %v", v, err) + } + + // Verify IPMask + if mask.String() != "ffff0000" || pMaskp.String() != "ffff0000" || pMask.String() != "ffffff00" { + t.Errorf("IPMask mismatch") + } + if v, err := f.GetIPv4Mask("mask"); err != nil || v.String() != "ffff0000" { + t.Errorf("GetIPv4Mask = %v, %v", v, err) + } + + // Verify Time + if tm.Format(time.RFC3339) != "2026-10-04T12:00:00Z" || pTimep.Format("2006-01-02") != "2026-12-31" || pTime.Format("2006-01-02") != "2026-01-01" { + t.Errorf("Time mismatch") + } + if v, err := f.GetTime("time"); err != nil || v.Format(time.RFC3339) != "2026-10-04T12:00:00Z" { + t.Errorf("GetTime = %v, %v", v, err) + } + + // Error paths + if err := f.Set("hex", "not-a-hex"); err == nil { + t.Errorf("expected error setting invalid hex") + } + if err := f.Set("b64", "not-a-base64!!"); err == nil { + t.Errorf("expected error setting invalid base64") + } + if err := f.Set("mask", "invalid.mask.val"); err == nil { + t.Errorf("expected error setting invalid mask") + } + if err := f.Set("time", "invalid-time"); err == nil { + t.Errorf("expected error setting invalid time") + } + + // Package-level helpers + oldCommandLine := CommandLine + defer func() { CommandLine = oldCommandLine }() + + CommandLine = NewFlagSet("cmd_special", ContinueOnError) + CommandLine.SetOutput(io.Discard) + + var ( + pkgCount int + pkgHex []byte + pkgB64 []byte + pkgMask net.IPMask + pkgTime time.Time + ) + + CountVar(&pkgCount, "p-count", "usage") + CountVarP(&pkgCount, "p-count-p", "c", "usage") + _ = Count("pkg-cnt", "usage") + _ = CountP("pkg-cnt-p", "C", "usage") + + BytesHexVar(&pkgHex, "p-hex", nil, "usage") + BytesHexVarP(&pkgHex, "p-hex-p", "x", nil, "usage") + _ = BytesHex("pkg-hex", nil, "usage") + _ = BytesHexP("pkg-hex-p", "X", nil, "usage") + + BytesBase64Var(&pkgB64, "p-b64", nil, "usage") + BytesBase64VarP(&pkgB64, "p-b64-p", "b", nil, "usage") + _ = BytesBase64("pkg-b64", nil, "usage") + _ = BytesBase64P("pkg-b64-p", "B", nil, "usage") + + IPMaskVar(&pkgMask, "p-mask", defaultMask, "usage") + IPMaskVarP(&pkgMask, "p-mask-p", "m", defaultMask, "usage") + _ = IPMask("pkg-mask", defaultMask, "usage") + _ = IPMaskP("pkg-mask-p", "M", defaultMask, "usage") + + TimeVar(&pkgTime, "p-time", defaultTime, formats, "usage") + TimeVarP(&pkgTime, "p-time-p", "t", defaultTime, formats, "usage") + _ = Time("pkg-time", defaultTime, formats, "usage") + _ = TimeP("pkg-time-p", "T", defaultTime, formats, "usage") + + _ = CommandLine.Parse([]string{"-c", "-c", "--p-hex=abcd"}) + if pkgCount != 2 || hex.EncodeToString(pkgHex) != "abcd" { + t.Errorf("package level special types mismatch: count=%d, hex=%x", pkgCount, pkgHex) + } +}