chore: add unit test coverage to inspect dump-wal (#21994)

* chore: add unit test coverage to inspect dump-wal

* chore: ran make fmt

* refactor: small styling change in conditional statement

* refactor: passed cobra cmd as pointer instead of an object

Co-authored-by: Michelle McFarland <michellemcfarland@Michelles-MacBook-Pro.local>
pull/22023/head
mcfarlm3 2021-07-30 15:32:46 -07:00 committed by GitHub
parent 568c564182
commit 3c1d841df6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 239 additions and 18 deletions

View File

@ -33,7 +33,7 @@ It has two modes of operation, depending on the --find-duplicates flag.
`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return dumpWAL.run(args)
return dumpWAL.run(cmd, args)
},
}
@ -43,20 +43,20 @@ It has two modes of operation, depending on the --find-duplicates flag.
return cmd
}
func (dumpWAL *dumpWALCommand) run(args []string) error {
func (dumpWAL *dumpWALCommand) run(cmd *cobra.Command, args []string) error {
// Process each WAL file.
for _, path := range args {
if err := dumpWAL.processWALFile(path); err != nil {
if err := dumpWAL.processWALFile(cmd, path); err != nil {
return err
}
}
return nil
}
func (dumpWAL *dumpWALCommand) processWALFile(path string) error {
func (dumpWAL *dumpWALCommand) processWALFile(cmd *cobra.Command, path string) error {
if filepath.Ext(path) != "."+tsm1.WALFileExtension {
fmt.Fprintf(os.Stderr, "invalid wal file path, skipping %s\n", path)
cmd.Printf("invalid wal file path, skipping %s\n", path)
return nil
}
@ -83,7 +83,7 @@ func (dumpWAL *dumpWALCommand) processWALFile(path string) error {
switch entry := entry.(type) {
case *tsm1.WriteWALEntry:
if !dumpWAL.findDuplicates {
fmt.Printf("[write] sz=%d\n", entry.MarshalSize())
cmd.Printf("[write] sz=%d\n", entry.MarshalSize())
}
keys := make([]string, 0, len(entry.Values))
@ -109,31 +109,31 @@ func (dumpWAL *dumpWALCommand) processWALFile(path string) error {
switch v := v.(type) {
case tsm1.IntegerValue:
fmt.Printf("%s %vi %d\n", k, v.Value(), t)
cmd.Printf("%s %vi %d\n", k, v.Value(), t)
case tsm1.UnsignedValue:
fmt.Printf("%s %vu %d\n", k, v.Value(), t)
cmd.Printf("%s %vu %d\n", k, v.Value(), t)
case tsm1.FloatValue:
fmt.Printf("%s %v %d\n", k, v.Value(), t)
cmd.Printf("%s %v %d\n", k, v.Value(), t)
case tsm1.BooleanValue:
fmt.Printf("%s %v %d\n", k, v.Value(), t)
cmd.Printf("%s %v %d\n", k, v.Value(), t)
case tsm1.StringValue:
fmt.Printf("%s %q %d\n", k, v.Value(), t)
cmd.Printf("%s %q %d\n", k, v.Value(), t)
default:
fmt.Printf("%s EMPTY\n", k)
cmd.Printf("%s EMPTY\n", k)
}
}
}
case *tsm1.DeleteWALEntry:
fmt.Printf("[delete] sz=%d\n", entry.MarshalSize())
cmd.Printf("[delete] sz=%d\n", entry.MarshalSize())
for _, k := range entry.Keys {
fmt.Printf("%s\n", string(k))
cmd.Printf("%s\n", string(k))
}
case *tsm1.DeleteRangeWALEntry:
fmt.Printf("[delete-range] min=%d max=%d sz=%d\n", entry.Min, entry.Max, entry.MarshalSize())
cmd.Printf("[delete-range] min=%d max=%d sz=%d\n", entry.Min, entry.Max, entry.MarshalSize())
for _, k := range entry.Keys {
fmt.Printf("%s\n", string(k))
cmd.Printf("%s\n", string(k))
}
default:
@ -146,7 +146,7 @@ func (dumpWAL *dumpWALCommand) processWALFile(path string) error {
keys := make([]string, 0, len(duplicateKeys))
if len(duplicateKeys) == 0 {
fmt.Println("No duplicates or out of order timestamps found")
cmd.Println("No duplicates or out of order timestamps found")
return nil
}
@ -156,7 +156,7 @@ func (dumpWAL *dumpWALCommand) processWALFile(path string) error {
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k)
cmd.Println(k)
}
}

View File

@ -0,0 +1,221 @@
package dump_wal
import (
"bytes"
"fmt"
"io"
"os"
"testing"
"github.com/golang/snappy"
"github.com/influxdata/influxdb/v2/tsdb/engine/tsm1"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
)
func Test_DumpWal_No_Args(t *testing.T) {
params := cmdParams{
walPaths: []string{},
expectErr: true,
expectedOut: "requires at least 1 arg(s), only received 0",
}
runCommand(t, params)
}
func Test_DumpWal_Bad_Path(t *testing.T) {
params := cmdParams{
findDuplicates: false,
walPaths: []string{"badpath.wal"},
expectErr: true,
expectedOut: "open badpath.wal: no such file or directory",
}
runCommand(t, params)
}
func Test_DumpWal_Wrong_File_Type(t *testing.T) {
// Creates a temporary .txt file (wrong extension)
dir, file := newTempWal(t, false, false)
defer os.RemoveAll(dir)
params := cmdParams{
walPaths: []string{file},
expectedOut: fmt.Sprintf("invalid wal file path, skipping %s", file),
expectErr: false,
}
runCommand(t, params)
}
func Test_DumpWal_File_Valid(t *testing.T) {
dir, file := newTempWal(t, true, false)
defer os.RemoveAll(dir)
params := cmdParams{
walPaths: []string{file},
expectedOuts: []string{
"[write]",
"cpu,host=A#!~#float 1.1 1",
"cpu,host=A#!~#int 1i 1",
"cpu,host=A#!~#bool true 1",
"cpu,host=A#!~#string \"string\" 1",
"cpu,host=A#!~#unsigned 10u 5",
},
}
runCommand(t, params)
}
func Test_DumpWal_Find_Duplicates_None(t *testing.T) {
dir, file := newTempWal(t, true, false)
defer os.RemoveAll(dir)
params := cmdParams{
findDuplicates: true,
walPaths: []string{file},
expectedOut: "No duplicates or out of order timestamps found",
}
runCommand(t, params)
}
func Test_DumpWal_Find_Duplicates_Present(t *testing.T) {
dir, file := newTempWal(t, true, true)
defer os.RemoveAll(dir)
params := cmdParams{
findDuplicates: true,
walPaths: []string{file},
expectedOut: "cpu,host=A#!~#unsigned",
}
runCommand(t, params)
}
func newTempWal(t *testing.T, validExt bool, withDuplicate bool) (string, string) {
t.Helper()
dir, err := os.MkdirTemp("", "dump-wal")
require.NoError(t, err)
var file *os.File
if !validExt {
file, err := os.CreateTemp(dir, "dumpwaltest*.txt")
require.NoError(t, err)
return dir, file.Name()
}
file, err = os.CreateTemp(dir, "dumpwaltest*"+"."+tsm1.WALFileExtension)
require.NoError(t, err)
p1 := tsm1.NewValue(10, 1.1)
p2 := tsm1.NewValue(1, int64(1))
p3 := tsm1.NewValue(1, true)
p4 := tsm1.NewValue(1, "string")
p5 := tsm1.NewValue(5, uint64(10))
values := map[string][]tsm1.Value{
"cpu,host=A#!~#float": {p1},
"cpu,host=A#!~#int": {p2},
"cpu,host=A#!~#bool": {p3},
"cpu,host=A#!~#string": {p4},
"cpu,host=A#!~#unsigned": {p5},
}
if withDuplicate {
p6 := tsm1.NewValue(1, uint64(70))
values = map[string][]tsm1.Value{
"cpu,host=A#!~#unsigned": {p5, p6},
}
}
// Write to WAL File
writeWalFile(t, file, values)
return dir, file.Name()
}
func writeWalFile(t *testing.T, file *os.File, vals map[string][]tsm1.Value) {
t.Helper()
e := &tsm1.WriteWALEntry{Values: vals}
b, err := e.Encode(nil)
require.NoError(t, err)
w := tsm1.NewWALSegmentWriter(file)
err = w.Write(e.Type(), snappy.Encode(nil, b))
require.NoError(t, err)
err = w.Flush()
require.NoError(t, err)
err = file.Sync()
require.NoError(t, err)
}
type cmdParams struct {
findDuplicates bool
walPaths []string
expectedOut string
expectedOuts []string
expectErr bool
expectExactEqual bool
}
func initCommand(t *testing.T, params cmdParams) *cobra.Command {
t.Helper()
// Creates new command and sets args
cmd := NewDumpWALCommand()
allArgs := params.walPaths
if params.findDuplicates {
allArgs = append(allArgs, "--find-duplicates")
}
cmd.SetArgs(allArgs)
return cmd
}
func getOutput(t *testing.T, cmd *cobra.Command) []byte {
t.Helper()
b := bytes.NewBufferString("")
cmd.SetOut(b)
cmd.SetErr(b)
require.NoError(t, cmd.Execute())
out, err := io.ReadAll(b)
require.NoError(t, err)
return out
}
func runCommand(t *testing.T, params cmdParams) {
t.Helper()
cmd := initCommand(t, params)
if params.expectErr {
require.EqualError(t, cmd.Execute(), params.expectedOut)
return
}
// Get output
out := getOutput(t, cmd)
// Check output
if params.expectExactEqual {
require.Equal(t, string(out), params.expectedOut)
return
}
if params.expectedOut != "" {
require.Contains(t, string(out), params.expectedOut)
} else {
for _, output := range params.expectedOuts {
require.Contains(t, string(out), output)
}
}
}