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,36 +123,62 @@ 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 == "" {
startPeer(ctx, h, handleStream)
} else {
rw, err := startPeerAndConnect(ctx, h, *dest)
if err != nil {
log.Println(err)
return
}
// Create a thread to read and write data.
go writeData(rw)
go readData(rw)
}
// 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. // Set a function as stream handler.
// This function is called when a peer connects, and starts a stream with this protocol. // This function is called when a peer connects, and starts a stream with this protocol.
// Only applies on the receiving side. // Only applies on the receiving side.
host.SetStreamHandler("/chat/1.0.0", handleStream) 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). // Let's get the actual TCP port from our listen multiaddr, in case we're using 0 (default; random available port).
var port string var port string
for _, la := range host.Network().ListenAddresses() { for _, la := range h.Network().ListenAddresses() {
if p, err := la.ValueForProtocol(multiaddr.P_TCP); err == nil { if p, err := la.ValueForProtocol(multiaddr.P_TCP); err == nil {
port = p port = p
break break
@@ -156,53 +186,52 @@ func main() {
} }
if port == "" { if port == "" {
panic("was not able to find actual local port") log.Println("was not able to find actual local port")
return
} }
fmt.Printf("Run './chat -d /ip4/127.0.0.1/tcp/%v/p2p/%s' on another console.\n", port, host.ID().Pretty()) log.Printf("Run './chat -d /ip4/127.0.0.1/tcp/%v/p2p/%s' on another console.\n", port, h.ID().Pretty())
fmt.Println("You can replace 127.0.0.1 with public IP as well.") log.Println("You can replace 127.0.0.1 with public IP as well.")
fmt.Printf("\nWaiting for incoming connection\n\n") log.Println("Waiting for incoming connection")
log.Println()
}
// Hang forever func startPeerAndConnect(ctx context.Context, h host.Host, destination string) (*bufio.ReadWriter, error) {
<-make(chan struct{}) log.Println("This node's multiaddresses:")
} else { for _, la := range h.Addrs() {
fmt.Println("This node's multiaddresses:") log.Printf(" - %v\n", la)
for _, la := range host.Addrs() {
fmt.Printf(" - %v\n", la)
} }
fmt.Println() log.Println()
// Turn the destination into a multiaddr. // Turn the destination into a multiaddr.
maddr, err := multiaddr.NewMultiaddr(*dest) maddr, err := multiaddr.NewMultiaddr(destination)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return nil, err
} }
// Extract the peer ID from the multiaddr. // Extract the peer ID from the multiaddr.
info, err := peer.AddrInfoFromP2pAddr(maddr) info, err := peer.AddrInfoFromP2pAddr(maddr)
if err != nil { if err != nil {
log.Fatalln(err) log.Println(err)
return nil, err
} }
// Add the destination's peer multiaddress in the peerstore. // Add the destination's peer multiaddress in the peerstore.
// This will be used during connection and stream creation by libp2p. // This will be used during connection and stream creation by libp2p.
host.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL) h.Peerstore().AddAddrs(info.ID, info.Addrs, peerstore.PermanentAddrTTL)
// Start a stream with the destination. // Start a stream with the destination.
// Multiaddress of the destination peer is fetched from the peerstore using 'peerId'. // Multiaddress of the destination peer is fetched from the peerstore using 'peerId'.
s, err := host.NewStream(context.Background(), info.ID, "/chat/1.0.0") s, err := h.NewStream(context.Background(), info.ID, "/chat/1.0.0")
if err != nil { if err != nil {
panic(err) log.Println(err)
return nil, err
} }
log.Println("Established connection to destination")
// Create a buffered stream so that read and writes are non blocking. // Create a buffered stream so that read and writes are non blocking.
rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s))
// Create a thread to read and write data. return rw, nil
go writeData(rw)
go readData(rw)
// Hang forever.
select {}
}
} }

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")
}