File: vars.go

package info (click to toggle)
golang-github-peterbourgon-ff 3.4.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 408 kB
  • sloc: sh: 9; makefile: 4
file content (149 lines) | stat: -rw-r--r-- 4,815 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package fftest

import (
	"errors"
	"flag"
	"fmt"
	"reflect"
	"strings"
	"testing"
	"time"
)

// Pair defines and returns an empty flag set and vars assigned to it.
func Pair() (*flag.FlagSet, *Vars) {
	fs := flag.NewFlagSet("fftest", flag.ContinueOnError)
	vars := DefaultVars(fs)
	return fs, vars
}

// DefaultVars registers a predefined set of variables to the flag set.
// Tests can call parse on the flag set with a variety of flags, config files,
// and env vars, and check the resulting effect on the vars.
func DefaultVars(fs *flag.FlagSet) *Vars {
	var v Vars
	fs.StringVar(&v.S, "s", "", "string")
	fs.IntVar(&v.I, "i", 0, "int")
	fs.Float64Var(&v.F, "f", 0., "float64")
	fs.BoolVar(&v.B, "b", false, "bool")
	fs.DurationVar(&v.D, "d", 0*time.Second, "time.Duration")
	fs.Var(&v.X, "x", "collection of strings (repeatable)")
	return &v
}

// NonzeroDefaultVars is like DefaultVars, but provides each primitive flag with
// a nonzero default value. This is useful for tests that explicitly provide a
// zero value for the type.
func NonzeroDefaultVars(fs *flag.FlagSet) *Vars {
	var v Vars
	fs.StringVar(&v.S, "s", "foo", "string")
	fs.IntVar(&v.I, "i", 123, "int")
	fs.Float64Var(&v.F, "f", 9.99, "float64")
	fs.BoolVar(&v.B, "b", true, "bool")
	fs.DurationVar(&v.D, "d", 3*time.Hour, "time.Duration")
	fs.Var(&v.X, "x", "collection of strings (repeatable)")
	return &v
}

// NestedDefaultVars is similar to DefaultVars, but uses nested flag names.
func NestedDefaultVars(delimiter string) func(fs *flag.FlagSet) *Vars {
	return func(fs *flag.FlagSet) *Vars {
		var v Vars
		fs.StringVar(&v.S, fmt.Sprintf("foo%ss", delimiter), "", "string")
		fs.IntVar(&v.I, fmt.Sprintf("bar%[1]snested%[1]si", delimiter), 0, "int")
		fs.Float64Var(&v.F, fmt.Sprintf("bar%[1]snested%[1]sf", delimiter), 0., "float64")
		fs.BoolVar(&v.B, fmt.Sprintf("foo%sb", delimiter), false, "bool")
		fs.Var(&v.X, fmt.Sprintf("baz%[1]snested%[1]sx", delimiter), "collection of strings (repeatable)")
		return &v
	}
}

// Vars are a common set of variables used for testing.
type Vars struct {
	S string
	I int
	F float64
	B bool
	D time.Duration
	X StringSlice

	// ParseError should be assigned as the result of Parse in tests.
	ParseError error

	// If a test case expects an input to generate a parse error,
	// it can specify that error here. The Compare helper will
	// look for it using errors.Is.
	WantParseErrorIs error

	// If a test case expects an input to generate a parse error,
	// it can specify part of that error string here. The Compare
	// helper will look for it using strings.Contains.
	WantParseErrorString string
}

// Compare one set of vars with another
// and t.Error on any difference.
func Compare(t *testing.T, want, have *Vars) {
	t.Helper()

	if want.WantParseErrorIs != nil || want.WantParseErrorString != "" {
		if want.WantParseErrorIs != nil && have.ParseError == nil {
			t.Errorf("want error (%v), have none", want.WantParseErrorIs)
		}
		if want.WantParseErrorString != "" && have.ParseError == nil {
			t.Errorf("want error (%q), have none", want.WantParseErrorString)
		}
		if want.WantParseErrorIs == nil && want.WantParseErrorString == "" && have.ParseError != nil {
			t.Errorf("want clean parse, have error (%v)", have.ParseError)
		}
		if want.WantParseErrorIs != nil && have.ParseError != nil && !errors.Is(have.ParseError, want.WantParseErrorIs) {
			t.Errorf("want wrapped error (%#+v), have error (%#+v)", want.WantParseErrorIs, have.ParseError)
		}
		if want.WantParseErrorString != "" && have.ParseError != nil && !strings.Contains(have.ParseError.Error(), want.WantParseErrorString) {
			t.Errorf("want error string (%q), have error (%v)", want.WantParseErrorString, have.ParseError)
		}
		return
	}

	if have.ParseError != nil {
		t.Errorf("error: %v", have.ParseError)
	}

	if want.S != have.S {
		t.Errorf("var S: want %q, have %q", want.S, have.S)
	}
	if want.I != have.I {
		t.Errorf("var I: want %d, have %d", want.I, have.I)
	}
	if want.F != have.F {
		t.Errorf("var F: want %f, have %f", want.F, have.F)
	}
	if want.B != have.B {
		t.Errorf("var B: want %v, have %v", want.B, have.B)
	}
	if want.D != have.D {
		t.Errorf("var D: want %s, have %s", want.D, have.D)
	}
	if !reflect.DeepEqual(want.X, have.X) {
		t.Errorf("var X: want %v, have %v", want.X, have.X)
	}
}

// StringSlice is a flag.Value that collects each Set string
// into a slice, allowing for repeated flags.
type StringSlice []string

// Set implements flag.Value and appends the string to the slice.
func (ss *StringSlice) Set(s string) error {
	(*ss) = append(*ss, s)
	return nil
}

// String implements flag.Value and returns the list of
// strings, or "..." if no strings have been added.
func (ss *StringSlice) String() string {
	if len(*ss) <= 0 {
		return "..."
	}
	return strings.Join(*ss, ", ")
}