Variables inside templates in golang - templates

What is the namespace of variables inside html/text templates? I thought that a variable $x can change value inside a template, but this example shows me that I cannot.
I failed when I tried to group tournaments according year - something like this (http://play.golang.org/p/EX1Aut_ULD):
package main
import (
"fmt"
"os"
"text/template"
"time"
)
func main() {
tournaments := []struct {
Place string
Date time.Time
}{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
{{with .Date}}
{{$year:=.Year}}
{{if ne $year $prev_year}}
Actions in year {{$year}}:
{{$prev_year:=$year}}
{{end}}
{{end}}
{{.Place}}, {{.Date}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, tournaments)
if err != nil {
fmt.Println("executing template:", err)
}
}

Edit: See https://stackoverflow.com/a/52925780/1685538 for a more up-to-date answer.
Original answer:
https://golang.org/pkg/text/template/#hdr-Variables:
A variable's scope extends to the "end" action of the control
structure ("if", "with", or "range") in which it is declared, or to
the end of the template if there is no such control structure.
So the $prev_year you define with {{$prev_year:=$year}} only lives until.. the next line ({{end}}).
It seems there is no way of going around that.
The "right" way to do this is to take that logic out of your template, and do the grouping in your Go code.
Here is a working example : https://play.golang.org/p/DZoSXo9WQR
package main
import (
"fmt"
"os"
"text/template"
"time"
)
type Tournament struct {
Place string
Date time.Time
}
type TournamentGroup struct {
Year int
Tournaments []Tournament
}
func groupTournamentsByYear(tournaments []Tournament) []TournamentGroup {
if len(tournaments) == 0 {
return nil
}
result := []TournamentGroup{
{
Year: tournaments[0].Date.Year(),
Tournaments: make([]Tournament, 0, 1),
},
}
i := 0
for _, tournament := range tournaments {
year := tournament.Date.Year()
if result[i].Year == year {
// Add to existing group
result[i].Tournaments = append(result[i].Tournaments, tournament)
} else {
// New group
result = append(result, TournamentGroup{
Year: year,
Tournaments: []Tournament{
tournament,
},
})
i++
}
}
return result
}
func main() {
tournaments := []Tournament{
// for clarity - date is sorted, we don't need sort it again
{"Town1", time.Date(2015, time.November, 10, 23, 0, 0, 0, time.Local)},
{"Town2", time.Date(2015, time.October, 10, 23, 0, 0, 0, time.Local)},
{"Town3", time.Date(2014, time.November, 10, 23, 0, 0, 0, time.Local)},
}
t, err := template.New("").Parse(`
{{$prev_year:=0}}
{{range .}}
Actions in year {{.Year}}:
{{range .Tournaments}}
{{.Place}}, {{.Date}}
{{end}}
{{end}}
`)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, groupTournamentsByYear(tournaments))
if err != nil {
fmt.Println("executing template:", err)
}
}

In go1.11 text/template and hence html/template became able to set the value of existing variables, which means that the original code can be made to work with one very small modification.
Change
{{$prev_year:=$year}}
To
{{$prev_year = $year}}
Playground

As mentioned by this answer, the scope of that variable "re-assignment" ends with the {{end}} block. Therefore using standard variables only there's no way around the problem and it should be solved inside the Go program executing the template.
In some frameworks however this is not that easy (e.g. protoc-gen-gotemplate).
The Sprig library adds additional functionality to the standard template language. One of them are mutable maps that can be used in the following way:
// init the dictionary (you can init it without initial key/values as well)
{{$myVar := dict "key" "value"}}
// getting the "key" from the dictionary (returns array) and then fetching the first element from that array
{{pluck "key" $myVar | first}}
// conditional update block
{{if eq "some" "some"}}
// the $_ seems necessary because Go template functions need to return something
{{$_ := set $myVar "key" "newValue"}}
{{end}}
// print out the updated value
{{pluck "key" $myVar | first}}
This little example prints out:
value
newValue
A pragmatic approach would be to use a single dictionary for all mutable variables and store them under their corresponding variable name as key.
Reference:
http://masterminds.github.io/sprig/dicts.html
https://github.com/Masterminds/sprig

Related

Learning to write unit tests

I am trying to learn how to write tests for my code in order to write better code, but I just seem to have the hardest time figuring out how to actually test some code I have written. I have read so many tutorials, most of which seem to only cover functions that add two numbers or mock some database or server.
I have a simple function I wrote below that takes a text template and a CSV file as input and executes the template using the values of the CSV. I have "tested" the code by trial and error, passing files, and printing values, but I would like to learn how to write proper tests for it. I feel that learning to test my own code will help me understand and learn faster and better. Any help is appreciated.
// generateCmds generates configuration commands from a text template using
// the values from a CSV file. Multiple commands in the text template must
// be delimited by a semicolon. The first row of the CSV file is assumed to
// be the header row and the header values are used for key access in the
// text template.
func generateCmds(cmdTmpl string, filename string) ([]string, error) {
t, err := template.New("cmds").Parse(cmdTmpl)
if err != nil {
return nil, fmt.Errorf("parsing template: %v", err)
}
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("reading file: %v", err)
}
defer f.Close()
records, err := csv.NewReader(f).ReadAll()
if err != nil {
return nil, fmt.Errorf("reading records: %v", err)
}
if len(records) == 0 {
return nil, errors.New("no records to process")
}
var (
b bytes.Buffer
cmds []string
keys = records[0]
vals = make(map[string]string, len(keys))
)
for _, rec := range records[1:] {
for k, v := range rec {
vals[keys[k]] = v
}
if err := t.Execute(&b, vals); err != nil {
return nil, fmt.Errorf("executing template: %v", err)
}
for _, s := range strings.Split(b.String(), ";") {
if cmd := strings.TrimSpace(s); cmd != "" {
cmds = append(cmds, cmd)
}
}
b.Reset()
}
return cmds, nil
}
Edit: Thanks for all the suggestions so far! My question was flagged as being too broad, so I have some specific questions regarding my example.
Would a test table be useful in a function like this? And, if so, would the test struct need to include the returned cmds string slice and the value of err? For example:
type tmplTest struct {
name string // test name
tmpl string // the text template
filename string // CSV file with template values
expected []string // expected configuration commands
err error // expected error
}
How do you handle errors that are supposed to be returned for specific test cases? For example, os.Open() returns an error of type *PathError if an error is encountered. How do I initialize a *PathError that is equivalent to the one returned by os.Open()? Same idea for template.Parse(), template.Execute(), etc.
Edit 2: Below is a test function I came up with. My two question from the first edit still stand.
package cmd
import (
"testing"
"strings"
"path/filepath"
)
type tmplTest struct {
name string // test name
tmpl string // text template to execute
filename string // CSV containing template text values
cmds []string // expected configuration commands
}
var tests = []tmplTest{
{"empty_error", ``, "", nil},
{"file_error", ``, "fake_file.csv", nil},
{"file_empty_error", ``, "empty.csv", nil},
{"file_fmt_error", ``, "fmt_err.csv", nil},
{"template_fmt_error", `{{ }{{`, "test_values.csv", nil},
{"template_key_error", `{{.InvalidKey}}`, "test_values.csv", nil},
}
func TestGenerateCmds(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cmds, err := generateCmds(tc.tmpl, filepath.Join("testdata", tc.filename))
if err != nil {
// Unexpected error. Fail the test.
if !strings.Contains(tc.name, "error") {
t.Fatal(err)
}
// TODO: Otherwise, check that the function failed at the expected point.
}
if tc.cmds == nil && cmds != nil {
t.Errorf("expected no commands; got %d", len(cmds))
}
if len(cmds) != len(tc.cmds) {
t.Errorf("expected %d commands; got %d", len(tc.cmds), len(cmds))
}
for i := range cmds {
if cmds[i] != tc.cmds[i] {
t.Errorf("expected %q; got %q", tc.cmds[i], cmds[i])
}
}
})
}
}
You basically need to have some sample files with the contents you want to test, then in your test code you can call the generateCmds function passing in the template string and the files to then verify that the results are what you expect.
It is not so much different as the examples you probably saw for simpler cases.
You can place the files under a testdata folder inside the same package (testdata is a special name that the Go tools will ignore during build).
Then you can do something like:
func TestCSVProcessing(t *testing.T) {
templateStr := `<your template here>`
testFile := "testdata/yourtestfile.csv"
result, err := generateCmds(templateStr, testFile)
if err != nil {
// fail the test here, unless you expected an error with this file
}
// compare the "result" contents with what you expected
// failing the test if it does not match
}
EDIT
About the specific questions you added later:
Would a test table be useful in a function like this? And, if so, would the test struct need to include the returned cmds string slice and the value of err?
Yes, it'd make sense to include both the expected strings to be returned as well as the expected error (if any).
How do you handle errors that are supposed to be returned for specific test cases? For example, os.Open() returns an error of type *PathError if an error is encountered. How do I initialize a *PathError that is equivalent to the one returned by os.Open()?
I don't think you'll be able to "initialize" an equivalent error for each case. Sometimes the libraries might use internal types for their errors making this impossible. Easiest would be to "initialize" a regular error with the same value returned in its Error() method, then just compare the returned error's Error() value with the expected one.

Subset of table driven test

For testing functions I could select which will run by option -run.
go test -run regex
Very common if we have dozens test cases is put it into array in order not to write function for each of that:
cases := []struct {
arg, expected string
} {
{"%a", "[%a]"},
{"%-a", "[%-a]"},
// and many others
}
for _, c := range cases {
res := myfn(c.arg)
if res != c.expected {
t.Errorf("myfn(%q) should return %q, but it returns %q", c.arg, c.expected, res)
}
}
This work good, but problem is with maintanance. When I add a new testcase, while debugging I want to start just a new test case, but I cannot say something like:
go test -run TestMyFn.onlyThirdCase
Is there any elegant way, how to have many testcases in array together with ability to choose which testcase will run?
With Go 1.6 (and below)
This is not supported directly by the testing package in Go 1.6 and below. You have to implement it yourself.
But it's not that hard. You can use flag package to easily access command line arguments.
Let's see an example. We define an "idx" command line parameter, which if present, only the case at that index will be executed, else all test cases.
Define flag:
var idx = flag.Int("idx", -1, "specify case index to run only")
Parse command line flags (actually, this is not required as go test already calls this, but just to be sure / complete):
func init() {
flag.Parse()
}
Using this parameter:
for i, c := range cases {
if *idx != -1 && *idx != i {
println("Skipping idx", i)
continue
}
if res := myfn(c.arg); res != c.expected {
t.Errorf("myfn(%q) should return %q, but it returns %q", c.arg, c.expected, res)
}
}
Testing it with 3 test cases:
cases := []struct {
arg, expected string
}{
{"%a", "[%a]"},
{"%-a", "[%-a]"},
{"%+a", "[%+a]"},
}
Without idx parameter:
go test
Output:
PASS
ok play 0.172s
Specifying an index:
go test -idx=1
Output:
Skipping idx 0
Skipping idx 2
PASS
ok play 0.203s
Of course you can implement more sophisticated filtering logic, e.g. you can have minidx and maxidx flags to run cases in a range:
var (
minidx = flag.Int("minidx", 0, "min case idx to run")
maxidx = flag.Int("maxidx", -1, "max case idx to run")
)
And the filtering:
if i < *minidx || *maxidx != -1 && i > *maxidx {
println("Skipping idx", i)
continue
}
Using it:
go test -maxidx=1
Output:
Skipping idx 2
PASS
ok play 0.188s
Starting with Go 1.7
Go 1.7 (to be released on August 18, 2016) adds the definition of subtests and sub-benchmarks:
The testing package now supports the definition of tests with subtests and benchmarks with sub-benchmarks. This support makes it easy to write table-driven benchmarks and to create hierarchical tests. It also provides a way to share common setup and tear-down code. See the package documentation for details.
With that, you can do things like:
func TestFoo(t *testing.T) {
// <setup code>
t.Run("A=1", func(t *testing.T) { ... })
t.Run("A=2", func(t *testing.T) { ... })
t.Run("B=1", func(t *testing.T) { ... })
// <tear-down code>
}
Where the subtests are named "A=1", "A=2", "B=1".
The argument to the -run and -bench command-line flags is a slash-separated list of regular expressions that match each name element in turn. For example:
go test -run Foo # Run top-level tests matching "Foo".
go test -run Foo/A= # Run subtests of Foo matching "A=".
go test -run /A=1 # Run all subtests of a top-level test matching "A=1".
How does this help your case? The names of subtests are string values, which can be generated on-the-fly, e.g.:
for i, c := range cases {
name := fmt.Sprintf("C=%d", i)
t.Run(name, func(t *testing.T) {
if res := myfn(c.arg); res != c.expected {
t.Errorf("myfn(%q) should return %q, but it returns %q",
c.arg, c.expected, res)
}
})
}
To run the case at index 2, you could start it like
go test -run /C=2
or
go test -run TestName/C=2
I wrote a simple code, that work fine with both, although with a bit different command line options. Version for 1.7 is:
// +build go1.7
package plist
import "testing"
func runTest(name string, fn func(t *testing.T), t *testing.T) {
t.Run(name, fn)
}
and 1.6 and older:
// +build !go1.7
package plist
import (
"flag"
"testing"
"runtime"
"strings"
"fmt"
)
func init() {
flag.Parse()
}
var pattern = flag.String("pattern", "", "specify which test(s) should be executed")
var verbose = flag.Bool("verbose", false, "write whether test was done")
// This is a hack, that a bit simulate t.Run available from go1.7
func runTest(name string, fn func(t *testing.T), t *testing.T) {
// obtain name of caller
var pc[10]uintptr
runtime.Callers(2, pc[:])
var fnName = ""
f := runtime.FuncForPC(pc[0])
if f != nil {
fnName = f.Name()
}
names := strings.Split(fnName, ".")
fnName = names[len(names)-1] + "/" + name
if strings.Contains(fnName, *pattern) {
if *verbose {
fmt.Printf("%s is executed\n", fnName)
}
fn(t)
} else {
if *verbose {
fmt.Printf("%s is skipped\n", fnName)
}
}
}

Concrete range example

The Go documentation on the text/template package is so abstract that I'm having trouble figuring out how to actually range over a slice of objects. Here's my attempt so far (This outputs nothing for me):
package main
import (
"os"
templ "text/template"
)
type Context struct {
people []Person
}
type Person struct {
Name string //exported field since it begins with a capital letter
Senior bool
}
func main() {
// Range example
tRange := templ.New("Range Example")
ctx2 := Context{people: []Person{Person{Name: "Mary", Senior: false}, Person{Name: "Joseph", Senior: true}}}
tRange = templ.Must(
tRange.Parse(`
{{range $i, $x := $.people}} Name={{$x.Name}} Senior={{$x.Senior}} {{end}}
`))
tRange.Execute(os.Stdout, ctx2)
}
The range is correct. The problem is that the Context people field is not exported. The template package ignores unexported fields. Change the type definition to:
type Context struct {
People []Person // <-- note that People starts with capital P.
}
and the template to:
{{range $i, $x := $.People}} Name={{$x.Name}} Senior={{$x.Senior}} {{end}}
playground

Go Templates: Are Nested Ranges Possible?

This one is seemingly simple but it's driving me insane.
How does one go about referencing a struct element higher in the scope within a nested range in golang templates?
Example:
type Foo struct {
Id string
Name string
}
type Bar struct {
Id string
Name string
}
var foos []Foo
var bars []Bar
// logic to populate both foos and bars
In the template:
{{range .foos}}
<div>Foo {{.Name}}</div>
<div>
{{range ..bars}}
<div>Bar {{.Name}} <input type="text" name="ids_{{..Id}}_{{.Id}}" /></div>
{{end}}
</div>
{{end}}
Obviously ..bars and ..Id don't work, but hopefully my intent is clear. I'd like to iterate through all combinations of Foo and Bar and generate a form element with a name build by both the Foo's Id and the Bar's Id.
The problem is that it seems it is impossible to:
Access bars from inside the scope of the foos range scope
Access Foo's Id from inside the bar's range scope
I have a temporary workaround to this by putting a bunch of redundant fields in both structs, but this seems very ugly to me, violates DRY, and in general feels very wrong.
Is there any way with golang templates to do what I'd like to do?
Yes. I feel as if not finding a solution comes from not reading the text/template package closely enough. If you are using html/template, the syntax is the same (and they tell you to read text/template ;)). Here is a complete working solution for what you might want to do.
Go file:
package main
import (
"bytes"
"io/ioutil"
"os"
"strconv"
"text/template"
)
type Foo struct {
Id string
Name string
}
type Bar struct {
Id string
Name string
}
var foos []Foo
var bars []Bar
func main() {
foos = make([]Foo, 10)
bars = make([]Bar, 10)
for i := 0; i < 10; i++ {
foos[i] = Foo{strconv.Itoa(i), strconv.Itoa(i)} // just random strings
bars[i] = Bar{strconv.Itoa(10 * i), strconv.Itoa(10 * i)}
}
tmpl, err := ioutil.ReadFile("so.tmpl")
if err != nil {
panic(err)
}
buffer := bytes.NewBuffer(make([]byte, 0, len(tmpl)))
output := template.Must(template.New("FUBAR").Parse(string(tmpl)))
output.Execute(buffer, struct {
FooSlice []Foo
BarSlice []Bar
}{
FooSlice: foos,
BarSlice: bars,
})
outfile, err := os.Create("output.html")
if err != nil {
panic(err)
}
defer outfile.Close()
outfile.Write(buffer.Bytes())
}
Note: You can probably do something to not load the file into an intermediate buffer (use ParseFiles), I just copied and pasted some code that I had written for one of my projects.
Template file:
{{ $foos := .FooSlice }}
{{ $bars := .BarSlice }}
{{range $foo := $foos }}
<div>Foo {{$foo.Name}}</div>
<div>
{{range $bar := $bars}}
<div>Bar {{$bar.Name}} <input type="text" name="ids_{{$foo.Id}}_{{$bar.Id}}" /></div>
{{end}}
</div>
{{end}}
The two morals of this story are
a) use variables in templates judiciously, they are beneficial
b) range in templates also can set variables, you do not need to rely solely on $ or .

How do I unit test command line flags in Go?

I would like a unit test that verifies a particular command line flag is within an enumeration.
Here is the code I would like to write tests against:
var formatType string
const (
text = "text"
json = "json"
hash = "hash"
)
func init() {
const (
defaultFormat = "text"
formatUsage = "desired output format"
)
flag.StringVar(&formatType, "format", defaultFormat, formatUsage)
flag.StringVar(&formatType, "f", defaultFormat, formatUsage+" (shorthand)")
}
func main() {
flag.Parse()
}
The desired test would pass only if -format equalled one of the const values given above. This value would be available in formatType. An example correct call would be: program -format text
What is the best way to test the desired behaviors?
Note: Perhaps I have phrased this poorly, but the displayed code it not the unit test itself, but the code I want to write unit tests against. This is a simple example from the tool I am writing and wanted to ask if there were a good way to test valid inputs to the tool.
Custom testing and processing of flags can be achieved with the flag.Var function in the flag package.
Flag.Var "defines a flag with the specified name and usage string. The type and value of the flag are represented by the first argument, of type Value, which typically holds a user-defined implementation of Value."
A flag.Value is any type that satisfies the Value interface, defined as:
type Value interface {
String() string
Set(string) error
}
There is a good example in the example_test.go file in the flag package source
For your use case you could use something like:
package main
import (
"errors"
"flag"
"fmt"
)
type formatType string
func (f *formatType) String() string {
return fmt.Sprint(*f)
}
func (f *formatType) Set(value string) error {
if len(*f) > 0 && *f != "text" {
return errors.New("format flag already set")
}
if value != "text" && value != "json" && value != "hash" {
return errors.New("Invalid Format Type")
}
*f = formatType(value)
return nil
}
var typeFlag formatType
func init() {
typeFlag = "text"
usage := `Format type. Must be "text", "json" or "hash". Defaults to "text".`
flag.Var(&typeFlag, "format", usage)
flag.Var(&typeFlag, "f", usage+" (shorthand)")
}
func main() {
flag.Parse()
fmt.Println("Format type is", typeFlag)
}
This is probably overkill for such a simple example, but may be very useful when defining more complex flag types (The linked example converts a comma separated list of intervals into a slice of a custom type based on time.Duration).
EDIT: In answer to how to run unit tests against flags, the most canonical example is flag_test.go in the flag package source. The section related to testing custom flag variables starts at Line 181.
You can do this
func main() {
var name string
var password string
flag.StringVar(&name, "name", "", "")
flag.StringVar(&password, "password", "", "")
flag.Parse()
for _, v := range os.Args {
fmt.Println(v)
}
if len(strings.TrimSpace(name)) == 0 || len(strings.TrimSpace(password)) == 0 {
log.Panicln("no name or no passward")
}
fmt.Printf("name:%s\n", name)
fmt.Printf("password:%s\n", password)
}
func TestMainApp(t *testing.T) {
os.Args = []string{"test", "-name", "Hello", "-password", "World"}
main()
}
You can test main() by:
Making a test that runs a command
Which then calls the app test binary, built from go test, directly
Passing the desired flags you want to test
Passing back the exit code, stdout, and stderr which you can assert on.
NOTE This only works when main exits, so that the test does not run infinitely, or gets caught in a recursive loop.
Given your main.go looks like:
package main
import (
"flag"
"fmt"
"os"
)
var formatType string
const (
text = "text"
json = "json"
hash = "hash"
)
func init() {
const (
defaultFormat = "text"
formatUsage = "desired output format"
)
flag.StringVar(&formatType, "format", defaultFormat, formatUsage)
flag.StringVar(&formatType, "f", defaultFormat, formatUsage+" (shorthand)")
}
func main() {
flag.Parse()
fmt.Printf("format type = %v\n", formatType)
os.Exit(0)
}
Your main_test.go may then look something like:
package main
import (
"fmt"
"os"
"os/exec"
"path"
"runtime"
"strings"
"testing"
)
// This will be used to pass args to app and keep the test framework from looping
const subCmdFlags = "FLAGS_FOR_MAIN"
func TestMain(m *testing.M) {
// Only runs when this environment variable is set.
if os.Getenv(subCmdFlags) != "" {
runAppMain()
}
// Run all tests
exitCode := m.Run()
// Clean up
os.Exit(exitCode)
}
func TestMainForCorrectness(tester *testing.T) {
var tests = []struct {
name string
wantCode int
args []string
}{
{"formatTypeJson", 0, []string{"-format", "json"}},
}
for _, test := range tests {
tester.Run(test.name, func(t *testing.T) {
cmd := getTestBinCmd(test.args)
cmdOut, cmdErr := cmd.CombinedOutput()
got := cmd.ProcessState.ExitCode()
// Debug
showCmdOutput(cmdOut, cmdErr)
if got != test.wantCode {
t.Errorf("unexpected error on exit. want %q, got %q", test.wantCode, got)
}
})
}
}
// private helper methods.
// Used for running the application's main function from other test.
func runAppMain() {
// the test framework has process its flags,
// so now we can remove them and replace them with the flags we want to pass to main.
// we are pulling them out of the environment var we set.
args := strings.Split(os.Getenv(subCmdFlags), " ")
os.Args = append([]string{os.Args[0]}, args...)
// Debug stmt, can be removed
fmt.Printf("\nos args = %v\n", os.Args)
main() // will run and exit, signaling the test framework to stop and return the exit code.
}
// getTestBinCmd return a command to run your app (test) binary directly; `TestMain`, will be run automatically.
func getTestBinCmd(args []string) *exec.Cmd {
// call the generated test binary directly
// Have it the function runAppMain.
cmd := exec.Command(os.Args[0], "-args", strings.Join(args, " "))
// Run in the context of the source directory.
_, filename, _, _ := runtime.Caller(0)
cmd.Dir = path.Dir(filename)
// Set an environment variable
// 1. Only exist for the life of the test that calls this function.
// 2. Passes arguments/flag to your app
// 3. Lets TestMain know when to run the main function.
subEnvVar := subCmdFlags + "=" + strings.Join(args, " ")
cmd.Env = append(os.Environ(), subEnvVar)
return cmd
}
func showCmdOutput(cmdOut []byte, cmdErr error) {
if cmdOut != nil {
fmt.Printf("\nBEGIN sub-command out:\n%v", string(cmdOut))
fmt.Print("END sub-command\n")
}
if cmdErr != nil {
fmt.Printf("\nBEGIN sub-command stderr:\n%v", cmdErr.Error())
fmt.Print("END sub-command\n")
}
}
I'm not sure whether we agree on the term 'unit test'. What you want to achieve seems to me
more like a pretty normal test in a program. You probably want to do something like this:
func main() {
flag.Parse()
if formatType != text || formatType != json || formatType != hash {
flag.Usage()
return
}
// ...
}
Sadly, it is not easily possible to extend the flag Parser with own value verifiers
so you have to stick with this for now.
See Intermernet for a solution which defines a custom format type and its validator.