diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..35cde5c --- /dev/null +++ b/codecov.yml @@ -0,0 +1,4 @@ +coverage: + status: + project: off + patch: off diff --git a/pkg/tstune/tuner.go b/pkg/tstune/tuner.go index 8b61ecb..c6fb99c 100644 --- a/pkg/tstune/tuner.go +++ b/pkg/tstune/tuner.go @@ -52,7 +52,7 @@ const ( ) var ( - fileExistsFn = fileExists + osStatFn = os.Stat pgVersions = []string{"10", "9.6"} ) @@ -78,6 +78,16 @@ type Tuner struct { flags *TunerFlags } +func (t *Tuner) initializeIOHandler(out io.Writer, outErr io.Writer) { + var p printer + if t.flags.UseColor { + p = &colorPrinter{outErr} + } else { + p = &noColorPrinter{outErr} + } + t.handler = &ioHandler{p: p, out: out, outErr: outErr} +} + // Run executes the tuning process given the provided flags and looks for input // on the in io.Reader. Informational messages are written to outErr while // actual recommendations are written to out. @@ -86,23 +96,20 @@ func (t *Tuner) Run(flags *TunerFlags, in io.Reader, out io.Writer, outErr io.Wr if t.flags == nil { t.flags = &TunerFlags{} } - var err error - // setup IO - var p printer - if t.flags.UseColor { - p = &colorPrinter{outErr} - } else { - p = &noColorPrinter{outErr} + t.initializeIOHandler(out, outErr) + + ifErrHandle := func(err error) { + if err != nil { + t.handler.errorExit(err) + } } - t.handler = &ioHandler{p: p, out: out, outErr: outErr} + var err error // attempt to find the config file and open it for reading fileName := t.flags.ConfPath if len(fileName) == 0 { fileName, err = getConfigFilePath(runtime.GOOS) - if err != nil { - t.handler.errorExit(err) - } + ifErrHandle(err) } file, err := os.Open(fileName) @@ -124,12 +131,6 @@ func (t *Tuner) Run(flags *TunerFlags, in io.Reader, out io.Writer, outErr io.Wr } } - ifErrHandle := func(err error) { - if err != nil { - t.handler.errorExit(err) - } - } - // write backup cfs, err := getConfigFileState(file) @@ -177,20 +178,25 @@ func (t *Tuner) Run(flags *TunerFlags, in io.Reader, out io.Writer, outErr io.Wr } } +// fileExists is a simple check for stating if a file exists and if any error +// occurs it returns false. func fileExists(name string) bool { // for our purposes, any error is a problem, so assume it does not exist - if _, err := os.Stat(name); err != nil { + if _, err := osStatFn(name); err != nil { return false } return true } +// getConfigFilePath attempts to find the postgresql.conf file using path heuristics +// for different operating systems. If successful it returns the full path to +// the file; otherwise, it returns with an empty path and error. func getConfigFilePath(os string) (string, error) { tried := []string{} try := func(format string, args ...interface{}) string { fileName := fmt.Sprintf(format, args...) tried = append(tried, fileName) - if fileExistsFn(fileName) { + if fileExists(fileName) { return fileName } return "" diff --git a/pkg/tstune/tuner_test.go b/pkg/tstune/tuner_test.go index 5db91a5..1ac880a 100644 --- a/pkg/tstune/tuner_test.go +++ b/pkg/tstune/tuner_test.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" "io" + "os" "strings" "testing" @@ -16,6 +17,72 @@ func newTunerWithDefaultFlags(handler *ioHandler, cfs *configFileState) *Tuner { return &Tuner{handler, cfs, &TunerFlags{}} } +func TestTunerInitializeIOHandler(t *testing.T) { + tuner := &Tuner{nil, nil, &TunerFlags{}} + tuner.flags.UseColor = true + tuner.initializeIOHandler(os.Stdout, os.Stderr) + + switch x := tuner.handler.p.(type) { + case *colorPrinter: + default: + t.Errorf("non-color printer for UseColor flag: got %T", x) + } + + tuner.flags.UseColor = false + tuner.initializeIOHandler(os.Stdout, os.Stderr) + + switch x := tuner.handler.p.(type) { + case *noColorPrinter: + default: + t.Errorf("color printer for UseColor=false flag: got %T", x) + } +} + +func TestFileExists(t *testing.T) { + existsName := "exists.txt" + errorName := "error.txt" + cases := []struct { + desc string + filename string + want bool + }{ + { + desc: "found file", + filename: existsName, + want: true, + }, + { + desc: "not found file", + filename: "ghost.txt", + want: false, + }, + { + desc: "error in stat", + filename: errorName, + want: false, + }, + } + + oldOSStatFn := osStatFn + osStatFn = func(name string) (os.FileInfo, error) { + if name == existsName { + return nil, nil + } else if name == errorName { + return nil, fmt.Errorf("this is an error") + } else { + return nil, os.ErrNotExist + } + } + + for _, c := range cases { + if got := fileExists(c.filename); got != c.want { + t.Errorf("%s: incorrect result: got %v want %v", c.desc, got, c.want) + } + } + + osStatFn = oldOSStatFn +} + func TestGetConfigFilePath(t *testing.T) { cases := []struct { desc string @@ -66,6 +133,14 @@ func TestGetConfigFilePath(t *testing.T) { wantFile: fmt.Sprintf(fileNameDebianFmt, "9.6"), shouldErr: false, }, + { + desc: "linux - arch", + os: osLinux, + files: []string{fileNameArch}, + wantFile: fileNameArch, + shouldErr: false, + }, + { desc: "linux - no", os: osLinux, @@ -75,15 +150,15 @@ func TestGetConfigFilePath(t *testing.T) { }, } - oldFileExistsFn := fileExistsFn + oldOSStatFn := osStatFn for _, c := range cases { - fileExistsFn = func(fn string) bool { + osStatFn = func(fn string) (os.FileInfo, error) { for _, s := range c.files { if fn == s { - return true + return nil, nil } } - return false + return nil, os.ErrNotExist } filename, err := getConfigFilePath(c.os) if err != nil && !c.shouldErr { @@ -100,7 +175,7 @@ func TestGetConfigFilePath(t *testing.T) { t.Errorf("%s: incorrect filename: got %s want %s", c.desc, got, c.wantFile) } } - fileExistsFn = oldFileExistsFn + osStatFn = oldOSStatFn } type limitChecker struct {