code.cestus.io/tools/fabricator@v0.4.3/pkg/ff/ffpflag/pflag_test.go (about)

     1  package ffpflag_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"reflect"
     8  	"strings"
     9  	"testing"
    10  
    11  	"code.cestus.io/tools/fabricator/pkg/ff"
    12  	"code.cestus.io/tools/fabricator/pkg/ff/ffpflag"
    13  	"code.cestus.io/tools/fabricator/pkg/ff/fftoml"
    14  	"code.cestus.io/tools/fabricator/pkg/ff/ffyaml"
    15  	"github.com/spf13/pflag"
    16  )
    17  
    18  type Vars struct {
    19  	S    string
    20  	B    bool
    21  	F    float32
    22  	I    []int32
    23  	Args []string
    24  
    25  	ParseError           error
    26  	WantParseErrorIs     error
    27  	WantParseErrorString string
    28  }
    29  
    30  func Pair() (*pflag.FlagSet, *Vars) {
    31  	fs := pflag.NewFlagSet("ffpflag_test", pflag.ContinueOnError)
    32  
    33  	var v Vars
    34  	fs.StringVarP(&v.S, "string", "s", "", "a string value")
    35  	fs.BoolVarP(&v.B, "bool", "b", false, "a bool value")
    36  	fs.Float32Var(&v.F, "f", 0., "a float32 value")
    37  	fs.Int32SliceVarP(&v.I, "int32", "i", nil, "collection of int32 (repeatable)")
    38  
    39  	return fs, &v
    40  }
    41  
    42  func Compare(want, have *Vars) error {
    43  	// Normalize args.
    44  	if want.Args == nil {
    45  		want.Args = []string{}
    46  	}
    47  	if have.Args == nil {
    48  		have.Args = []string{}
    49  	}
    50  
    51  	if want.WantParseErrorIs != nil || want.WantParseErrorString != "" {
    52  		if want.WantParseErrorIs != nil && have.ParseError == nil {
    53  			return fmt.Errorf("want error (%v), have none", want.WantParseErrorIs)
    54  		}
    55  
    56  		if want.WantParseErrorString != "" && have.ParseError == nil {
    57  			return fmt.Errorf("want error (%q), have none", want.WantParseErrorString)
    58  		}
    59  
    60  		if want.WantParseErrorIs == nil && want.WantParseErrorString == "" && have.ParseError != nil {
    61  			return fmt.Errorf("want clean parse, have error (%v)", have.ParseError)
    62  		}
    63  
    64  		if want.WantParseErrorIs != nil && have.ParseError != nil && !errors.Is(have.ParseError, want.WantParseErrorIs) {
    65  			return fmt.Errorf("want wrapped error (%#+v), have error (%#+v)", want.WantParseErrorIs, have.ParseError)
    66  		}
    67  
    68  		if want.WantParseErrorString != "" && have.ParseError != nil && !strings.Contains(have.ParseError.Error(), want.WantParseErrorString) {
    69  			return fmt.Errorf("want error string (%q), have error (%v)", want.WantParseErrorString, have.ParseError)
    70  		}
    71  
    72  		return nil
    73  	} else {
    74  		if have.ParseError != nil {
    75  			return fmt.Errorf("want no parse error, have error: %v", have.ParseError)
    76  		}
    77  	}
    78  
    79  	if want.S != have.S {
    80  		return fmt.Errorf("var S: want %q, have %q", want.S, have.S)
    81  	}
    82  	if want.B != have.B {
    83  		return fmt.Errorf("var B: want %v, have %v", want.B, have.B)
    84  	}
    85  	if want.F != have.F {
    86  		return fmt.Errorf("var F: want %v, have %v", want.F, have.F)
    87  	}
    88  	if !reflect.DeepEqual(want.I, have.I) {
    89  		return fmt.Errorf("var I: want %v, have %v", want.I, have.I)
    90  	}
    91  	if !reflect.DeepEqual(want.Args, have.Args) {
    92  		return fmt.Errorf("var Args: want %v, have %v", want.Args, have.Args)
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  func TestBasics(t *testing.T) {
    99  	t.Parallel()
   100  
   101  	for _, testcase := range []struct {
   102  		name string
   103  		env  map[string]string
   104  		file string
   105  		args []string
   106  		opts []ff.Option
   107  		want Vars
   108  	}{
   109  		{
   110  			name: "empty",
   111  			args: []string{},
   112  			want: Vars{},
   113  		},
   114  		{
   115  			name: "long flags",
   116  			args: []string{"--string=foo", "--bool=true", "--int32=123", "--int32=456"},
   117  			want: Vars{S: "foo", B: true, I: []int32{123, 456}},
   118  		},
   119  		{
   120  			name: "short and long flags",
   121  			args: []string{"--string=foo", "-s", "bar", "-b", "--int32=1", "-i", "2"},
   122  			want: Vars{S: "bar", B: true, I: []int32{1, 2}},
   123  		},
   124  		{
   125  			name: "flags interspersed with args",
   126  			args: []string{"hello world", "--string=foo", "-b", "another", "argument", "-i", "1"},
   127  			want: Vars{S: "foo", B: true, I: []int32{1}, Args: []string{"hello world", "another", "argument"}},
   128  		},
   129  		{
   130  			name: "args after delimiter",
   131  			args: []string{"--string=foo", "--", "--string=bar"},
   132  			want: Vars{S: "foo", Args: []string{"--string=bar"}},
   133  		},
   134  		{
   135  			name: "config file with mixed prefixes",
   136  			file: "testdata/1.conf",
   137  			opts: []ff.Option{ff.WithConfigFileParser(ff.PlainParser)},
   138  			want: Vars{S: "B", F: 1.23, I: []int32{1, 2, 3, 4}},
   139  		},
   140  		{
   141  			name: "env vars",
   142  			env:  map[string]string{"PF_STRING": "hello", "PF_F": "0.123"},
   143  			opts: []ff.Option{ff.WithEnvVarPrefix("PF")},
   144  			want: Vars{S: "hello", F: 0.123},
   145  		},
   146  		{
   147  			name: "JSON config file",
   148  			file: "testdata/2.json",
   149  			opts: []ff.Option{ff.WithConfigFileParser(ff.JSONParser)},
   150  			want: Vars{S: "hello", I: []int32{1, 2, 3}},
   151  		},
   152  		{
   153  			name: "TOML config file",
   154  			file: "testdata/3.toml",
   155  			opts: []ff.Option{ff.WithConfigFileParser(fftoml.Parser)},
   156  			want: Vars{B: true, I: []int32{50, 100, 150, 0}},
   157  		},
   158  		{
   159  			name: "YAML config file",
   160  			file: "testdata/4.yaml",
   161  			opts: []ff.Option{ff.WithConfigFileParser(ffyaml.Parser)},
   162  			want: Vars{S: "c", F: 123.4, I: []int32{10, 11, 12}},
   163  		},
   164  	} {
   165  		t.Run(testcase.name, func(t *testing.T) {
   166  			if testcase.file != "" {
   167  				testcase.opts = append(testcase.opts, ff.WithConfigFile(testcase.file))
   168  			}
   169  
   170  			if len(testcase.env) > 0 {
   171  				for k, v := range testcase.env {
   172  					defer os.Setenv(k, os.Getenv(k))
   173  					os.Setenv(k, v)
   174  				}
   175  			}
   176  
   177  			fs, vars := Pair()
   178  			vars.ParseError = ff.Parse(ffpflag.NewFlagSet(fs), testcase.args, testcase.opts...)
   179  			vars.Args = fs.Args()
   180  			if err := Compare(&testcase.want, vars); err != nil {
   181  				t.Fatal(err)
   182  			}
   183  		})
   184  	}
   185  }