From 13fc0bf720487103c0fecfa5edce08250b0582f6 Mon Sep 17 00:00:00 2001 From: Matthias Radestock Date: Sat, 24 Jan 2015 10:56:44 +0000 Subject: [PATCH] add some tests for Peers extracted from #313 --- Makefile | 1 + router/mocks_test.go | 113 +++++++++++++++++++++++++++++++++++++++++++ router/peers_test.go | 113 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 router/mocks_test.go create mode 100644 router/peers_test.go diff --git a/Makefile b/Makefile index 4b432353..cfe33ff8 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,7 @@ $(WEAVETOOLS_EXPORT): tools/Dockerfile $(WEAVETOOLS_EXES) # Add more directories in here as more tests are created tests: + cd router; go test -tags netgo cd nameserver; go test -tags netgo $(PUBLISH): publish_%: diff --git a/router/mocks_test.go b/router/mocks_test.go new file mode 100644 index 00000000..b4ed8e0b --- /dev/null +++ b/router/mocks_test.go @@ -0,0 +1,113 @@ +// No mocks are tested by this file. +// +// It supplies some mock implementations to other unit tests, and is +// named "...test.go" so it is only compiled under `go test`. + +package router + +import ( + wt "github.com/zettio/weave/testing" + "testing" +) + +// Add to peers a connection from peers.ourself to p +func (peers *Peers) AddTestConnection(p *Peer) { + toPeer := NewPeer(p.Name, p.UID, 0) + peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount + conn := newMockConnection(peers.ourself, toPeer) + peers.ourself.addConnection(conn) + peers.ourself.connectionEstablished(conn) +} + +// Add to (peers,p0) a connection from p1 to p2 +func (peers *Peers) AddTestRemoteConnection(p1, p2 *Peer) { + fromName := p1.Name + fromPeer := NewPeer(fromName, p1.UID, 0) + fromPeer = peers.FetchWithDefault(fromPeer) + toName := p2.Name + toPeer := NewPeer(toName, p2.UID, 0) + toPeer = peers.FetchWithDefault(toPeer) + peers.ourself.addConnection(&RemoteConnection{fromPeer, toPeer, ""}) +} + +func (peers *Peers) DeleteTestConnection(ourself *Peer, peer *Peer) { + toName := peer.Name + toPeer, _ := peers.Fetch(toName) + toPeer.DecrementLocalRefCount() + conn, _ := ourself.ConnectionTo(toName) + ourself.deleteConnection(conn) +} + +// mockConnection used in testing is very similar to a +// RemoteConnection, without the RemoteTCPAddr(). We are making it a +// separate type in order to distinguish what is created by the test +// from what is created by the real code. +func newMockConnection(from, to *Peer) Connection { + type mockConnection struct{ RemoteConnection } + return &mockConnection{RemoteConnection{from, to, ""}} +} + +func checkEqualConns(t *testing.T, ourName PeerName, got, wanted map[PeerName]Connection) { + checkConns := make(map[PeerName]bool) + for _, conn := range wanted { + checkConns[conn.Remote().Name] = true + } + for _, conn := range got { + remoteName := conn.Remote().Name + if _, found := checkConns[remoteName]; found { + delete(checkConns, remoteName) + } else { + wt.Fatalf(t, "Unexpected connection from %s to %s", ourName, remoteName) + } + } + if len(checkConns) > 0 { + wt.Fatalf(t, "Expected connections not found: from %s to %v", ourName, checkConns) + } +} + +// Get all the peers from a Peers in a slice +func (peers *Peers) allPeers() []*Peer { + res := make([]*Peer, 0) + for _, peer := range peers.table { + res = append(res, peer) + } + return res +} + +func (peers *Peers) allPeersExcept(excludeName PeerName) []*Peer { + res := peers.allPeers() + for i, peer := range res { + if peer.Name == excludeName { + return append(res[:i], res[i+1:]...) + } + } + return res +} + +// Check that the peers slice matches the wanted peers +func checkPeerArray(t *testing.T, peers []*Peer, wantedPeers ...*Peer) { + checkTopologyPeers(t, false, peers, wantedPeers...) +} + +// Check that the peers slice matches the wanted peers and optionally +// all of their connections +func checkTopologyPeers(t *testing.T, checkConns bool, peers []*Peer, wantedPeers ...*Peer) { + check := make(map[PeerName]*Peer) + for _, peer := range wantedPeers { + check[peer.Name] = peer + } + for _, peer := range peers { + name := peer.Name + if wantedPeer, found := check[name]; found { + if checkConns { + checkEqualConns(t, name, peer.connections, wantedPeer.connections) + } + delete(check, name) + } else { + wt.Fatalf(t, "Unexpected peer: %s", name) + } + } + if len(check) > 0 { + wt.Fatalf(t, "Expected peers not found: %v", check) + } +} diff --git a/router/peers_test.go b/router/peers_test.go new file mode 100644 index 00000000..94f905ec --- /dev/null +++ b/router/peers_test.go @@ -0,0 +1,113 @@ +package router + +import ( + "fmt" + wt "github.com/zettio/weave/testing" + "math/rand" + "testing" +) + +// TODO we should also test: +// +// - applying an incremental update, including the case where that +// leads to an UnknownPeersError +// +// - the "improved update" calculation +// +// - non-gc of peers that are only referenced locally + +func newNode(name PeerName) (*Peer, *Peers) { + peer := NewPeer(name, 0, 0) + peers := NewPeers(peer, func(*Peer) {}) + peers.FetchWithDefault(peer) + return peer, peers +} + +// Check that ApplyUpdate copies the whole topology from peers +func checkApplyUpdate(t *testing.T, peers *Peers) { + dummyName, _ := PeerNameFromString("99:00:00:01:00:00") + // We need a new node outside of the network, with a connection + // into it. + _, testBedPeers := newNode(dummyName) + testBedPeers.AddTestConnection(peers.ourself) + testBedPeers.ApplyUpdate(peers.EncodeAllPeers()) + + checkTopologyPeers(t, true, testBedPeers.allPeersExcept(dummyName), peers.allPeers()...) +} + +func TestPeersEncoding(t *testing.T) { + const numNodes = 20 + const numIters = 1000 + var peer [numNodes]*Peer + var ps [numNodes]*Peers + for i := 0; i < numNodes; i++ { + name, _ := PeerNameFromString(fmt.Sprintf("%02d:00:00:01:00:00", i)) + peer[i], ps[i] = newNode(name) + } + + conns := make([]struct{ from, to int }, 0) + for i := 0; i < numIters; i++ { + oper := rand.Intn(2) + switch oper { + case 0: + from, to := rand.Intn(numNodes), rand.Intn(numNodes) + if from != to { + if _, found := peer[from].ConnectionTo(peer[to].Name); !found { + ps[from].AddTestConnection(peer[to]) + conns = append(conns, struct{ from, to int }{from, to}) + checkApplyUpdate(t, ps[from]) + } + } + case 1: + if len(conns) > 0 { + n := rand.Intn(len(conns)) + c := conns[n] + ps[c.from].DeleteTestConnection(peer[c.from], peer[c.to]) + ps[c.from].GarbageCollect() + checkApplyUpdate(t, ps[c.from]) + conns = append(conns[:n], conns[n+1:]...) + } + } + } +} + +func TestPeersGarbageCollection(t *testing.T) { + const ( + peer1NameString = "01:00:00:01:00:00" + peer2NameString = "02:00:00:02:00:00" + peer3NameString = "03:00:00:03:00:00" + ) + var ( + peer1Name, _ = PeerNameFromString(peer1NameString) + peer2Name, _ = PeerNameFromString(peer2NameString) + peer3Name, _ = PeerNameFromString(peer3NameString) + ) + + // Create some peers with some connections to each other + p1, ps1 := newNode(peer1Name) + p2, ps2 := newNode(peer2Name) + p3, ps3 := newNode(peer3Name) + ps1.AddTestConnection(p2) + ps2.AddTestRemoteConnection(p1, p2) + ps2.AddTestConnection(p1) + ps2.AddTestConnection(p3) + ps3.AddTestConnection(p1) + ps1.AddTestConnection(p3) + ps2.AddTestRemoteConnection(p1, p3) + ps2.AddTestRemoteConnection(p3, p1) + + // Every peer is referenced, so nothing should be dropped + wt.AssertEmpty(t, ps1.GarbageCollect(), "peers removed") + wt.AssertEmpty(t, ps2.GarbageCollect(), "peers removed") + wt.AssertEmpty(t, ps3.GarbageCollect(), "peers removed") + + // Drop the connection from 2 to 3, and 3 isn't garbage-collected + // because 1 has a connection to 3 + ps2.DeleteTestConnection(p2, p3) + wt.AssertEmpty(t, ps2.GarbageCollect(), "peers removed") + + // Drop the connection from 1 to 3, and 3 will get removed by + // garbage-collection + ps1.DeleteTestConnection(p1, p3) + checkPeerArray(t, ps1.GarbageCollect(), p3) +}