2022-02-08 16:15:45 +00:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
2022-02-08 17:24:45 +00:00
|
|
|
"encoding/json"
|
2022-02-09 14:29:10 +00:00
|
|
|
"fmt"
|
2022-02-08 16:15:45 +00:00
|
|
|
"regexp"
|
2022-02-09 15:22:52 +00:00
|
|
|
"strings"
|
2022-02-08 16:15:45 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
|
|
"github.com/filecoin-project/lotus/api/mocks"
|
2022-02-08 17:24:45 +00:00
|
|
|
types "github.com/filecoin-project/lotus/chain/types"
|
2022-02-08 16:15:45 +00:00
|
|
|
"github.com/filecoin-project/lotus/chain/types/mock"
|
|
|
|
"github.com/golang/mock/gomock"
|
2022-02-08 17:24:45 +00:00
|
|
|
cid "github.com/ipfs/go-cid"
|
2022-02-08 16:15:45 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
ucli "github.com/urfave/cli/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// newMockAppWithFullAPI returns a gomock-ed CLI app used for unit tests
|
|
|
|
// see cli/util/api.go:GetFullNodeAPI for mock API injection
|
|
|
|
func newMockAppWithFullAPI(t *testing.T, cmd *ucli.Command) (*ucli.App, *mocks.MockFullNode, *bytes.Buffer, func()) {
|
|
|
|
app := ucli.NewApp()
|
|
|
|
app.Commands = ucli.Commands{cmd}
|
|
|
|
app.Setup()
|
|
|
|
|
|
|
|
// create and inject the mock API into app Metadata
|
|
|
|
ctrl := gomock.NewController(t)
|
|
|
|
mockFullNode := mocks.NewMockFullNode(ctrl)
|
|
|
|
var fullNode api.FullNode = mockFullNode
|
|
|
|
app.Metadata["test-full-api"] = fullNode
|
|
|
|
|
|
|
|
// this will only work if the implementation uses the app.Writer,
|
|
|
|
// if it uses fmt.*, it has to be refactored
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
app.Writer = buf
|
|
|
|
|
|
|
|
return app, mockFullNode, buf, ctrl.Finish
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestChainHead(t *testing.T) {
|
|
|
|
app, mockApi, buf, done := newMockAppWithFullAPI(t, WithCategory("chain", ChainHeadCmd))
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
ts := mock.TipSet(mock.MkBlock(nil, 0, 0))
|
|
|
|
gomock.InOrder(
|
|
|
|
mockApi.EXPECT().ChainHead(ctx).Return(ts, nil),
|
|
|
|
)
|
|
|
|
|
|
|
|
err := app.Run([]string{"chain", "head"})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Regexp(t, regexp.MustCompile(ts.Cids()[0].String()), buf.String())
|
|
|
|
}
|
2022-02-08 17:24:45 +00:00
|
|
|
|
2022-02-09 14:29:10 +00:00
|
|
|
// TestGetBlock checks if "chain getblock" returns the block information in the expected format
|
2022-02-08 17:24:45 +00:00
|
|
|
func TestGetBlock(t *testing.T) {
|
|
|
|
app, mockApi, buf, done := newMockAppWithFullAPI(t, WithCategory("chain", ChainGetBlock))
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
block := mock.MkBlock(nil, 0, 0)
|
|
|
|
blockMsgs := api.BlockMessages{}
|
|
|
|
|
|
|
|
gomock.InOrder(
|
|
|
|
mockApi.EXPECT().ChainGetBlock(ctx, block.Cid()).Return(block, nil),
|
|
|
|
mockApi.EXPECT().ChainGetBlockMessages(ctx, block.Cid()).Return(&blockMsgs, nil),
|
|
|
|
mockApi.EXPECT().ChainGetParentMessages(ctx, block.Cid()).Return([]api.Message{}, nil),
|
|
|
|
mockApi.EXPECT().ChainGetParentReceipts(ctx, block.Cid()).Return([]*types.MessageReceipt{}, nil),
|
|
|
|
)
|
|
|
|
|
|
|
|
err := app.Run([]string{"chain", "getblock", block.Cid().String()})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
2022-02-09 14:29:10 +00:00
|
|
|
// expected output format
|
2022-02-08 17:24:45 +00:00
|
|
|
out := struct {
|
|
|
|
types.BlockHeader
|
|
|
|
BlsMessages []*types.Message
|
|
|
|
SecpkMessages []*types.SignedMessage
|
|
|
|
ParentReceipts []*types.MessageReceipt
|
|
|
|
ParentMessages []cid.Cid
|
|
|
|
}{}
|
|
|
|
|
|
|
|
err = json.Unmarshal(buf.Bytes(), &out)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
assert.True(t, block.Cid().Equals(out.Cid()))
|
|
|
|
}
|
2022-02-09 14:29:10 +00:00
|
|
|
|
|
|
|
// TestChainReadObj checks if "chain read-obj" prints the referenced IPLD node as hex, if exists
|
|
|
|
func TestReadOjb(t *testing.T) {
|
|
|
|
app, mockApi, buf, done := newMockAppWithFullAPI(t, WithCategory("chain", ChainReadObjCmd))
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
block := mock.MkBlock(nil, 0, 0)
|
|
|
|
obj := new(bytes.Buffer)
|
|
|
|
err := block.MarshalCBOR(obj)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
gomock.InOrder(
|
|
|
|
mockApi.EXPECT().ChainReadObj(ctx, block.Cid()).Return(obj.Bytes(), nil),
|
|
|
|
)
|
|
|
|
|
|
|
|
err = app.Run([]string{"chain", "read-obj", block.Cid().String()})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Equal(t, buf.String(), fmt.Sprintf("%x\n", obj.Bytes()))
|
|
|
|
}
|
2022-02-09 14:56:13 +00:00
|
|
|
|
|
|
|
// TestChainDeleteObj checks if "chain delete-obj" deletes an object from the chain blockstore, respecting the --really-do-it flag
|
|
|
|
func TestChainDeleteObj(t *testing.T) {
|
|
|
|
cmd := WithCategory("chain", ChainDeleteObjCmd)
|
|
|
|
block := mock.MkBlock(nil, 0, 0)
|
|
|
|
|
|
|
|
// given no force flag, it should return an error and no API calls should be made
|
|
|
|
t.Run("no-really-do-it", func(t *testing.T) {
|
|
|
|
app, _, _, done := newMockAppWithFullAPI(t, cmd)
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
err := app.Run([]string{"chain", "delete-obj", block.Cid().String()})
|
|
|
|
assert.Error(t, err)
|
|
|
|
})
|
|
|
|
|
2022-02-09 15:22:52 +00:00
|
|
|
// given a force flag, it calls API delete
|
2022-02-09 14:56:13 +00:00
|
|
|
t.Run("really-do-it", func(t *testing.T) {
|
|
|
|
app, mockApi, buf, done := newMockAppWithFullAPI(t, cmd)
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
gomock.InOrder(
|
|
|
|
mockApi.EXPECT().ChainDeleteObj(ctx, block.Cid()).Return(nil),
|
|
|
|
)
|
|
|
|
|
|
|
|
err := app.Run([]string{"chain", "delete-obj", "--really-do-it=true", block.Cid().String()})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
assert.Contains(t, buf.String(), block.Cid().String())
|
|
|
|
})
|
|
|
|
}
|
2022-02-09 15:22:52 +00:00
|
|
|
|
|
|
|
func TestChainStatObj(t *testing.T) {
|
|
|
|
cmd := WithCategory("chain", ChainStatObjCmd)
|
|
|
|
block := mock.MkBlock(nil, 0, 0)
|
|
|
|
stat := api.ObjStat{Size: 123, Links: 321}
|
|
|
|
|
|
|
|
checkOutput := func(buf *bytes.Buffer) {
|
|
|
|
out := buf.String()
|
|
|
|
outSplit := strings.Split(out, "\n")
|
|
|
|
|
|
|
|
assert.Contains(t, outSplit[0], fmt.Sprintf("%d", stat.Links))
|
|
|
|
assert.Contains(t, outSplit[1], fmt.Sprintf("%d", stat.Size))
|
|
|
|
}
|
|
|
|
|
|
|
|
// given no --base flag, it calls ChainStatObj with base=cid.Undef
|
|
|
|
t.Run("no-base", func(t *testing.T) {
|
|
|
|
app, mockApi, buf, done := newMockAppWithFullAPI(t, cmd)
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
gomock.InOrder(
|
|
|
|
mockApi.EXPECT().ChainStatObj(ctx, block.Cid(), cid.Undef).Return(stat, nil),
|
|
|
|
)
|
|
|
|
|
|
|
|
err := app.Run([]string{"chain", "stat-obj", block.Cid().String()})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
checkOutput(buf)
|
|
|
|
})
|
|
|
|
|
|
|
|
// given a --base flag, it calls ChainStatObj with that base
|
|
|
|
t.Run("base", func(t *testing.T) {
|
|
|
|
app, mockApi, buf, done := newMockAppWithFullAPI(t, cmd)
|
|
|
|
defer done()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
gomock.InOrder(
|
|
|
|
mockApi.EXPECT().ChainStatObj(ctx, block.Cid(), block.Cid()).Return(stat, nil),
|
|
|
|
)
|
|
|
|
|
|
|
|
err := app.Run([]string{"chain", "stat-obj", fmt.Sprintf("-base=%s", block.Cid().String()), block.Cid().String()})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
checkOutput(buf)
|
|
|
|
})
|
|
|
|
}
|