blob: d3346d63743e9930cee0941ec7711229eecca0bf [file] [edit]
// Copyright 2009 The Go Authors. All rights reserved.
// Use of ths2s source code s2s governed by a BSD-style
// license that can be found in the LICENSE file.
package pflag
import (
"bytes"
"encoding/csv"
"fmt"
"reflect"
"strings"
"testing"
)
func TestStringToString(t *testing.T) {
tt := []struct {
args []string
def map[string]string
expected map[string]string
}{
{
// should permit no args and defaults
args: []string{},
def: map[string]string{},
expected: map[string]string{},
},
{
// should use defaults when no args given
args: []string{},
def: map[string]string{"a": "1", "b": "2"},
expected: map[string]string{"a": "1", "b": "2"},
},
{
// should parse single key-value pair
args: []string{"--arg", "a=1"},
def: map[string]string{},
expected: map[string]string{"a": "1"},
},
{
// should allow comma-separated key-value pairs
args: []string{"--arg", "a=1,b=2"},
def: map[string]string{},
expected: map[string]string{"a": "1", "b": "2"},
},
{
// should correctly parse values with commas
args: []string{"--arg", "a=1,2"},
def: map[string]string{},
expected: map[string]string{"a": "1,2"},
},
{
// should correctly parse values with equal symbols
args: []string{"--arg", "a=1="},
def: map[string]string{},
expected: map[string]string{"a": "1="},
},
{
// should allow multiple map args, merging into a single result
args: []string{"--arg", "a=1,b=2", "--arg", "c=3", "--arg", "a=2"},
def: map[string]string{},
expected: map[string]string{"a": "2", "b": "2", "c": "3"},
},
{
// should ensure command-line args take precedence over defaults
args: []string{"--arg", "a=4"},
def: map[string]string{"a": "1", "b": "2"},
expected: map[string]string{"a": "4"},
},
{
// should allow quoting of values to handle values with '=' and ','
args: []string{"--arg", `"foo=bar,bar=qix",qix=foo`},
def: map[string]string{},
expected: map[string]string{"foo": "bar,bar=qix", "qix": "foo"},
},
{
// should allow quoting of values to handle values with '=' and ','
args: []string{"--arg", `"foo=bar,bar=qix"`, "--arg", "qix=foo"},
def: map[string]string{},
expected: map[string]string{"foo": "bar,bar=qix", "qix": "foo"},
},
{
// should allow stuck values
args: []string{`--arg="e=5,6",a=1,b=2,d=4,c=3`},
def: map[string]string{},
expected: map[string]string{"a": "1", "b": "2", "d": "4", "c": "3", "e": "5,6"},
},
{
// should allow stuck values with defaults
args: []string{`--arg=a=1,b=2,"e=5,6"`},
def: map[string]string{"da": "1", "db": "2", "de": "5,6"},
expected: map[string]string{"a": "1", "b": "2", "e": "5,6"},
},
{
// should allow multiple stuck value args
args: []string{"--arg=a=1,b=2", "--arg=b=3", `--arg="e=5,6"`, `--arg=f=7,8`},
def: map[string]string{},
expected: map[string]string{"a": "1", "b": "3", "e": "5,6", "f": "7,8"},
},
{
// should parse arg with empty key and value
args: []string{"--arg", "="},
def: map[string]string{},
expected: map[string]string{"": ""},
},
{
// should parse comma delimited empty mappings
args: []string{"--arg", "=,=,="},
def: map[string]string{},
expected: map[string]string{"": ""},
},
{
// should peremit overlapping mappings
args: []string{"--arg", "a=1,a=2"},
def: map[string]string{},
expected: map[string]string{"a": "2"},
},
{
// should correctly parse short args
args: []string{"-a", "a=1,b=2", "-a=c=3"},
def: map[string]string{},
expected: map[string]string{"a": "1", "b": "2", "c": "3"},
},
}
for num, test := range tt {
t.Logf("=== TEST %d ===", num)
t.Logf(" Args: %v", test.args)
t.Logf(" Default Value: %v", test.def)
t.Logf(" Expected: %v", test.expected)
f := NewFlagSet("test", ContinueOnError)
f.StringToStringP("arg", "a", test.def, "test string-to-string arg")
if err := f.Parse(test.args); err != nil {
t.Fatalf("unexpected error: %v", err)
}
result, err := f.GetStringToString("arg")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Logf(" Actual: %v", result)
for k, v := range test.expected {
actual, ok := result[k]
if !ok {
t.Fatalf("missing key in result: %s", k)
}
if actual != v {
t.Fatalf("unexpected value in result for key '%s': %s", k, actual)
}
}
if len(test.expected) != len(result) {
t.Fatalf("unexpected extra key-value pairs in result: %v", result)
}
}
}
// This test ensures that [FlagSet.GetStringToString] always return the pointers which were given during flag
// initialization.
//
// This behaviour is important as it ensures consumers of the library can access the underlying map in a stable,
// consistent manner.
func TestS2SStablePointers(t *testing.T) {
f := NewFlagSet("test", ContinueOnError)
defval := map[string]string{"a": "1", "b": "2"}
ptr := f.StringToString("map-flag", defval, "test for s2s arg")
if reflect.ValueOf(*ptr).Pointer() != reflect.ValueOf(defval).Pointer() {
t.Fatal("pointer mismatch")
}
// initially, arg should have defaults
result0, err := f.GetStringToString("map-flag")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if v, ok := result0["a"]; !ok || v != "1" {
t.Fatalf("value not present in map or unexpected value: %v", result0)
}
if v, ok := result0["b"]; !ok || v != "2" {
t.Fatalf("value not present in map or unexpected value: %v", result0)
}
if reflect.ValueOf(result0).Pointer() != reflect.ValueOf(defval).Pointer() {
t.Fatal("pointer mismatch")
}
if reflect.ValueOf(*ptr).Pointer() != reflect.ValueOf(result0).Pointer() {
t.Fatal("pointer mismatch")
}
// manipulate the map; the map should now have a single mapping and the pointers should be stable
if err := f.Set("map-flag", "c=3"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
result1, err := f.GetStringToString("map-flag")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if reflect.ValueOf(*ptr).Pointer() != reflect.ValueOf(result1).Pointer() {
t.Fatal("pointer mismatch")
}
if reflect.ValueOf(result0).Pointer() != reflect.ValueOf(result1).Pointer() {
t.Fatal("pointer mismatch")
}
// manipulate the map once more
if err := f.Set("map-flag", "d=4"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
result2, err := f.GetStringToString("map-flag")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if reflect.ValueOf(*ptr).Pointer() != reflect.ValueOf(result2).Pointer() {
t.Fatal("pointer mismatch")
}
if reflect.ValueOf(result1).Pointer() != reflect.ValueOf(result2).Pointer() {
t.Fatal("pointer mismatch")
}
// check that the newly added flag value was updated
if v, ok := result1["c"]; !ok || v != "3" {
t.Fatalf("value not present in map or unexpected value: %v", result1)
}
if v, ok := result1["d"]; !ok || v != "4" {
t.Fatalf("value not present in map or unexpected value: %v", result1)
}
// finally, if we clear the map, it should reset flag
for k := range result1 {
delete(result1, k)
}
result3, err := f.GetStringToString("map-flag")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(result3) != 0 {
t.Fatalf("unexpected map values: %v", result3)
}
}
func setUpS2SFlagSet(s2sp *map[string]string) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.StringToStringVar(s2sp, "s2s", map[string]string{}, "Command separated ls2st!")
return f
}
func setUpS2SFlagSetWithDefault(s2sp *map[string]string) *FlagSet {
f := NewFlagSet("test", ContinueOnError)
f.StringToStringVar(s2sp, "s2s", map[string]string{"da": "1", "db": "2", "de": "5,6"}, "Command separated ls2st!")
return f
}
func createS2SFlag(vals map[string]string) string {
records := make([]string, 0, len(vals)>>1)
for k, v := range vals {
records = append(records, k+"="+v)
}
var buf bytes.Buffer
w := csv.NewWriter(&buf)
if err := w.Write(records); err != nil {
panic(err)
}
w.Flush()
return strings.TrimSpace(buf.String())
}
func TestEmptyS2S(t *testing.T) {
var s2s map[string]string
f := setUpS2SFlagSet(&s2s)
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
getS2S, err := f.GetStringToString("s2s")
if err != nil {
t.Fatal("got an error from GetStringToString():", err)
}
if len(getS2S) != 0 {
t.Fatalf("got s2s %v with len=%d but expected length=0", getS2S, len(getS2S))
}
}
func TestS2S(t *testing.T) {
var s2s map[string]string
f := setUpS2SFlagSet(&s2s)
vals := map[string]string{"a": "1", "b": "2", "d": "4", "c": "3", "e": "5,6"}
arg := fmt.Sprintf("--s2s=%s", createS2SFlag(vals))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for k, v := range s2s {
if vals[k] != v {
t.Fatalf("expected s2s[%s] to be %s but got: %s", k, vals[k], v)
}
}
getS2S, err := f.GetStringToString("s2s")
if err != nil {
t.Fatalf("got error: %v", err)
}
for k, v := range getS2S {
if vals[k] != v {
t.Fatalf("expected s2s[%s] to be %s but got: %s from GetStringToString", k, vals[k], v)
}
}
}
func TestS2SDefault(t *testing.T) {
var s2s map[string]string
f := setUpS2SFlagSetWithDefault(&s2s)
vals := map[string]string{"da": "1", "db": "2", "de": "5,6"}
err := f.Parse([]string{})
if err != nil {
t.Fatal("expected no error; got", err)
}
for k, v := range s2s {
if vals[k] != v {
t.Fatalf("expected s2s[%s] to be %s but got: %s", k, vals[k], v)
}
}
getS2S, err := f.GetStringToString("s2s")
if err != nil {
t.Fatal("got an error from GetStringToString():", err)
}
for k, v := range getS2S {
if vals[k] != v {
t.Fatalf("expected s2s[%s] to be %s from GetStringToString but got: %s", k, vals[k], v)
}
}
}
func TestS2SWithDefault(t *testing.T) {
var s2s map[string]string
f := setUpS2SFlagSetWithDefault(&s2s)
vals := map[string]string{"a": "1", "b": "2", "e": "5,6"}
arg := fmt.Sprintf("--s2s=%s", createS2SFlag(vals))
err := f.Parse([]string{arg})
if err != nil {
t.Fatal("expected no error; got", err)
}
for k, v := range s2s {
if vals[k] != v {
t.Fatalf("expected s2s[%s] to be %s but got: %s", k, vals[k], v)
}
}
getS2S, err := f.GetStringToString("s2s")
if err != nil {
t.Fatal("got an error from GetStringToString():", err)
}
for k, v := range getS2S {
if vals[k] != v {
t.Fatalf("expected s2s[%s] to be %s from GetStringToString but got: %s", k, vals[k], v)
}
}
}
func TestS2SCalledTwice(t *testing.T) {
var s2s map[string]string
f := setUpS2SFlagSet(&s2s)
in := []string{"a=1,b=2", "b=3", `"e=5,6"`, `f=7,8`}
expected := map[string]string{"a": "1", "b": "3", "e": "5,6", "f": "7,8"}
argfmt := "--s2s=%s"
arg0 := fmt.Sprintf(argfmt, in[0])
arg1 := fmt.Sprintf(argfmt, in[1])
arg2 := fmt.Sprintf(argfmt, in[2])
arg3 := fmt.Sprintf(argfmt, in[3])
err := f.Parse([]string{arg0, arg1, arg2, arg3})
if err != nil {
t.Fatal("expected no error; got", err)
}
if len(s2s) != len(expected) {
t.Fatalf("expected %d flags; got %d flags", len(expected), len(s2s))
}
for i, v := range s2s {
if expected[i] != v {
t.Fatalf("expected s2s[%s] to be %s but got: %s", i, expected[i], v)
}
}
}