diff --git a/itests/batch_deal_test.go b/itests/batch_deal_test.go index 9676dffcc..9cc4d7ac1 100644 --- a/itests/batch_deal_test.go +++ b/itests/batch_deal_test.go @@ -10,16 +10,15 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" "github.com/filecoin-project/lotus/markets/storageadapter" "github.com/filecoin-project/lotus/node" - "github.com/filecoin-project/lotus/node/impl" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/stretchr/testify/require" ) func TestBatchDealInput(t *testing.T) { - kit.QuietMiningLogs() + kit2.QuietMiningLogs() var ( blockTime = 10 * time.Millisecond @@ -32,50 +31,40 @@ func TestBatchDealInput(t *testing.T) { run := func(piece, deals, expectSectors int) func(t *testing.T) { return func(t *testing.T) { + ctx := context.Background() + publishPeriod := 10 * time.Second maxDealsPerMsg := uint64(deals) // Set max deals per publish deals message to maxDealsPerMsg - minerDef := []kit.StorageMiner{{ - Full: 0, - Opts: node.Options( - node.Override( - new(*storageadapter.DealPublisher), - storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ - Period: publishPeriod, - MaxDealsPerMsg: maxDealsPerMsg, - })), - node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { - return func() (sealiface.Config, error) { - return sealiface.Config{ - MaxWaitDealsSectors: 2, - MaxSealingSectors: 1, - MaxSealingSectorsForDeals: 3, - AlwaysKeepUnsealedCopy: true, - WaitDealsDelay: time.Hour, - }, nil + opts := kit2.ConstructorOpts(node.Options( + node.Override( + new(*storageadapter.DealPublisher), + storageadapter.NewDealPublisher(nil, storageadapter.PublishMsgConfig{ + Period: publishPeriod, + MaxDealsPerMsg: maxDealsPerMsg, + })), + node.Override(new(dtypes.GetSealingConfigFunc), func() (dtypes.GetSealingConfigFunc, error) { + return func() (sealiface.Config, error) { + return sealiface.Config{ + MaxWaitDealsSectors: 2, + MaxSealingSectors: 1, + MaxSealingSectorsForDeals: 3, + AlwaysKeepUnsealedCopy: true, + WaitDealsDelay: time.Hour, }, nil - }), - ), - Preseal: kit.PresealGenesis, - }} - - // Create a connect client and miner node - n, sn := kit.MockMinerBuilder(t, kit.OneFull, minerDef) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - blockMiner := kit.ConnectAndStartMining(t, blockTime, miner, client) - t.Cleanup(blockMiner.Stop) - - dh := kit.NewDealHarness(t, client, miner) - ctx := context.Background() + }, nil + }), + )) + client, miner, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), opts) + ens.InterconnectAll().BeginMining(blockTime) + dh := kit2.NewDealHarness(t, client, miner) err := miner.MarketSetAsk(ctx, big.Zero(), big.Zero(), 200, 128, 32<<30) require.NoError(t, err) checkNoPadding := func() { - sl, err := sn[0].SectorsList(ctx) + sl, err := miner.SectorsList(ctx) require.NoError(t, err) sort.Slice(sl, func(i, j int) bool { @@ -83,7 +72,7 @@ func TestBatchDealInput(t *testing.T) { }) for _, snum := range sl { - si, err := sn[0].SectorsStatus(ctx, snum, false) + si, err := miner.SectorsStatus(ctx, snum, false) require.NoError(t, err) // fmt.Printf("S %d: %+v %s\n", snum, si.Deals, si.State) @@ -98,7 +87,7 @@ func TestBatchDealInput(t *testing.T) { // Starts a deal and waits until it's published runDealTillSeal := func(rseed int) { - res, _, _, err := kit.CreateImportFile(ctx, client, rseed, piece) + res, _, _, err := kit2.CreateImportFile(ctx, client, rseed, piece) require.NoError(t, err) deal := dh.StartDeal(ctx, res.Root, false, dealStartEpoch) @@ -122,7 +111,7 @@ func TestBatchDealInput(t *testing.T) { checkNoPadding() - sl, err := sn[0].SectorsList(ctx) + sl, err := miner.SectorsList(ctx) require.NoError(t, err) require.Equal(t, len(sl), expectSectors) } diff --git a/itests/cli_test.go b/itests/cli_test.go index 10e2af15c..8436f189e 100644 --- a/itests/cli_test.go +++ b/itests/cli_test.go @@ -1,22 +1,21 @@ package itests import ( - "context" "os" "testing" "time" "github.com/filecoin-project/lotus/cli" - "github.com/filecoin-project/lotus/itests/kit" + "github.com/filecoin-project/lotus/itests/kit2" ) // TestClient does a basic test to exercise the client CLI commands. func TestClient(t *testing.T) { _ = os.Setenv("BELLMAN_NO_GPU", "1") - kit.QuietMiningLogs() + kit2.QuietMiningLogs() - blocktime := 5 * time.Millisecond - ctx := context.Background() - clientNode, _ := kit.StartOneNodeOneMiner(ctx, t, blocktime) - kit.RunClientTest(t, cli.Commands, clientNode) + blockTime := 5 * time.Millisecond + client, _, ens := kit2.EnsembleMinimal(t, kit2.MockProofs(), kit2.ThroughRPC()) + ens.InterconnectAll().BeginMining(blockTime) + kit2.RunClientTest(t, cli.Commands, *client) } diff --git a/itests/kit2/client.go b/itests/kit2/client.go new file mode 100644 index 000000000..247d20836 --- /dev/null +++ b/itests/kit2/client.go @@ -0,0 +1,146 @@ +package kit2 + +import ( + "context" + "fmt" + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/specs-actors/v2/actors/builtin" + "github.com/stretchr/testify/require" + lcli "github.com/urfave/cli/v2" +) + +// RunClientTest exercises some of the Client CLI commands +func RunClientTest(t *testing.T, cmds []*lcli.Command, clientNode TestFullNode) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + // Create mock CLI + mockCLI := NewMockCLI(ctx, t, cmds) + clientCLI := mockCLI.Client(clientNode.ListenAddr) + + // Get the Miner address + addrs, err := clientNode.StateListMiners(ctx, types.EmptyTSK) + require.NoError(t, err) + require.Len(t, addrs, 1) + + minerAddr := addrs[0] + fmt.Println("Miner:", minerAddr) + + // client query-ask + out := clientCLI.RunCmd("client", "query-ask", minerAddr.String()) + require.Regexp(t, regexp.MustCompile("Ask:"), out) + + // Create a deal (non-interactive) + // client deal --start-epoch= 1000000attofil + res, _, _, err := CreateImportFile(ctx, clientNode, 1, 0) + + require.NoError(t, err) + startEpoch := fmt.Sprintf("--start-epoch=%d", 2<<12) + dataCid := res.Root + price := "1000000attofil" + duration := fmt.Sprintf("%d", build.MinDealDuration) + out = clientCLI.RunCmd("client", "deal", startEpoch, dataCid.String(), minerAddr.String(), price, duration) + fmt.Println("client deal", out) + + // Create a deal (interactive) + // client deal + // + // (in days) + // + // "no" (verified Client) + // "yes" (confirm deal) + res, _, _, err = CreateImportFile(ctx, clientNode, 2, 0) + require.NoError(t, err) + dataCid2 := res.Root + duration = fmt.Sprintf("%d", build.MinDealDuration/builtin.EpochsInDay) + cmd := []string{"client", "deal"} + interactiveCmds := []string{ + dataCid2.String(), + duration, + minerAddr.String(), + "no", + "yes", + } + out = clientCLI.RunInteractiveCmd(cmd, interactiveCmds) + fmt.Println("client deal:\n", out) + + // Wait for provider to start sealing deal + dealStatus := "" + for { + // client list-deals + out = clientCLI.RunCmd("client", "list-deals") + fmt.Println("list-deals:\n", out) + + lines := strings.Split(out, "\n") + require.GreaterOrEqual(t, len(lines), 2) + re := regexp.MustCompile(`\s+`) + parts := re.Split(lines[1], -1) + if len(parts) < 4 { + require.Fail(t, "bad list-deals output format") + } + dealStatus = parts[3] + fmt.Println(" Deal status:", dealStatus) + + st := CategorizeDealState(dealStatus) + require.NotEqual(t, TestDealStateFailed, st) + if st == TestDealStateComplete { + break + } + + time.Sleep(time.Second) + } + + // Retrieve the first file from the Miner + // client retrieve + tmpdir, err := ioutil.TempDir(os.TempDir(), "test-cli-Client") + require.NoError(t, err) + path := filepath.Join(tmpdir, "outfile.dat") + out = clientCLI.RunCmd("client", "retrieve", dataCid.String(), path) + fmt.Println("retrieve:\n", out) + require.Regexp(t, regexp.MustCompile("Success"), out) +} + +func CreateImportFile(ctx context.Context, client api.FullNode, rseed int, size int) (res *api.ImportRes, path string, data []byte, err error) { + data, path, err = createRandomFile(rseed, size) + if err != nil { + return nil, "", nil, err + } + + res, err = client.ClientImport(ctx, api.FileRef{Path: path}) + if err != nil { + return nil, "", nil, err + } + return res, path, data, nil +} + +func createRandomFile(rseed, size int) ([]byte, string, error) { + if size == 0 { + size = 1600 + } + data := make([]byte, size) + rand.New(rand.NewSource(int64(rseed))).Read(data) + + dir, err := ioutil.TempDir(os.TempDir(), "test-make-deal-") + if err != nil { + return nil, "", err + } + + path := filepath.Join(dir, "sourcefile.dat") + err = ioutil.WriteFile(path, data, 0644) + if err != nil { + return nil, "", err + } + + return data, path, nil +} diff --git a/itests/kit2/mockcli.go b/itests/kit2/mockcli.go new file mode 100644 index 000000000..592c97333 --- /dev/null +++ b/itests/kit2/mockcli.go @@ -0,0 +1,141 @@ +package kit2 + +import ( + "bytes" + "context" + "flag" + "strings" + "testing" + + "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" + lcli "github.com/urfave/cli/v2" +) + +type MockCLI struct { + t *testing.T + cmds []*lcli.Command + cctx *lcli.Context + out *bytes.Buffer +} + +func NewMockCLI(ctx context.Context, t *testing.T, cmds []*lcli.Command) *MockCLI { + // Create a CLI App with an --api-url flag so that we can specify which node + // the command should be executed against + app := &lcli.App{ + Flags: []lcli.Flag{ + &lcli.StringFlag{ + Name: "api-url", + Hidden: true, + }, + }, + Commands: cmds, + } + + var out bytes.Buffer + app.Writer = &out + app.Setup() + + cctx := lcli.NewContext(app, &flag.FlagSet{}, nil) + cctx.Context = ctx + return &MockCLI{t: t, cmds: cmds, cctx: cctx, out: &out} +} + +func (c *MockCLI) Client(addr multiaddr.Multiaddr) *MockCLIClient { + return &MockCLIClient{t: c.t, cmds: c.cmds, addr: addr, cctx: c.cctx, out: c.out} +} + +// MockCLIClient runs commands against a particular node +type MockCLIClient struct { + t *testing.T + cmds []*lcli.Command + addr multiaddr.Multiaddr + cctx *lcli.Context + out *bytes.Buffer +} + +func (c *MockCLIClient) RunCmd(input ...string) string { + out, err := c.RunCmdRaw(input...) + require.NoError(c.t, err, "output:\n%s", out) + + return out +} + +// Given an input, find the corresponding command or sub-command. +// eg "paych add-funds" +func (c *MockCLIClient) cmdByNameSub(input []string) (*lcli.Command, []string) { + name := input[0] + for _, cmd := range c.cmds { + if cmd.Name == name { + return c.findSubcommand(cmd, input[1:]) + } + } + return nil, []string{} +} + +func (c *MockCLIClient) findSubcommand(cmd *lcli.Command, input []string) (*lcli.Command, []string) { + // If there are no sub-commands, return the current command + if len(cmd.Subcommands) == 0 { + return cmd, input + } + + // Check each sub-command for a match against the name + subName := input[0] + for _, subCmd := range cmd.Subcommands { + if subCmd.Name == subName { + // Found a match, recursively search for sub-commands + return c.findSubcommand(subCmd, input[1:]) + } + } + return nil, []string{} +} + +func (c *MockCLIClient) RunCmdRaw(input ...string) (string, error) { + cmd, input := c.cmdByNameSub(input) + if cmd == nil { + panic("Could not find command " + input[0] + " " + input[1]) + } + + // prepend --api-url= + apiFlag := "--api-url=" + c.addr.String() + input = append([]string{apiFlag}, input...) + + fs := c.flagSet(cmd) + err := fs.Parse(input) + require.NoError(c.t, err) + + err = cmd.Action(lcli.NewContext(c.cctx.App, fs, c.cctx)) + + // Get the output + str := strings.TrimSpace(c.out.String()) + c.out.Reset() + return str, err +} + +func (c *MockCLIClient) flagSet(cmd *lcli.Command) *flag.FlagSet { + // Apply app level flags (so we can process --api-url flag) + fs := &flag.FlagSet{} + for _, f := range c.cctx.App.Flags { + err := f.Apply(fs) + if err != nil { + c.t.Fatal(err) + } + } + // Apply command level flags + for _, f := range cmd.Flags { + err := f.Apply(fs) + if err != nil { + c.t.Fatal(err) + } + } + return fs +} + +func (c *MockCLIClient) RunInteractiveCmd(cmd []string, interactive []string) string { + c.toStdin(strings.Join(interactive, "\n") + "\n") + return c.RunCmd(cmd...) +} + +func (c *MockCLIClient) toStdin(s string) { + c.cctx.App.Metadata["stdin"] = bytes.NewBufferString(s) +}