diff --git a/cli/sync.go b/cli/sync.go index c7b010111..0c4984379 100644 --- a/cli/sync.go +++ b/cli/sync.go @@ -33,6 +33,8 @@ var SyncStatusCmd = &cli.Command{ Name: "status", Usage: "check sync status", Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + apic, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -45,9 +47,9 @@ var SyncStatusCmd = &cli.Command{ return err } - fmt.Println("sync status:") + afmt.Println("sync status:") for _, ss := range state.ActiveSyncs { - fmt.Printf("worker %d:\n", ss.WorkerID) + afmt.Printf("worker %d:\n", ss.WorkerID) var base, target []cid.Cid var heightDiff int64 var theight abi.ChainEpoch @@ -62,20 +64,20 @@ var SyncStatusCmd = &cli.Command{ } else { heightDiff = 0 } - fmt.Printf("\tBase:\t%s\n", base) - fmt.Printf("\tTarget:\t%s (%d)\n", target, theight) - fmt.Printf("\tHeight diff:\t%d\n", heightDiff) - fmt.Printf("\tStage: %s\n", ss.Stage) - fmt.Printf("\tHeight: %d\n", ss.Height) + afmt.Printf("\tBase:\t%s\n", base) + afmt.Printf("\tTarget:\t%s (%d)\n", target, theight) + afmt.Printf("\tHeight diff:\t%d\n", heightDiff) + afmt.Printf("\tStage: %s\n", ss.Stage) + afmt.Printf("\tHeight: %d\n", ss.Height) if ss.End.IsZero() { if !ss.Start.IsZero() { - fmt.Printf("\tElapsed: %s\n", time.Since(ss.Start)) + afmt.Printf("\tElapsed: %s\n", time.Since(ss.Start)) } } else { - fmt.Printf("\tElapsed: %s\n", ss.End.Sub(ss.Start)) + afmt.Printf("\tElapsed: %s\n", ss.End.Sub(ss.Start)) } if ss.Stage == api.StageSyncErrored { - fmt.Printf("\tError: %s\n", ss.Message) + afmt.Printf("\tError: %s\n", ss.Message) } } return nil @@ -168,6 +170,8 @@ var SyncCheckBadCmd = &cli.Command{ Usage: "check if the given block was marked bad, and for what reason", ArgsUsage: "[blockCid]", Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + napi, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -190,11 +194,11 @@ var SyncCheckBadCmd = &cli.Command{ } if reason == "" { - fmt.Println("block was not marked as bad") + afmt.Println("block was not marked as bad") return nil } - fmt.Println(reason) + afmt.Println(reason) return nil }, } diff --git a/cli/sync_test.go b/cli/sync_test.go new file mode 100644 index 000000000..90f20a029 --- /dev/null +++ b/cli/sync_test.go @@ -0,0 +1,189 @@ +package cli + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/mock" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestSyncStatus(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("sync", SyncStatusCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ts1 := mock.TipSet(mock.MkBlock(nil, 0, 0)) + ts2 := mock.TipSet(mock.MkBlock(ts1, 0, 0)) + + start := time.Now() + end := start.Add(time.Minute) + + state := &api.SyncState{ + ActiveSyncs: []api.ActiveSync{{ + WorkerID: 1, + Base: ts1, + Target: ts2, + Stage: api.StageMessages, + Height: abi.ChainEpoch(0), + Start: start, + End: end, + Message: "whatever", + }}, + VMApplied: 0, + } + + mockApi.EXPECT().SyncState(ctx).Return(state, nil) + + //stm: @CLI_SYNC_STATUS_001 + err := app.Run([]string{"sync", "status"}) + assert.NoError(t, err) + + out := buf.String() + + // output is plaintext, had to do string matching + assert.Contains(t, out, fmt.Sprintf("Base:\t[%s]", ts1.Blocks()[0].Cid().String())) + assert.Contains(t, out, fmt.Sprintf("Target:\t[%s]", ts2.Blocks()[0].Cid().String())) + assert.Contains(t, out, "Height diff:\t1") + assert.Contains(t, out, "Stage: message sync") + assert.Contains(t, out, "Height: 0") + assert.Contains(t, out, "Elapsed: 1m0s") +} + +func TestSyncMarkBad(t *testing.T) { + app, mockApi, _, done := NewMockAppWithFullAPI(t, WithCategory("sync", SyncMarkBadCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + blk := mock.MkBlock(nil, 0, 0) + + mockApi.EXPECT().SyncMarkBad(ctx, blk.Cid()).Return(nil) + + //stm: @CLI_SYNC_MARK_BAD_001 + err := app.Run([]string{"sync", "mark-bad", blk.Cid().String()}) + assert.NoError(t, err) +} + +func TestSyncUnmarkBad(t *testing.T) { + t.Run("one-block", func(t *testing.T) { + app, mockApi, _, done := NewMockAppWithFullAPI(t, WithCategory("sync", SyncUnmarkBadCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + blk := mock.MkBlock(nil, 0, 0) + + mockApi.EXPECT().SyncUnmarkBad(ctx, blk.Cid()).Return(nil) + + //stm: @CLI_SYNC_UNMARK_BAD_001 + err := app.Run([]string{"sync", "unmark-bad", blk.Cid().String()}) + assert.NoError(t, err) + }) + + t.Run("all", func(t *testing.T) { + app, mockApi, _, done := NewMockAppWithFullAPI(t, WithCategory("sync", SyncUnmarkBadCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + mockApi.EXPECT().SyncUnmarkAllBad(ctx).Return(nil) + + //stm: @CLI_SYNC_UNMARK_BAD_002 + err := app.Run([]string{"sync", "unmark-bad", "-all"}) + assert.NoError(t, err) + }) +} + +func TestSyncCheckBad(t *testing.T) { + t.Run("not-bad", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("sync", SyncCheckBadCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + blk := mock.MkBlock(nil, 0, 0) + + mockApi.EXPECT().SyncCheckBad(ctx, blk.Cid()).Return("", nil) + + //stm: @CLI_SYNC_CHECK_BAD_002 + err := app.Run([]string{"sync", "check-bad", blk.Cid().String()}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), "block was not marked as bad") + }) + + t.Run("bad", func(t *testing.T) { + app, mockApi, buf, done := NewMockAppWithFullAPI(t, WithCategory("sync", SyncCheckBadCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + blk := mock.MkBlock(nil, 0, 0) + reason := "whatever" + + mockApi.EXPECT().SyncCheckBad(ctx, blk.Cid()).Return(reason, nil) + + //stm: @CLI_SYNC_CHECK_BAD_001 + err := app.Run([]string{"sync", "check-bad", blk.Cid().String()}) + assert.NoError(t, err) + + assert.Contains(t, buf.String(), reason) + }) +} + +func TestSyncCheckpoint(t *testing.T) { + t.Run("tipset", func(t *testing.T) { + app, mockApi, _, done := NewMockAppWithFullAPI(t, WithCategory("sync", SyncCheckpointCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + blk := mock.MkBlock(nil, 0, 0) + ts := mock.TipSet(blk) + + gomock.InOrder( + mockApi.EXPECT().ChainGetBlock(ctx, blk.Cid()).Return(blk, nil), + mockApi.EXPECT().SyncCheckpoint(ctx, ts.Key()).Return(nil), + ) + + //stm: @CLI_SYNC_CHECKPOINT_001 + err := app.Run([]string{"sync", "checkpoint", blk.Cid().String()}) + assert.NoError(t, err) + }) + + t.Run("epoch", func(t *testing.T) { + app, mockApi, _, done := NewMockAppWithFullAPI(t, WithCategory("sync", SyncCheckpointCmd)) + defer done() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + epoch := abi.ChainEpoch(0) + blk := mock.MkBlock(nil, 0, 0) + ts := mock.TipSet(blk) + + gomock.InOrder( + mockApi.EXPECT().ChainGetTipSetByHeight(ctx, epoch, types.EmptyTSK).Return(ts, nil), + mockApi.EXPECT().SyncCheckpoint(ctx, ts.Key()).Return(nil), + ) + + //stm: @CLI_SYNC_CHECKPOINT_002 + err := app.Run([]string{"sync", "checkpoint", fmt.Sprintf("-epoch=%d", epoch)}) + assert.NoError(t, err) + }) +}