github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/p2p/test/p2ptest.go (about) 1 // Package p2ptest defines utilities for qri peer-2-peer testing 2 package p2ptest 3 4 import ( 5 "context" 6 "fmt" 7 "io/ioutil" 8 "path/filepath" 9 "sync" 10 11 "github.com/ipfs/go-cid" 12 coreiface "github.com/ipfs/interface-go-ipfs-core" 13 host "github.com/libp2p/go-libp2p-core/host" 14 peer "github.com/libp2p/go-libp2p-core/peer" 15 ma "github.com/multiformats/go-multiaddr" 16 "github.com/qri-io/dag" 17 testkeys "github.com/qri-io/qri/auth/key/test" 18 "github.com/qri-io/qri/config" 19 "github.com/qri-io/qri/event" 20 "github.com/qri-io/qri/profile" 21 "github.com/qri-io/qri/repo" 22 reporef "github.com/qri-io/qri/repo/ref" 23 "github.com/qri-io/qri/repo/test" 24 ) 25 26 // TestablePeerNode is used by tests only. Implemented by QriNode 27 type TestablePeerNode interface { 28 Host() host.Host 29 SimpleAddrInfo() peer.AddrInfo 30 GoOnline(ctx context.Context) error 31 } 32 33 // NodeMakerFunc is a function that constructs a Node from a Repo and options. 34 type NodeMakerFunc func(repo.Repo, *config.P2P, event.Publisher) (TestablePeerNode, error) 35 36 // TestNodeFactory can be used to safetly construct nodes for tests 37 type TestNodeFactory struct { 38 count int 39 maker NodeMakerFunc 40 pub event.Publisher 41 } 42 43 // NewTestNodeFactory returns a new TestNodeFactory 44 func NewTestNodeFactory(maker NodeMakerFunc) *TestNodeFactory { 45 return &TestNodeFactory{count: 0, maker: maker, pub: event.NilBus} 46 } 47 48 // NewTestNodeFactoryWithBus returns a new TestNodeFactory with non nil buses 49 func NewTestNodeFactoryWithBus(maker NodeMakerFunc) *TestNodeFactory { 50 return &TestNodeFactory{count: 0, maker: maker, pub: event.NewBus(context.Background())} 51 } 52 53 // New creates a new Node for testing 54 func (f *TestNodeFactory) New(r repo.Repo) (TestablePeerNode, error) { 55 kd := testkeys.GetKeyData(f.count) 56 f.count++ 57 p2pconf := config.DefaultP2P() 58 p2pconf.PeerID = kd.EncodedPeerID 59 p2pconf.PrivKey = kd.EncodedPrivKey 60 return f.maker(r, p2pconf, f.pub) 61 } 62 63 // NewWithConf creates a new Node for testing using a configuration 64 func (f *TestNodeFactory) NewWithConf(r repo.Repo, p2pconf *config.P2P) (TestablePeerNode, error) { 65 kd := testkeys.GetKeyData(f.count) 66 f.count++ 67 p2pconf.PeerID = kd.EncodedPeerID 68 p2pconf.PrivKey = kd.EncodedPrivKey 69 return f.maker(r, p2pconf, f.pub) 70 } 71 72 // NextKeyData gets the KeyData for the next test Node to be constructed 73 func (f *TestNodeFactory) NextKeyData() *testkeys.KeyData { 74 return testkeys.GetKeyData(f.count) 75 } 76 77 // NewTestNetwork constructs nodes to test p2p functionality. 78 // each of these peers has no datasets and no peers are connected 79 func NewTestNetwork(ctx context.Context, f *TestNodeFactory, num int) ([]TestablePeerNode, error) { 80 nodes := make([]TestablePeerNode, num) 81 for i := 0; i < num; i++ { 82 info := f.NextKeyData() 83 r, err := test.NewTestRepoFromProfileID(profile.IDFromPeerID(info.PeerID), i, i) 84 if err != nil { 85 return nil, fmt.Errorf("error creating test repo: %s", err.Error()) 86 } 87 node, err := NewAvailableTestNode(ctx, r, f) 88 if err != nil { 89 return nil, err 90 } 91 nodes[i] = node 92 } 93 return nodes, nil 94 } 95 96 // NewNodeWithBus constructs the next node in the factory, but with 97 // the given bus, helpful for tests where you need to subscribe to specific 98 // events in order to coordiate timing 99 func NewNodeWithBus(ctx context.Context, f *TestNodeFactory, bus event.Publisher) (TestablePeerNode, error) { 100 prevBus := f.pub 101 defer func() { 102 f.pub = prevBus 103 }() 104 kd := f.NextKeyData() 105 r, err := test.NewTestRepoFromProfileID(profile.IDFromPeerID(kd.PeerID), 0, 0) 106 if err != nil { 107 return nil, fmt.Errorf("error creating test repo: %s", err.Error()) 108 } 109 110 f.pub = bus 111 node, err := NewAvailableTestNode(ctx, r, f) 112 if err != nil { 113 return nil, err 114 } 115 return node, nil 116 } 117 118 // NewTestDirNetwork constructs nodes from the testdata directory, for p2p testing 119 // Peers are pulled from the "testdata" directory, and come pre-populated with datasets 120 // no peers are connected. 121 func NewTestDirNetwork(ctx context.Context, f *TestNodeFactory) ([]TestablePeerNode, error) { 122 dirs, err := ioutil.ReadDir("testdata") 123 if err != nil { 124 return nil, err 125 } 126 127 nodes := []TestablePeerNode{} 128 for _, dir := range dirs { 129 if dir.IsDir() { 130 131 repo, _, err := test.NewMemRepoFromDir(filepath.Join("testdata", dir.Name())) 132 if err != nil { 133 return nil, err 134 } 135 136 node, err := NewAvailableTestNode(ctx, repo, f) 137 if err != nil { 138 return nil, err 139 } 140 nodes = append(nodes, node) 141 } 142 } 143 return nodes, nil 144 } 145 146 // NewAvailableTestNode constructs a test node that is hooked up and ready to Connect 147 func NewAvailableTestNode(ctx context.Context, r repo.Repo, f *TestNodeFactory) (TestablePeerNode, error) { 148 info := f.NextKeyData() 149 addr, _ := ma.NewMultiaddr("/ip4/127.0.0.1/tcp/0") 150 p2pconf := config.DefaultP2P() 151 p2pconf.Addrs = []ma.Multiaddr{addr} 152 p2pconf.QriBootstrapAddrs = []string{} 153 node, err := f.NewWithConf(r, p2pconf) 154 if err != nil { 155 return nil, fmt.Errorf("error creating test node: %s", err.Error()) 156 } 157 if err := node.GoOnline(ctx); err != nil { 158 return nil, fmt.Errorf("errror connecting: %s", err.Error()) 159 } 160 node.Host().Peerstore().AddPubKey(info.PeerID, info.PrivKey.GetPublic()) 161 node.Host().Peerstore().AddPrivKey(info.PeerID, info.PrivKey) 162 return node, err 163 } 164 165 // ConnectNodes creates a basic connection between the nodes. This connection 166 // mirrors the connection that would normally occur between two p2p nodes. 167 // The Host.Connect function adds the addresses into the peerstore and dials the 168 // remote peer. 169 // Take a look at https://github.com/libp2p/go-libp2p-core/host/blob/623ffaa4ef2b8dad77933159d0848a393a91c41e/host.go#L36 170 // for more info 171 // Connect should always: 172 // - add a connections to the peer 173 // - add the addrs of the peer to the peerstore 174 // - add a tag for each peer in the connmanager 175 func ConnectNodes(ctx context.Context, nodes []TestablePeerNode) error { 176 var wg sync.WaitGroup 177 connect := func(n TestablePeerNode, pinfo peer.AddrInfo) error { 178 if err := n.Host().Connect(ctx, pinfo); err != nil { 179 return fmt.Errorf("error connecting nodes: %s", err) 180 } 181 wg.Done() 182 return nil 183 } 184 185 for i, s1 := range nodes { 186 for _, s2 := range nodes[i+1:] { 187 wg.Add(1) 188 if err := connect(s1, s2.SimpleAddrInfo()); err != nil { 189 return err 190 } 191 } 192 } 193 wg.Wait() 194 // wait for 195 return nil 196 } 197 198 // GetSomeBlocks returns a list of num ids for blocks that are in the referenced dataset. 199 func GetSomeBlocks(capi coreiface.CoreAPI, ref reporef.DatasetRef, num int) []string { 200 result := []string{} 201 202 ctx := context.Background() 203 204 ng := dag.NewNodeGetter(capi.Dag()) 205 206 id, err := cid.Parse(ref.Path) 207 if err != nil { 208 panic(err) 209 } 210 211 elem, err := ng.Get(ctx, id) 212 if err != nil { 213 panic(err) 214 } 215 216 for _, link := range elem.Links() { 217 result = append(result, link.Cid.String()) 218 if len(result) >= num { 219 break 220 } 221 } 222 223 return result 224 }