Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd: migrate check command to cobra style #723

Merged
merged 1 commit into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions cmd/bbolt/command_check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package main

import (
"fmt"

"github.com/spf13/cobra"

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

func newCheckCommand() *cobra.Command {
checkCmd := &cobra.Command{
Use: "check <bbolt-file>",
Short: "verify integrity of bbolt database data",
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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while we're at it, can you add some test that exercises the corruption?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR just migrates the check command to cobra style. This comment is an enhancement request. It's OK to discuss it separately.

I think the purpose of CLI's test cases are just to verify the CLI itself instead of the functionalities. Also we already some test cases to verify the corrupted pages. FYI. https://github.com/etcd-io/bbolt/blob/main/tx_check_test.go

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes the corruption check has been test already, this test case just to verify the cli is working as expected 👍🏽

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, part of testing that CLI is also ensuring this case works. Feel free to do it as a follow-up PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am confused now, so is this

This comment is an enhancement request

or just adding new test for the corruption check, because we already have tests for this https://github.com/etcd-io/bbolt/blob/main/tx_check_test.go

just to make sure we are all on the same page

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(),
newCheckCommand(),
)

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
Loading