chore: add tests for examples

This commit is contained in:
Ian Davis
2021-05-06 13:39:20 +01:00
parent 76e1ce667e
commit 0343b56ad5
12 changed files with 618 additions and 164 deletions

View File

@@ -40,6 +40,7 @@ import (
"github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p-core/peerstore" "github.com/libp2p/go-libp2p-core/peerstore"
@@ -58,6 +59,7 @@ func handleStream(s network.Stream) {
// stream 's' will stay open until you close it (or the other side closes it). // stream 's' will stay open until you close it (or the other side closes it).
} }
func readData(rw *bufio.ReadWriter) { func readData(rw *bufio.ReadWriter) {
for { for {
str, _ := rw.ReadString('\n') str, _ := rw.ReadString('\n')
@@ -80,18 +82,20 @@ func writeData(rw *bufio.ReadWriter) {
for { for {
fmt.Print("> ") fmt.Print("> ")
sendData, err := stdReader.ReadString('\n') sendData, err := stdReader.ReadString('\n')
if err != nil { if err != nil {
panic(err) log.Println(err)
return
} }
rw.WriteString(fmt.Sprintf("%s\n", sendData)) rw.WriteString(fmt.Sprintf("%s\n", sendData))
rw.Flush() rw.Flush()
} }
} }
func main() { func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sourcePort := flag.Int("sp", 0, "Source port number") sourcePort := flag.Int("sp", 0, "Source port number")
dest := flag.String("d", "", "Destination multiaddr string") dest := flag.String("d", "", "Destination multiaddr string")
help := flag.Bool("help", false, "Display help") help := flag.Bool("help", false, "Display help")
@@ -119,90 +123,115 @@ func main() {
r = rand.Reader r = rand.Reader
} }
// Creates a new RSA key pair for this host. h, err := makeHost(ctx, *sourcePort, r)
prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
if err != nil { if err != nil {
panic(err) log.Println(err)
} return
// 0.0.0.0 will listen on any interface device.
sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", *sourcePort))
// libp2p.New constructs a new libp2p Host.
// Other options can be added here.
host, err := libp2p.New(
context.Background(),
libp2p.ListenAddrs(sourceMultiAddr),
libp2p.Identity(prvKey),
)
if err != nil {
panic(err)
} }
if *dest == "" { if *dest == "" {
// Set a function as stream handler. startPeer(ctx, h, handleStream)
// This function is called when a peer connects, and starts a stream with this protocol.
// Only applies on the receiving side.
host.SetStreamHandler("/chat/1.0.0", handleStream)
// Let's get the actual TCP port from our listen multiaddr, in case we're using 0 (default; random available port).
var port string
for _, la := range host.Network().ListenAddresses() {
if p, err := la.ValueForProtocol(multiaddr.P_TCP); err == nil {
port = p
break
}
}
if port == "" {
panic("was not able to find actual local port")
}
fmt.Printf("Run './chat -d /ip4/127.0.0.1/tcp/%v/p2p/%s' on another console.\n", port, host.ID().Pretty())
fmt.Println("You can replace 127.0.0.1 with public IP as well.")
fmt.Printf("\nWaiting for incoming connection\n\n")
// Hang forever
<-make(chan struct{})
} else { } else {
fmt.Println("This node's multiaddresses:") rw, err := startPeerAndConnect(ctx, h, *dest)
for _, la := range host.Addrs() {
fmt.Printf(" - %v\n", la)
}
fmt.Println()
// Turn the destination into a multiaddr.
maddr, err := multiaddr.NewMultiaddr(*dest)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
// Extract the peer ID from the multiaddr.
info, err := peer.AddrInfoFromP2pAddr(maddr)
if err != nil {
log.Fatalln(err)
}
// Add the destination's peer multiaddress in the peerstore.
// This will be used during connection and stream creation by libp2p.
host.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL)
// Start a stream with the destination.
// Multiaddress of the destination peer is fetched from the peerstore using 'peerId'.
s, err := host.NewStream(context.Background(), info.ID, "/chat/1.0.0")
if err != nil {
panic(err)
}
// Create a buffered stream so that read and writes are non blocking.
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
// Create a thread to read and write data. // Create a thread to read and write data.
go writeData(rw) go writeData(rw)
go readData(rw) go readData(rw)
// Hang forever. }
select {}
// Wait until canceled
select {
case <-ctx.Done():
} }
} }
func makeHost(ctx context.Context, port int, randomness io.Reader) (host.Host, error) {
// Creates a new RSA key pair for this host.
prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, randomness)
if err != nil {
log.Println(err)
return nil, err
}
// 0.0.0.0 will listen on any interface device.
sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", port))
// libp2p.New constructs a new libp2p Host.
// Other options can be added here.
return libp2p.New(
ctx,
libp2p.ListenAddrs(sourceMultiAddr),
libp2p.Identity(prvKey),
)
}
func startPeer(ctx context.Context, h host.Host, streamHandler network.StreamHandler) {
// Set a function as stream handler.
// This function is called when a peer connects, and starts a stream with this protocol.
// Only applies on the receiving side.
h.SetStreamHandler("/chat/1.0.0", streamHandler)
// Let's get the actual TCP port from our listen multiaddr, in case we're using 0 (default; random available port).
var port string
for _, la := range h.Network().ListenAddresses() {
if p, err := la.ValueForProtocol(multiaddr.P_TCP); err == nil {
port = p
break
}
}
if port == "" {
log.Println("was not able to find actual local port")
return
}
log.Printf("Run './chat -d /ip4/127.0.0.1/tcp/%v/p2p/%s' on another console.\n", port, h.ID().Pretty())
log.Println("You can replace 127.0.0.1 with public IP as well.")
log.Println("Waiting for incoming connection")
log.Println()
}
func startPeerAndConnect(ctx context.Context, h host.Host, destination string) (*bufio.ReadWriter, error) {
log.Println("This node's multiaddresses:")
for _, la := range h.Addrs() {
log.Printf(" - %v\n", la)
}
log.Println()
// Turn the destination into a multiaddr.
maddr, err := multiaddr.NewMultiaddr(destination)
if err != nil {
log.Println(err)
return nil, err
}
// Extract the peer ID from the multiaddr.
info, err := peer.AddrInfoFromP2pAddr(maddr)
if err != nil {
log.Println(err)
return nil, err
}
// Add the destination's peer multiaddress in the peerstore.
// This will be used during connection and stream creation by libp2p.
h.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL)
// Start a stream with the destination.
// Multiaddress of the destination peer is fetched from the peerstore using 'peerId'.
s, err := h.NewStream(context.Background(), info.ID, "/chat/1.0.0")
if err != nil {
log.Println(err)
return nil, err
}
log.Println("Established connection to destination")
// Create a buffered stream so that read and writes are non blocking.
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
return rw, nil
}

View File

@@ -0,0 +1,72 @@
package main
import (
"context"
"crypto/rand"
"fmt"
"log"
"testing"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p/examples/testutils"
)
func TestMain(t *testing.T) {
var h testutils.LogHarness
h.Expect("Waiting for incoming connection")
h.Expect("Established connection to destination")
h.Expect("Got a new stream!")
h.Run(t, func() {
// Create a context that will stop the hosts when the tests end
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
port1, err := testutils.FindFreePort(t, "", 5)
if err != nil {
log.Println(err)
return
}
port2, err := testutils.FindFreePort(t, "", 5)
if err != nil {
log.Println(err)
return
}
h1, err := makeHost(ctx, port1, rand.Reader)
if err != nil {
log.Println(err)
return
}
go startPeer(ctx, h1, func(network.Stream) {
log.Println("Got a new stream!")
cancel() // end the test
})
dest := fmt.Sprintf("/ip4/127.0.0.1/tcp/%v/p2p/%s", port1, h1.ID().Pretty())
h2, err := makeHost(ctx, port2, rand.Reader)
if err != nil {
log.Println(err)
return
}
go func() {
rw, err := startPeerAndConnect(ctx, h2, dest)
if err != nil {
log.Println(err)
return
}
rw.WriteString("test message")
rw.Flush()
}()
select {
case <-ctx.Done():
}
})
}

View File

@@ -22,13 +22,42 @@ import (
ma "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr"
) )
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// LibP2P code uses golog to log messages. They log with different
// string IDs (i.e. "swarm"). We can control the verbosity level for
// all loggers with:
golog.SetAllLoggers(golog.LevelInfo) // Change to INFO for extra info
// Parse options from the command line
listenF := flag.Int("l", 0, "wait for incoming connections")
targetF := flag.String("d", "", "target peer to dial")
insecureF := flag.Bool("insecure", false, "use an unencrypted connection")
seedF := flag.Int64("seed", 0, "set random seed for id generation")
flag.Parse()
if *listenF == 0 {
log.Fatal("Please provide a port to bind on with -l")
}
// Make a host that listens on the given multiaddress
ha, err := makeBasicHost(*listenF, *insecureF, *seedF)
if err != nil {
log.Fatal(err)
}
if *targetF == "" {
runListener(ctx, ha, *listenF, *insecureF)
} else {
runSender(ctx, ha, *targetF)
}
}
// makeBasicHost creates a LibP2P host with a random peer ID listening on the // makeBasicHost creates a LibP2P host with a random peer ID listening on the
// given multiaddress. It won't encrypt the connection if insecure is true. // given multiaddress. It won't encrypt the connection if insecure is true.
func makeBasicHost(listenPort int, insecure bool, randseed int64) (host.Host, error) { func makeBasicHost(listenPort int, insecure bool, randseed int64) (host.Host, error) {
// If the seed is zero, use real cryptographic randomness. Otherwise, use a
// deterministic randomness source to make generated keys stay the same
// across multiple runs
var r io.Reader var r io.Reader
if randseed == 0 { if randseed == 0 {
r = rand.Reader r = rand.Reader
@@ -53,55 +82,27 @@ func makeBasicHost(listenPort int, insecure bool, randseed int64) (host.Host, er
opts = append(opts, libp2p.NoSecurity) opts = append(opts, libp2p.NoSecurity)
} }
basicHost, err := libp2p.New(context.Background(), opts...) return libp2p.New(context.Background(), opts...)
if err != nil { }
return nil, err
}
func getHostAddress(ha host.Host) string {
// Build host multiaddress // Build host multiaddress
hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", basicHost.ID().Pretty())) hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", ha.ID().Pretty()))
// Now we can build a full multiaddress to reach this host // Now we can build a full multiaddress to reach this host
// by encapsulating both addresses: // by encapsulating both addresses:
addr := basicHost.Addrs()[0] addr := ha.Addrs()[0]
fullAddr := addr.Encapsulate(hostAddr) return addr.Encapsulate(hostAddr).String()
log.Printf("I am %s\n", fullAddr)
if insecure {
log.Printf("Now run \"./echo -l %d -d %s -insecure\" on a different terminal\n", listenPort+1, fullAddr)
} else {
log.Printf("Now run \"./echo -l %d -d %s\" on a different terminal\n", listenPort+1, fullAddr)
}
return basicHost, nil
} }
func main() { func runListener(ctx context.Context, ha host.Host, listenPort int, insecure bool) {
// LibP2P code uses golog to log messages. They log with different fullAddr := getHostAddress(ha)
// string IDs (i.e. "swarm"). We can control the verbosity level for log.Printf("I am %s\n", fullAddr)
// all loggers with:
golog.SetAllLoggers(golog.LevelInfo) // Change to INFO for extra info
// Parse options from the command line
listenF := flag.Int("l", 0, "wait for incoming connections")
target := flag.String("d", "", "target peer to dial")
insecure := flag.Bool("insecure", false, "use an unencrypted connection")
seed := flag.Int64("seed", 0, "set random seed for id generation")
flag.Parse()
if *listenF == 0 {
log.Fatal("Please provide a port to bind on with -l")
}
// Make a host that listens on the given multiaddress
ha, err := makeBasicHost(*listenF, *insecure, *seed)
if err != nil {
log.Fatal(err)
}
// Set a stream handler on host A. /echo/1.0.0 is // Set a stream handler on host A. /echo/1.0.0 is
// a user-defined protocol name. // a user-defined protocol name.
ha.SetStreamHandler("/echo/1.0.0", func(s network.Stream) { ha.SetStreamHandler("/echo/1.0.0", func(s network.Stream) {
log.Println("Got a new stream!") log.Println("listener received new stream")
if err := doEcho(s); err != nil { if err := doEcho(s); err != nil {
log.Println(err) log.Println(err)
s.Reset() s.Reset()
@@ -110,27 +111,54 @@ func main() {
} }
}) })
if *target == "" { log.Println("listening for connections")
log.Println("listening for connections")
select {} // hang forever if insecure {
log.Printf("Now run \"./echo -l %d -d %s -insecure\" on a different terminal\n", listenPort+1, fullAddr)
} else {
log.Printf("Now run \"./echo -l %d -d %s\" on a different terminal\n", listenPort+1, fullAddr)
} }
/**** This is where the listener code ends ****/
// Wait until canceled
select {
case <-ctx.Done():
}
}
func runSender(ctx context.Context, ha host.Host, targetPeer string) {
fullAddr := getHostAddress(ha)
log.Printf("I am %s\n", fullAddr)
// Set a stream handler on host A. /echo/1.0.0 is
// a user-defined protocol name.
ha.SetStreamHandler("/echo/1.0.0", func(s network.Stream) {
log.Println("sender received new stream")
if err := doEcho(s); err != nil {
log.Println(err)
s.Reset()
} else {
s.Close()
}
})
// The following code extracts target's the peer ID from the // The following code extracts target's the peer ID from the
// given multiaddress // given multiaddress
ipfsaddr, err := ma.NewMultiaddr(*target) ipfsaddr, err := ma.NewMultiaddr(targetPeer)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS) pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
peerid, err := peer.IDB58Decode(pid) peerid, err := peer.IDB58Decode(pid)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
// Decapsulate the /ipfs/<peerID> part from the target // Decapsulate the /ipfs/<peerID> part from the target
@@ -142,23 +170,27 @@ func main() {
// so LibP2P knows how to contact it // so LibP2P knows how to contact it
ha.Peerstore().AddAddr(peerid, targetAddr, peerstore.PermanentAddrTTL) ha.Peerstore().AddAddr(peerid, targetAddr, peerstore.PermanentAddrTTL)
log.Println("opening stream") log.Println("sender opening stream")
// make a new stream from host B to host A // make a new stream from host B to host A
// it should be handled on host A by the handler we set above because // it should be handled on host A by the handler we set above because
// we use the same /echo/1.0.0 protocol // we use the same /echo/1.0.0 protocol
s, err := ha.NewStream(context.Background(), peerid, "/echo/1.0.0") s, err := ha.NewStream(context.Background(), peerid, "/echo/1.0.0")
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
log.Println("sender saying hello")
_, err = s.Write([]byte("Hello, world!\n")) _, err = s.Write([]byte("Hello, world!\n"))
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
out, err := ioutil.ReadAll(s) out, err := ioutil.ReadAll(s)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return
} }
log.Printf("read reply: %q\n", out) log.Printf("read reply: %q\n", out)
@@ -172,7 +204,7 @@ func doEcho(s network.Stream) error {
return err return err
} }
log.Printf("read: %s\n", str) log.Printf("read: %s", str)
_, err = s.Write([]byte(str)) _, err = s.Write([]byte(str))
return err return err
} }

View File

@@ -0,0 +1,58 @@
package main
import (
"context"
"log"
"testing"
"github.com/libp2p/go-libp2p/examples/testutils"
)
func TestMain(t *testing.T) {
var h testutils.LogHarness
h.Expect("listening for connections")
h.Expect("sender opening stream")
h.Expect("sender saying hello")
h.Expect("listener received new stream")
h.Expect("read: Hello, world!")
h.Expect(`read reply: "Hello, world!\n"`)
h.Run(t, func() {
// Create a context that will stop the hosts when the tests end
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Get a tcp port for the listener
lport, err := testutils.FindFreePort(t, "", 5)
if err != nil {
log.Println(err)
return
}
// Get a tcp port for the sender
sport, err := testutils.FindFreePort(t, "", 5)
if err != nil {
log.Println(err)
return
}
// Make listener
lh, err := makeBasicHost(lport, true, 1)
if err != nil {
log.Println(err)
return
}
go runListener(ctx, lh, lport, true)
// Make sender
listenAddr := getHostAddress(lh)
sh, err := makeBasicHost(sport, true, 2)
if err != nil {
log.Println(err)
return
}
runSender(ctx, sh, listenAddr)
})
}

View File

@@ -2,7 +2,7 @@ package main
import ( import (
"context" "context"
"fmt" "log"
"time" "time"
"github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p"
@@ -17,6 +17,10 @@ import (
) )
func main() { func main() {
run()
}
func run() {
// The context governs the lifetime of the libp2p node. // The context governs the lifetime of the libp2p node.
// Cancelling it will stop the the host. // Cancelling it will stop the the host.
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@@ -28,7 +32,7 @@ func main() {
panic(err) panic(err)
} }
fmt.Printf("Hello World, my hosts ID is %s\n", h.ID()) log.Printf("Hello World, my hosts ID is %s\n", h.ID())
// Now, normally you do not just want a simple host, you want // Now, normally you do not just want a simple host, you want
// that is fully configured to best support your p2p application. // that is fully configured to best support your p2p application.
@@ -105,5 +109,5 @@ func main() {
h2.Connect(ctx, *pi) h2.Connect(ctx, *pi)
} }
*/ */
fmt.Printf("Hello World, my second hosts ID is %s\n", h2.ID()) log.Printf("Hello World, my second hosts ID is %s\n", h2.ID())
} }

View File

@@ -0,0 +1,14 @@
package main
import (
"testing"
"github.com/libp2p/go-libp2p/examples/testutils"
)
func TestMain(t *testing.T) {
var h testutils.LogHarness
h.ExpectPrefix("Hello World, my hosts ID is ")
h.ExpectPrefix("Hello World, my second hosts ID is ")
h.Run(t, run)
}

View File

@@ -13,6 +13,23 @@ import (
ma "github.com/multiformats/go-multiaddr" ma "github.com/multiformats/go-multiaddr"
) )
func main() {
// Choose random ports between 10000-10100
rand.Seed(666)
port1 := rand.Intn(100) + 10000
port2 := port1 + 1
done := make(chan bool, 1)
// Make 2 hosts
h1 := makeRandomNode(port1, done)
h2 := makeRandomNode(port2, done)
log.Printf("This is a conversation between %s and %s\n", h1.ID(), h2.ID())
run(h1, h2, done)
}
// helper method - create a lib-p2p host to listen on a port // helper method - create a lib-p2p host to listen on a port
func makeRandomNode(port int, done chan bool) *Node { func makeRandomNode(port int, done chan bool) *Node {
// Ignoring most errors for brevity // Ignoring most errors for brevity
@@ -28,22 +45,11 @@ func makeRandomNode(port int, done chan bool) *Node {
return NewNode(host, done) return NewNode(host, done)
} }
func main() { func run(h1, h2 *Node, done <-chan bool) {
// Choose random ports between 10000-10100 // connect peers
rand.Seed(666)
port1 := rand.Intn(100) + 10000
port2 := port1 + 1
done := make(chan bool, 1)
// Make 2 hosts
h1 := makeRandomNode(port1, done)
h2 := makeRandomNode(port2, done)
h1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), peerstore.PermanentAddrTTL) h1.Peerstore().AddAddrs(h2.ID(), h2.Addrs(), peerstore.PermanentAddrTTL)
h2.Peerstore().AddAddrs(h1.ID(), h1.Addrs(), peerstore.PermanentAddrTTL) h2.Peerstore().AddAddrs(h1.ID(), h1.Addrs(), peerstore.PermanentAddrTTL)
log.Printf("This is a conversation between %s and %s\n", h1.ID(), h2.ID())
// send messages using the protocols // send messages using the protocols
h1.Ping(h2.Host) h1.Ping(h2.Host)
h2.Ping(h1.Host) h2.Ping(h1.Host)

View File

@@ -0,0 +1,55 @@
package main
import (
"fmt"
"log"
"testing"
"github.com/libp2p/go-libp2p/examples/testutils"
)
func TestMain(t *testing.T) {
port1, err := testutils.FindFreePort(t, "", 5)
if err != nil {
log.Println(err)
return
}
port2, err := testutils.FindFreePort(t, "", 5)
if err != nil {
log.Println(err)
return
}
done := make(chan bool, 1)
h1 := makeRandomNode(port1, done)
h2 := makeRandomNode(port2, done)
var h testutils.LogHarness
// Sequence of log messages when h1 pings h2
pingh1h2 := h.NewSequence("ping h1->h2")
pingh1h2.ExpectPrefix(fmt.Sprintf("%s: Sending ping to: %s", h1.ID(), h2.ID()))
pingh1h2.ExpectPrefix(fmt.Sprintf("%s: Received ping request from %s", h2.ID(), h1.ID()))
pingh1h2.ExpectPrefix(fmt.Sprintf("%s: Received ping response from %s", h1.ID(), h2.ID()))
// Sequence of log messages when h2 pings h1
pingh2h1 := h.NewSequence("ping h2->h1")
pingh2h1.ExpectPrefix(fmt.Sprintf("%s: Sending ping to: %s", h2.ID(), h1.ID()))
pingh2h1.ExpectPrefix(fmt.Sprintf("%s: Received ping request from %s", h1.ID(), h2.ID()))
pingh2h1.ExpectPrefix(fmt.Sprintf("%s: Received ping response from %s", h2.ID(), h1.ID()))
// Sequence of log messages when h1 sends echo to h2
echoh1h2 := h.NewSequence("echo h1->h2")
echoh1h2.ExpectPrefix(fmt.Sprintf("%s: Sending echo to: %s", h1.ID(), h2.ID()))
echoh1h2.ExpectPrefix(fmt.Sprintf("%s: Echo response to %s", h2.ID(), h1.ID()))
// Sequence of log messages when h1 sends echo to h2
echoh2h1 := h.NewSequence("echo h2->h1")
echoh2h1.ExpectPrefix(fmt.Sprintf("%s: Sending echo to: %s", h2.ID(), h1.ID()))
echoh2h1.ExpectPrefix(fmt.Sprintf("%s: Echo response to %s", h1.ID(), h2.ID()))
h.Run(t, func() {
run(h1, h2, done)
})
}

View File

@@ -2,7 +2,7 @@ package main
import ( import (
"context" "context"
"fmt" "log"
"github.com/libp2p/go-libp2p" "github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/network" "github.com/libp2p/go-libp2p-core/network"
@@ -14,27 +14,34 @@ import (
) )
func main() { func main() {
run()
}
func run() {
// Create three libp2p hosts, enable relay client capabilities on all // Create three libp2p hosts, enable relay client capabilities on all
// of them. // of them.
// Tell the host to monitor for relays. // Tell the host use relays
h1, err := libp2p.New(context.Background(), libp2p.EnableRelay(circuit.OptDiscovery)) h1, err := libp2p.New(context.Background(), libp2p.EnableRelay())
if err != nil { if err != nil {
panic(err) log.Printf("Failed to create h1: %v", err)
return
} }
// Tell the host to relay connections for other peers (The ability to *use* // Tell the host to relay connections for other peers (The ability to *use*
// a relay vs the ability to *be* a relay) // a relay vs the ability to *be* a relay)
h2, err := libp2p.New(context.Background(), libp2p.EnableRelay(circuit.OptHop)) h2, err := libp2p.New(context.Background(), libp2p.EnableRelay(circuit.OptHop))
if err != nil { if err != nil {
panic(err) log.Printf("Failed to create h2: %v", err)
return
} }
// Zero out the listen addresses for the host, so it can only communicate // Zero out the listen addresses for the host, so it can only communicate
// via p2p-circuit for our example // via p2p-circuit for our example
h3, err := libp2p.New(context.Background(), libp2p.ListenAddrs(), libp2p.EnableRelay()) h3, err := libp2p.New(context.Background(), libp2p.ListenAddrs(), libp2p.EnableRelay())
if err != nil { if err != nil {
panic(err) log.Printf("Failed to create h3: %v", err)
return
} }
h2info := peer.AddrInfo{ h2info := peer.AddrInfo{
@@ -44,30 +51,33 @@ func main() {
// Connect both h1 and h3 to h2, but not to each other // Connect both h1 and h3 to h2, but not to each other
if err := h1.Connect(context.Background(), h2info); err != nil { if err := h1.Connect(context.Background(), h2info); err != nil {
panic(err) log.Printf("Failed to connect h1 and h2: %v", err)
return
} }
if err := h3.Connect(context.Background(), h2info); err != nil { if err := h3.Connect(context.Background(), h2info); err != nil {
panic(err) log.Printf("Failed to connect h3 and h2: %v", err)
return
} }
// Now, to test things, let's set up a protocol handler on h3 // Now, to test things, let's set up a protocol handler on h3
h3.SetStreamHandler("/cats", func(s network.Stream) { h3.SetStreamHandler("/cats", func(s network.Stream) {
fmt.Println("Meow! It worked!") log.Println("Meow! It worked!")
s.Close() s.Close()
}) })
_, err = h1.NewStream(context.Background(), h3.ID(), "/cats") _, err = h1.NewStream(context.Background(), h3.ID(), "/cats")
if err == nil { if err == nil {
fmt.Println("Didnt actually expect to get a stream here. What happened?") log.Println("Didnt actually expect to get a stream here. What happened?")
return return
} }
fmt.Println("Okay, no connection from h1 to h3: ", err) log.Printf("Okay, no connection from h1 to h3: %v", err)
fmt.Println("Just as we suspected") log.Println("Just as we suspected")
// Creates a relay address // Creates a relay address to h3 using h2 as the relay
relayaddr, err := ma.NewMultiaddr("/p2p-circuit/ipfs/" + h3.ID().Pretty()) relayaddr, err := ma.NewMultiaddr("/p2p/" + h2.ID().Pretty() + "/p2p-circuit/ipfs/" + h3.ID().Pretty())
if err != nil { if err != nil {
panic(err) log.Println(err)
return
} }
// Since we just tried and failed to dial, the dialer system will, by default // Since we just tried and failed to dial, the dialer system will, by default
@@ -81,13 +91,14 @@ func main() {
Addrs: []ma.Multiaddr{relayaddr}, Addrs: []ma.Multiaddr{relayaddr},
} }
if err := h1.Connect(context.Background(), h3relayInfo); err != nil { if err := h1.Connect(context.Background(), h3relayInfo); err != nil {
panic(err) log.Printf("Failed to connect h1 and h3: %v", err)
return
} }
// Woohoo! we're connected! // Woohoo! we're connected!
s, err := h1.NewStream(context.Background(), h3.ID(), "/cats") s, err := h1.NewStream(context.Background(), h3.ID(), "/cats")
if err != nil { if err != nil {
fmt.Println("huh, this should have worked: ", err) log.Println("huh, this should have worked: ", err)
return return
} }

View File

@@ -0,0 +1,14 @@
package main
import (
"testing"
"github.com/libp2p/go-libp2p/examples/testutils"
)
func TestMain(t *testing.T) {
var h testutils.LogHarness
h.ExpectPrefix("Okay, no connection from h1 to h3")
h.ExpectPrefix("Meow! It worked!")
h.Run(t, run)
}

View File

@@ -0,0 +1,122 @@
package testutils
import (
"bufio"
"bytes"
"fmt"
"log"
"os"
"strings"
"testing"
)
// A LogHarness runs sets of assertions against the log output of a function. Assertions are grouped
// into sequences of messages that are expected to be found in the log output. Calling one of the Expect
// methods on the harness adds an expectation to the default sequence of messages. Additional sequences
// can be created by calling NewSequence.
type LogHarness struct {
buf bytes.Buffer
sequences []*Sequence
}
type Expectation interface {
IsMatch(line string) bool
String() string
}
// Run executes the function f and captures any output written using Go's standard log. Each sequence
// of expected messages is then
func (h *LogHarness) Run(t *testing.T, f func()) {
// Capture raw log output
fl := log.Flags()
log.SetFlags(0)
log.SetOutput(&h.buf)
f()
log.SetFlags(fl)
log.SetOutput(os.Stderr)
for _, seq := range h.sequences {
seq.Assert(t, bufio.NewScanner(bytes.NewReader(h.buf.Bytes())))
}
}
// Expect adds an expectation to the default sequence that the log contains a line equal to s
func (h *LogHarness) Expect(s string) {
if len(h.sequences) == 0 {
h.sequences = append(h.sequences, &Sequence{name: ""})
}
h.sequences[0].Expect(s)
}
// ExpectPrefix adds an to the default sequence expectation that the log contains a line starting with s
func (h *LogHarness) ExpectPrefix(s string) {
if len(h.sequences) == 0 {
h.sequences = append(h.sequences, &Sequence{name: ""})
}
h.sequences[0].ExpectPrefix(s)
}
// NewSequence creates a new sequence of expected log messages
func (h *LogHarness) NewSequence(name string) *Sequence {
seq := &Sequence{name: name}
h.sequences = append(h.sequences, seq)
return seq
}
type prefix string
func (p prefix) IsMatch(line string) bool {
return strings.HasPrefix(line, string(p))
}
func (p prefix) String() string {
return fmt.Sprintf("prefix %q", string(p))
}
type text string
func (t text) IsMatch(line string) bool {
return line == string(t)
}
func (t text) String() string {
return fmt.Sprintf("text %q", string(t))
}
type Sequence struct {
name string
exp []Expectation
}
func (seq *Sequence) Assert(t *testing.T, s *bufio.Scanner) {
var tag string
if seq.name != "" {
tag = fmt.Sprintf("[%s] ", seq.name)
}
// Match raw log lines against expectations
exploop:
for _, e := range seq.exp {
for s.Scan() {
if e.IsMatch(s.Text()) {
t.Logf("%ssaw: %s", tag, s.Text())
continue exploop
}
}
if s.Err() == nil {
t.Errorf("%sdid not see expected %s", tag, e.String())
return
}
}
return
}
// Expect adds an expectation that the log contains a line equal to s
func (seq *Sequence) Expect(s string) {
seq.exp = append(seq.exp, text(s))
}
// ExpectPrefix adds an expectation that the log contains a line starting with s
func (seq *Sequence) ExpectPrefix(s string) {
seq.exp = append(seq.exp, prefix(s))
}

37
examples/testutils/net.go Normal file
View File

@@ -0,0 +1,37 @@
package testutils
import (
"fmt"
"net"
"testing"
)
// FindFreePort attempts to find an unused tcp port
func FindFreePort(t *testing.T, host string, maxAttempts int) (int, error) {
t.Helper()
if host == "" {
host = "localhost"
}
for i := 0; i < maxAttempts; i++ {
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(host, "0"))
if err != nil {
t.Logf("unable to resolve tcp addr: %v", err)
continue
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
l.Close()
t.Logf("unable to listen on addr %q: %v", addr, err)
continue
}
port := l.Addr().(*net.TCPAddr).Port
l.Close()
return port, nil
}
return 0, fmt.Errorf("no free port found")
}