Skip to content

Commit

Permalink
add check cmd cobra
Browse files Browse the repository at this point in the history
Signed-off-by: Mustafa Elbehery <[email protected]>
  • Loading branch information
Elbehery committed Apr 17, 2024
1 parent cc22218 commit c5618e4
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 92 deletions.
75 changes: 75 additions & 0 deletions cmd/bbolt/command_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"

bolt "go.etcd.io/bbolt"
"go.etcd.io/bbolt/internal/guts_cli"
)

var (
// ErrTooManyArgs is returned with a cmd is being executed with more arguments than required.
ErrTooManyArgs = errors.New("too many arguments")
)

func newCheckCobraCommand() *cobra.Command {
checkCmd := &cobra.Command{
Use: "check",
Short: "verifies integrity of bbolt database",
Long: strings.TrimLeft(`
usage: bolt check PATH
Check opens a database at PATH and runs an exhaustive check to verify that
all pages are accessible or are marked as freed. It also verifies that no
pages are double referenced.
Verification errors will stream out as they are found and the process will
return after all pages have been checked.
`, "\n"),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return checkFunc(cmd, args[0])
},
}

return checkCmd
}

func checkFunc(cmd *cobra.Command, dbPath string) error {
if _, err := checkSourceDBPath(dbPath); err != nil {
return err
}

// Open database.
db, err := bolt.Open(dbPath, 0600, &bolt.Options{
ReadOnly: true,
PreLoadFreelist: true,
})
if err != nil {
return err
}
defer db.Close()

// Perform consistency check.
return db.View(func(tx *bolt.Tx) error {
var count int
for err := range tx.Check(bolt.WithKVStringer(CmdKvStringer())) {
fmt.Fprintln(cmd.OutOrStdout(), err)
count++
}

// Print summary of errors.
if count > 0 {
fmt.Fprintf(cmd.OutOrStdout(), "%d errors found\n", count)
return guts_cli.ErrCorrupt
}

// Notify user that database is valid.
fmt.Fprintln(cmd.OutOrStdout(), "OK")
return nil
})
}
33 changes: 33 additions & 0 deletions cmd/bbolt/command_check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main_test

import (
"bytes"
"io"
"testing"

"github.com/stretchr/testify/require"

main "go.etcd.io/bbolt/cmd/bbolt"
"go.etcd.io/bbolt/internal/btesting"
)

func TestCheckCommand_Run(t *testing.T) {
db := btesting.MustCreateDB(t)
db.Close()
defer requireDBNoChange(t, dbData(t, db.Path()), db.Path())

rootCmd := main.NewRootCommand()
// capture output for assertion
outputBuf := bytes.NewBufferString("")
rootCmd.SetOut(outputBuf)

rootCmd.SetArgs([]string{
"check", db.Path(),
})
err := rootCmd.Execute()
require.NoError(t, err)

output, err := io.ReadAll(outputBuf)
require.NoError(t, err)
require.Equalf(t, "OK\n", string(output), "unexpected stdout:\n\n%s", string(output))
}
1 change: 1 addition & 0 deletions cmd/bbolt/command_root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func NewRootCommand() *cobra.Command {
newVersionCobraCommand(),
newSurgeryCobraCommand(),
newInspectCobraCommand(),
newCheckCobraCommand(),
)

return rootCmd
Expand Down
78 changes: 0 additions & 78 deletions cmd/bbolt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ func (m *Main) Run(args ...string) error {
return newBenchCommand(m).Run(args[1:]...)
case "buckets":
return newBucketsCommand(m).Run(args[1:]...)
case "check":
return newCheckCommand(m).Run(args[1:]...)
case "compact":
return newCompactCommand(m).Run(args[1:]...)
case "dump":
Expand Down Expand Up @@ -180,82 +178,6 @@ Use "bbolt [command] -h" for more information about a command.
`, "\n")
}

// checkCommand represents the "check" command execution.
type checkCommand struct {
baseCommand
}

// newCheckCommand returns a checkCommand.
func newCheckCommand(m *Main) *checkCommand {
c := &checkCommand{}
c.baseCommand = m.baseCommand
return c
}

// Run executes the command.
func (cmd *checkCommand) Run(args ...string) error {
// Parse flags.
fs := flag.NewFlagSet("", flag.ContinueOnError)
help := fs.Bool("h", false, "")
if err := fs.Parse(args); err != nil {
return err
} else if *help {
fmt.Fprintln(cmd.Stderr, cmd.Usage())
return ErrUsage
}

// Require database path.
path := fs.Arg(0)
if path == "" {
return ErrPathRequired
} else if _, err := os.Stat(path); os.IsNotExist(err) {
return ErrFileNotFound
}

// Open database.
db, err := bolt.Open(path, 0600, &bolt.Options{
ReadOnly: true,
PreLoadFreelist: true,
})
if err != nil {
return err
}
defer db.Close()

// Perform consistency check.
return db.View(func(tx *bolt.Tx) error {
var count int
for err := range tx.Check(bolt.WithKVStringer(CmdKvStringer())) {
fmt.Fprintln(cmd.Stdout, err)
count++
}

// Print summary of errors.
if count > 0 {
fmt.Fprintf(cmd.Stdout, "%d errors found\n", count)
return guts_cli.ErrCorrupt
}

// Notify user that database is valid.
fmt.Fprintln(cmd.Stdout, "OK")
return nil
})
}

// Usage returns the help message.
func (cmd *checkCommand) Usage() string {
return strings.TrimLeft(`
usage: bolt check PATH
Check opens a database at PATH and runs an exhaustive check to verify that
all pages are accessible or are marked as freed. It also verifies that no
pages are double referenced.
Verification errors will stream out as they are found and the process will
return after all pages have been checked.
`, "\n")
}

// infoCommand represents the "info" command execution.
type infoCommand struct {
baseCommand
Expand Down
14 changes: 0 additions & 14 deletions cmd/bbolt/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,6 @@ func TestStatsCommand_Run_EmptyDatabase(t *testing.T) {
}
}

func TestCheckCommand_Run(t *testing.T) {
db := btesting.MustCreateDB(t)
db.Close()

defer requireDBNoChange(t, dbData(t, db.Path()), db.Path())

m := NewMain()
err := m.Run("check", db.Path())
require.NoError(t, err)
if m.Stdout.String() != "OK\n" {
t.Fatalf("unexpected stdout:\n\n%s", m.Stdout.String())
}
}

func TestDumpCommand_Run(t *testing.T) {
db := btesting.MustCreateDBWithOption(t, &bolt.Options{PageSize: 4096})
db.Close()
Expand Down

0 comments on commit c5618e4

Please sign in to comment.