Merge pull request #12 from filecoin-project/test/net

Wire up tests
This commit is contained in:
Łukasz Magiera 2019-07-10 15:10:17 +02:00 committed by GitHub
commit 0aacd4048c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 188 additions and 36 deletions

View File

@ -40,7 +40,7 @@ type API interface {
NetPeers(context.Context) ([]peer.AddrInfo, error) NetPeers(context.Context) ([]peer.AddrInfo, error)
NetConnect(context.Context, peer.AddrInfo) error NetConnect(context.Context, peer.AddrInfo) error
NetAddrsListen(context.Context) (MultiaddrSlice, error) NetAddrsListen(context.Context) (peer.AddrInfo, error)
// // ping // // ping
// Struct // Struct

View File

@ -20,7 +20,7 @@ type Struct struct {
NetPeers func(context.Context) ([]peer.AddrInfo, error) NetPeers func(context.Context) ([]peer.AddrInfo, error)
NetConnect func(context.Context, peer.AddrInfo) error NetConnect func(context.Context, peer.AddrInfo) error
NetAddrsListen func(context.Context) (MultiaddrSlice, error) NetAddrsListen func(context.Context) (peer.AddrInfo, error)
} }
} }
@ -32,7 +32,7 @@ func (c *Struct) NetConnect(ctx context.Context, p peer.AddrInfo) error {
return c.Internal.NetConnect(ctx, p) return c.Internal.NetConnect(ctx, p)
} }
func (c *Struct) NetAddrsListen(ctx context.Context) (MultiaddrSlice, error) { func (c *Struct) NetAddrsListen(ctx context.Context) (peer.AddrInfo, error) {
return c.Internal.NetAddrsListen(ctx) return c.Internal.NetAddrsListen(ctx)
} }

View File

@ -2,6 +2,7 @@ package test
import ( import (
"context" "context"
"strings"
"testing" "testing"
"github.com/filecoin-project/go-lotus/api" "github.com/filecoin-project/go-lotus/api"
@ -10,25 +11,28 @@ import (
// APIBuilder is a function which is invoked in test suite to provide // APIBuilder is a function which is invoked in test suite to provide
// test nodes and networks // test nodes and networks
type APIBuilder func() api.API type APIBuilder func(t *testing.T, n int) []api.API
type testSuite struct { type testSuite struct {
makeNode APIBuilder makeNodes APIBuilder
} }
// TestApis is the entry point to API test suite // TestApis is the entry point to API test suite
func TestApis(t *testing.T, b APIBuilder) { func TestApis(t *testing.T, b APIBuilder) {
ts := testSuite{ ts := testSuite{
makeNode: b, makeNodes: b,
} }
t.Run("version", ts.testVersion) t.Run("version", ts.testVersion)
t.Run("id", ts.testID)
t.Run("testConnectTwo", ts.testConnectTwo)
} }
func (ts *testSuite) testVersion(t *testing.T) { func (ts *testSuite) testVersion(t *testing.T) {
ctx := context.Background() ctx := context.Background()
fc := ts.makeNode() api := ts.makeNodes(t, 1)[0]
v, err := fc.Version(ctx) v, err := api.Version(ctx)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -36,3 +40,62 @@ func (ts *testSuite) testVersion(t *testing.T) {
t.Error("Version didn't work properly") t.Error("Version didn't work properly")
} }
} }
func (ts *testSuite) testID(t *testing.T) {
ctx := context.Background()
api := ts.makeNodes(t, 1)[0]
id, err := api.ID(ctx)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(id.Pretty(), "Qm") {
t.Error("expected identity to be Qm..")
}
}
func (ts *testSuite) testConnectTwo(t *testing.T) {
ctx := context.Background()
apis := ts.makeNodes(t, 2)
p, err := apis[0].NetPeers(ctx)
if err != nil {
t.Fatal(err)
}
if len(p) != 0 {
t.Error("Node 0 has a peer")
}
p, err = apis[1].NetPeers(ctx)
if err != nil {
t.Fatal(err)
}
if len(p) != 0 {
t.Error("Node 1 has a peer")
}
addrs, err := apis[1].NetAddrsListen(ctx)
if err != nil {
t.Fatal(err)
}
if err := apis[0].NetConnect(ctx, addrs); err != nil {
t.Fatal(err)
}
p, err = apis[0].NetPeers(ctx)
if err != nil {
t.Fatal(err)
}
if len(p) != 1 {
t.Error("Node 0 doesn't have 1 peer")
}
p, err = apis[1].NetPeers(ctx)
if err != nil {
t.Fatal(err)
}
if len(p) != 1 {
t.Error("Node 0 doesn't have 1 peer")
}
}

View File

@ -53,8 +53,8 @@ var netListen = &cli.Command{
return err return err
} }
for _, peer := range addrs { for _, peer := range addrs.Addrs {
fmt.Println(peer) fmt.Printf("%s/p2p/%s\n", peer, addrs.ID)
} }
return nil return nil
}, },

View File

@ -64,8 +64,11 @@ func (a *API) NetConnect(ctx context.Context, p peer.AddrInfo) error {
return a.Host.Connect(ctx, p) return a.Host.Connect(ctx, p)
} }
func (a *API) NetAddrsListen(context.Context) (api.MultiaddrSlice, error) { func (a *API) NetAddrsListen(context.Context) (peer.AddrInfo, error) {
return a.Host.Addrs(), nil return peer.AddrInfo{
ID: a.Host.ID(),
Addrs: a.Host.Addrs(),
}, nil
} }
var _ api.API = &API{} var _ api.API = &API{}

View File

@ -68,7 +68,7 @@ const (
_nInvokes // keep this last _nInvokes // keep this last
) )
type settings struct { type Settings struct {
// modules is a map of constructors for DI // modules is a map of constructors for DI
// //
// In most cases the index will be a reflect. Type of element returned by // In most cases the index will be a reflect. Type of element returned by
@ -80,13 +80,13 @@ type settings struct {
// type, and must be applied in correct order // type, and must be applied in correct order
invokes []fx.Option invokes []fx.Option
online bool // Online option applied Online bool // Online option applied
config bool // Config option applied Config bool // Config option applied
} }
// Override option changes constructor for a given type // Override option changes constructor for a given type
func Override(typ, constructor interface{}) Option { func Override(typ, constructor interface{}) Option {
return func(s *settings) error { return func(s *Settings) error {
if i, ok := typ.(invoke); ok { if i, ok := typ.(invoke); ok {
s.invokes[i] = fx.Invoke(constructor) s.invokes[i] = fx.Invoke(constructor)
return nil return nil
@ -106,18 +106,20 @@ func Override(typ, constructor interface{}) Option {
var defConf = config.Default() var defConf = config.Default()
var defaults = []Option{ func defaults() []Option {
Override(new(helpers.MetricsCtx), context.Background), return []Option{
Override(new(helpers.MetricsCtx), context.Background),
randomIdentity(), randomIdentity(),
Override(new(datastore.Batching), testing.MapDatastore), Override(new(datastore.Batching), testing.MapDatastore),
Override(new(blockstore.Blockstore), testing.MapBlockstore), // NOT on top of ds above Override(new(blockstore.Blockstore), testing.MapBlockstore), // NOT on top of ds above
Override(new(record.Validator), modules.RecordValidator), Override(new(record.Validator), modules.RecordValidator),
// Filecoin modules // Filecoin modules
Override(new(*chain.ChainStore), chain.NewChainStore), Override(new(*chain.ChainStore), chain.NewChainStore),
}
} }
// Online sets up basic libp2p node // Online sets up basic libp2p node
@ -125,8 +127,8 @@ func Online() Option {
return Options( return Options(
// make sure that online is applied before Config. // make sure that online is applied before Config.
// This is important because Config overrides some of Online units // This is important because Config overrides some of Online units
func(s *settings) error { s.online = true; return nil }, func(s *Settings) error { s.Online = true; return nil },
applyIf(func(s *settings) bool { return s.config }, ApplyIf(func(s *Settings) bool { return s.Config },
Error(errors.New("the Online option must be set before Config option")), Error(errors.New("the Online option must be set before Config option")),
), ),
@ -180,12 +182,12 @@ func Online() Option {
) )
} }
// Config sets up constructors based on the provided config // Config sets up constructors based on the provided Config
func Config(cfg *config.Root) Option { func Config(cfg *config.Root) Option {
return Options( return Options(
func(s *settings) error { s.config = true; return nil }, func(s *Settings) error { s.Config = true; return nil },
applyIf(func(s *settings) bool { return s.online }, ApplyIf(func(s *Settings) bool { return s.Online },
Override(StartListeningKey, lp2p.StartListening(cfg.Libp2p.ListenAddresses)), Override(StartListeningKey, lp2p.StartListening(cfg.Libp2p.ListenAddresses)),
), ),
) )
@ -194,13 +196,13 @@ func Config(cfg *config.Root) Option {
// New builds and starts new Filecoin node // New builds and starts new Filecoin node
func New(ctx context.Context, opts ...Option) (api.API, error) { func New(ctx context.Context, opts ...Option) (api.API, error) {
resAPI := &API{} resAPI := &API{}
settings := settings{ settings := Settings{
modules: map[interface{}]fx.Option{}, modules: map[interface{}]fx.Option{},
invokes: make([]fx.Option, _nInvokes), invokes: make([]fx.Option, _nInvokes),
} }
// apply module options in the right order // apply module options in the right order
if err := Options(Options(defaults...), Options(opts...))(&settings); err != nil { if err := Options(Options(defaults()...), Options(opts...))(&settings); err != nil {
return nil, err return nil, err
} }
@ -222,6 +224,8 @@ func New(ctx context.Context, opts ...Option) (api.API, error) {
fx.Options(settings.invokes...), fx.Options(settings.invokes...),
fx.Extract(resAPI), fx.Extract(resAPI),
fx.NopLogger,
) )
// TODO: we probably should have a 'firewall' for Closing signal // TODO: we probably should have a 'firewall' for Closing signal

62
node/node_test.go Normal file
View File

@ -0,0 +1,62 @@
package node_test
import (
"context"
"github.com/filecoin-project/go-lotus/node"
"net/http/httptest"
"testing"
"github.com/filecoin-project/go-lotus/api"
"github.com/filecoin-project/go-lotus/api/client"
"github.com/filecoin-project/go-lotus/api/test"
"github.com/filecoin-project/go-lotus/lib/jsonrpc"
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
)
func builder(t *testing.T, n int) []api.API {
ctx := context.Background()
mn := mocknet.New(ctx)
out := make([]api.API, n)
for i := 0; i < n; i++ {
var err error
out[i], err = node.New(ctx,
node.Online(),
MockHost(mn),
)
if err != nil {
t.Fatal(err)
}
}
if err := mn.LinkAll(); err != nil {
t.Fatal(err)
}
return out
}
func TestAPI(t *testing.T) {
test.TestApis(t, builder)
}
var nextApi int
func rpcBuilder(t *testing.T, n int) []api.API {
nodeApis := builder(t, n)
out := make([]api.API, n)
for i, a := range nodeApis {
rpcServer := jsonrpc.NewServer()
rpcServer.Register("Filecoin", a)
testServ := httptest.NewServer(rpcServer) // todo: close
out[i] = client.NewRPC(testServ.URL)
}
return out
}
func TestAPIRPC(t *testing.T) {
test.TestApis(t, rpcBuilder)
}

View File

@ -8,11 +8,11 @@ import (
// change how the node is constructed // change how the node is constructed
// //
// Options are applied in sequence // Options are applied in sequence
type Option func(*settings) error type Option func(*Settings) error
// Options groups multiple options into one // Options groups multiple options into one
func Options(opts ...Option) Option { func Options(opts ...Option) Option {
return func(s *settings) error { return func(s *Settings) error {
for _, opt := range opts { for _, opt := range opts {
if err := opt(s); err != nil { if err := opt(s); err != nil {
return err return err
@ -24,13 +24,13 @@ func Options(opts ...Option) Option {
// Error is a special option which returns an error when applied // Error is a special option which returns an error when applied
func Error(err error) Option { func Error(err error) Option {
return func(_ *settings) error { return func(_ *Settings) error {
return err return err
} }
} }
func applyIf(check func(s *settings) bool, opts ...Option) Option { func ApplyIf(check func(s *Settings) bool, opts ...Option) Option {
return func(s *settings) error { return func(s *Settings) error {
if check(s) { if check(s) {
return Options(opts...)(s) return Options(opts...)(s)
} }

20
node/opts_test.go Normal file
View File

@ -0,0 +1,20 @@
package node_test
import (
"errors"
"github.com/filecoin-project/go-lotus/node"
"github.com/filecoin-project/go-lotus/node/modules/lp2p"
mocknet "github.com/libp2p/go-libp2p/p2p/net/mock"
)
func MockHost(mn mocknet.Mocknet) node.Option {
return node.Options(
node.ApplyIf(func(s *node.Settings) bool { return !s.Online },
node.Error(errors.New("MockHost must be specified after Online")),
),
node.Override(new(lp2p.RawHost), lp2p.MockHost),
node.Override(new(mocknet.Mocknet), mn),
)
}